登陆 注册

从PbootCMS审计到某狗绕过

Lzers 2020-07-24 代码审计安全文摘


这是 酒仙桥六号部队 的第 26 篇文章。

全文共计2033个字,预计阅读时长8分钟。


之前审计发现的PbootCMS2.0.3前台RCE,看了最近的版本更新漏洞被修复了,就放出之前的POC顺便看看能不能绕过补丁。 

项目地址:https://github.com/hnaoyun/PbootCMS 

PbootCMS自己实现了一个模板标签功能,在解析{pboot:if}标签的函数中使用了eval导致的任意代码执行。

1

PbootCMS2.0.3前台RCE

该cms在前台有留言功能,可以通过控制留言内容实现代码执行,但是需要在后台设置将留言内容显示。 

1)留言板插入标签代码

在2.0.3版本中留言板留言具体代码在\app\home\controller\IndexController.php的addMsg函数,

其中第270行有处过滤,使用双写即可绕过过滤,如pbootpboot:if:if,去留言板测试一下。

去后台可以看到插入成功。

2)解析if标签函数绕过

下面进入正戏:

解析if标签的函数为

\app\home\controller\ParserController.php的parserIfLabel函数。通过正则提取出所有if标签;

如刚才提交的内容,此时为变量matches0;

之后会将pboot:if标签()中的payload赋值给matches1,如之前提交的内容,此时matches1=1,接着再进行过滤。提取出左括号前的字符串,判断字符串是否是函数或者字符串为eval,并且字符串不在白名单中(date,in_array,explode,implode)。

这里正则有点瑕疵,一顿测试之后发现函数名与括号之间插入空格可以绕过该正则,并且不影响执行,如图:

具体的原理后来看到一篇文章,

https://www.leavesongs.com/PENETRATION/dynamic-features-and-webshell-tricks-in-php.html

在函数名和括号间可以插入控制字符[\x00-\x20],PHP引擎会忽略这些控制字符,接下来还有最后一处过滤就到了eval,胜利在望,此处正则匹配了一些常用的字符串。

个人测试时喜欢执行phpinfo,这个过滤了phpinfo,那就编码一下吧,还过滤了base64_decode,用chr拼接一下,注意chr和括号间也要插入空格,最终payload,插入之后需要后台管理员显示该留言,触发phpinfo:

{pbootpboot:if:if((eval ( chr (0x70).chr (0x68).chr (0x70).chr (0x69).chr (0x6e).chr (0x66).chr (0x6f).chr (0x28).chr (0x29).chr (0x3b))))}!!!{/pbootpboot:if:if}

2

PbootCMS2.0.7前台RCE

2.0.3之后过了一段时间再次看下这个cms,发现更新到了2.0.8,在2.0.8中暂时只能后台RCE,在2.0.7中还是可以留言板RCE的,只是加了一点点难度。

用之前的payload调试一下,发现在core\basic\Model.php的1255行,加了一处过滤,对最终的插入数据库的sql语句进行了一次过滤。

又过滤了一次pboot:if,那就在payload中再多加一层就好了,现在的payload变成这样:

{pbootpbootpboot:if:if:if((eval ( chr (0x70).chr (0x68).chr (0x70).chr (0x69).chr (0x6e).chr (0x66).chr (0x6f).chr (0x28).chr (0x29).chr (0x3b))))}!!!{/pbootpbootpboot:if:if:if}

之后又在

\apps\home\controller\ParserController.php的parserIfLabel中加了些黑名单,没有加assert,可见黑名单的防御方式还是不太靠谱的。

因此,将eval变成assert即可执行代码,payload变成这样:

{pbootpbootpboot:if:if:if((assert ( chr (0x70).chr (0x68).chr (0x70).chr (0x69).chr (0x6e).chr (0x66).chr (0x6f).chr (0x28).chr (0x29).chr (0x3b))))}!!!{/pbootpbootpboot:if:if:if}

测试一下,依旧可以执行成功。

3

PbootCMS2.0.8后台RCE

在2.0.8的

app\home\controller\MessageController.php的第61行提交留言的函数使用递归替换pboot:if字符串,因此双写操作不再管用了,也就无法在前台插入if标签,就无法走到解析if标签的函数了。

本想着既然前台RCE不行,去后台编辑一下网站信息之类的插入payload变成后台RCE算了,结果后台也不太顺利了。parserIfLabel函数的正则表达式变了,无法再通过函数名与括号之间插入空格来绕过了。

接下来是个比较骚的操作,看上图2549行的if判断,当函数在白名单中即可继续执行,白名单函数有些啥呢?

有date,in_array,explode,implode,乍一看是些没啥用的函数,但是经过一番冥思苦想,还是找到了可以利用的方式,只要将函数名写成数组,经由implode拼接成字符串,最后进入eval即可执行代码。

{pboot:if(implode('', ['c','a','l','l','_','u','s','e','r','_','f','u','n','c'])(implode('',['p','h','p','i','n','f','o'])))}!!!{/pboot:if}

if括号中的payload会最终进入到eval中执行,测试一下这种方式行不行,如图这样是可以执行代码的。

最后将payload插入到后台网站基本信息中,随便访问一个网页代码就会执行。

4

webshell绕过某狗

上文最后的绕过姿势主要使用了implode函数将数组元素拼接成了字符串,同时php有种可变函数的机制,如果一个变量名后面有圆括号,php将寻找与变量值同名的函数,并且尝试执行它。同时注意可变函数不能用于类似echo的语言结构,如何判断一个字符串能不能作为可变函数呢?

只需要

var_dump(function_exists('call_user_func'))返回true即可判断,如:

这个姿势除了能绕过上文的过滤还有啥用呢?想不到能干啥就过个狗吧。

环境搭起来,Apache2.4.39,某狗Apache版V4.0我们以执行系统命令的system函数做测试,首先判断一下system是不是一个函数;

返回true说明system能够作为可变函数使用,接着我们只使用可变函数看下会不会被狗拦截;

被狗咬了,我们再使用implode函数将system函数加工一下;

成功执行命令!

上文另外一个姿势,在函数名和圆括号之间插入控制字符能不能绕过狗呢?答案是可以!

先将十六进制转换为文本字符串;

复制这两个原点到php文件中,测试一下,执行成功!

请发表您的评论
请关注微信公众号
微信二维码
不容错过
Powered By HeiBaiTeam.