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

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

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

 $str) {
    echo complex($re, $str). "\n"; //打印
}
​
function getFlag(){
    @eval($_GET['cmd']);
}
//在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的用法:

 

 利用preg_replace与正则表达式实现任意代码执行_第1张图片

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

关键词备注:

pattern
要搜索的模式。可以使一个字符串或字符串数组。 
可以使用一些PCRE修饰符。 

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

subject
要进行搜索和替换的字符串或字符串数组。 
如果subject是一个数组,搜索和替换回在subject 的每一个元素上进行, 并且返回值也会是一个数组。 
​

第一个点

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

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

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

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

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

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

利用preg_replace与正则表达式实现任意代码执行_第2张图片

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

第二个点

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

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

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

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

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

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

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

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

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

利用preg_replace与正则表达式实现任意代码执行_第3张图片

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

第三个点

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

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

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

利用preg_replace与正则表达式实现任意代码执行_第4张图片 

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

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

 利用preg_replace与正则表达式实现任意代码执行_第5张图片

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

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

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

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

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

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

 利用preg_replace与正则表达式实现任意代码执行_第6张图片

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

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

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

你可能感兴趣的:(安全,前端,PHP,android,android,studio,ide,php,web安全,安全,正则表达式)