2020-09-08

文章转载整理于深入研究preg_replace与代码执行

0x01 前言

该文章主要研究preg_replace /e模式下的RCE中出现的一些小坑,以及利用$_GET替换非法字符、${}可变变量 这两个知识点来构造RCE的payload

以下内容基于题目[BJDCTF2020]ZJCTF,不过如此

0x02 踩坑

先附上[BJDCTF2020]ZJCTF,不过如此这道题有关preg_replace的源码:

<?php
$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']);
}

先给出这道题的payload模板:

payload 1: /?.*={${phpinfo()}}
payload 2: ?\S*={${phpinfo()}}或${phpinfo()}

这里payload1无法执行成功,payload2可以执行成功

结合源码我们思考这几个问题:

  1. preg_replace('/(' . $re . ')/ei','strtolower("\\1")',$str);中第二个参数已经固定为'strtolower("\\1")',那我们怎么还能构造自己执行的代码呢?
  2. 为什么payload中的.*不行而\S可以?
  3. 为什么要用${phpinfo()}{${phpinfo()}}这样的格式来实现代码的执行呢?为什么不能直接是phpinfo?

问题1

上面的命令执行相当于eval('strtolower("\\1");')的结果,而\\1是实际上是\1,而\1在PCRE中有特定的含义:

反向引用

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

关于反向引用,这里不做过多解释,想要深入了解的可以看这两篇文章

正则之反向引用

正则中\1的用法—反向引用

也就是说这里的\1实际上是第一个子匹配项,而在匹配模式中/(' . $re . ')/ei正好有用()括起来的子匹配项,因此可以使用反向引用,至此,我们可以知道在preg_replace中代码执行的逻辑是/(' . $re . ')/ei匹配到$str(传参进去也就是${phpinfo()})的内容,但是这个内容并不能直接作为代码执行,preg_replace是第二个参数(replacement)才能作为代码执行,因此第二个参数'strtolower("\\1");'反向引用/(' . $re . ')/ei匹配到的$str,这样就可以实现$str的代码执行了

问题2

这里涉及$_GET对非法参数的处理问题,先看下面这个例子

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hUiJJPSZ-1599572343887)(C:\Users\86188\AppData\Roaming\Typora\typora-user-images\image-20200908203401678.png)]

这里传入参数?.*=abc后,在php中.被替换为_下划线,因此$_GET接收参数时会把非法字符转化为下划线

这里fuzz一波哪些字符串会被替换

import requests
for i in range(0,256):
    url='http://10.11.184.39/test5.php?ha'+chr(i)+'ha=abc'
    r=requests.get(url)
    if '_' in r.text:
        print(str(i)+':'+chr(i))

image-20200908213710050

以上字符被替换了。但是需要注意:

  • +在开头,例如?+haha=123,这时+会被url解释为空格,然后传入参数中开头的空格会被去掉,只剩下haha
  • 以上测试都是将特殊字符放在字符串中间,而在开头只有.会变成下划线,其余字符不会变成下划线

通过上面的测试分析,我们知道了.*传入后实际上变为了_*这也就使得我们的贪婪匹配失效了,因此我们需要替换这一匹配规则,就有了payload2中的\S*(\S匹配所有非空白字符,\s匹配所有空白字符)因此\S*可以完全匹配到我们$str传入的代码

问题3

最后说说为什么要匹配到${xx}{${xx}}这样的格式才能执行其中的函数。实际上这是php可变变量的原因。在php双引号包裹的字符串中可以解析变量,${phpinfo()} 中的 phpinfo() 会被当做变量先执行,执行后变为 ${1}(phpinfo执行完返回true)。简单理解的话,就是在preg_replace中,匹配到了${xx}先会执行${}中的内容。

0x03 总结

这是做题中的一些思考,也算是提升了自己对于正则的理解能力和涨姿势了吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值