全版本Pbootcms Rce分析
PS:此rce仍未公布,属于0day状态,请各位务必小心
起因
在某个风和日丽的早晨,群里某位大佬丢给了我两个pboot的0day,一个是sql,一个是rce。群里某位大佬又通过此sql挖出了另一个sql,而我这个菜鸡在旁边不知道干什么,为了不让自己尴尬,随手就开始分析Pboot的全版本rce
附上群里某佬学有所成对我的嘲笑
对,没错,就是那个粉色头像的家伙
经过
exp: #}{pboot:if(1==1)(@phpin.@fo)();//)}test{/pboot:if
这种语句很奇怪吧,反正我是没见到过。于是,我去查了一下pboot的开发手册,发现是这样的
注意:条件语句中字符串需要用单引号或双引号;
所有对其它标签的调用都为字符串,需要加单引号。
{pboot:if('a'=='b')}
内容1
{else}
内容2
{/pboot:if}
示例一:在IF中使用PHP函数示例:
{pboot:if(date('Y')==2018)}2018年{/pboot:if}
pboot专门设置了一个if标签,方便模板的输出
从exp我们不难可以看出,是围绕if标签操作的,相应的,肯定要经过对if标签解析的函数
来到app/home/controller/ParserController.php文件下,转到parserIfLabel()函数
源码就不放了,着实太长了
这个函数会解析当前页面的所有if标签,并进行循环处理
直接转到关键点
可以看到,这里eval执行了相关标签。
至于matches数组,来自于前面匹配的输出值,也就是来自于content
preg_match_all(表达式,内容,结果(数组形式))
接下来我们看看哪儿调用了parserIfLabel()函数
通过搜索可以得知,同控制器下的parserAfter方法调用了parserIfLabel方法
那哪儿又调用了parserAfter呢?
index控制器中,有多处调用parserAfter
而进入getIndex()的条件就是$pathinfo、$path以及p参数都为空
道理我都懂,但为什么这个exp能成功呢?现在debug试一下
我们的exp在第21个,在数组中也就是20
等到i(也就是计数器)为20时,我们的exp已经变样了
语句就理所当然的变成了
eval('if(1==1)(phpin.fo)();//){$flag="if";}else{$flag="else";}');
只要是个明眼人,那都能看明白了,为什么能执行phpinfo了,字符串拼接绕过+if语句条件闭合
眼尖的人发现,这中间还有个decode_string函数,会不会对exp造成影响呢?
答案是,不会
至于exp为什么会变成这样,那是因为正则匹配的缘故
而经过本地测试以上的语句,是能单独执行成功的
最后一个问题,我们是如何把exp写入主页的?
从上面的步骤可以得知,攻击链大概是这样一个情况
主页内容——>parserAfter($content)———->parseIfLable()处理标签———->eval()执行,最重要的点就在于如何把if标签写入主页内容中
刚开始我也很疑惑,明明pboot的点写的比较死,无论怎么传参都会被拦下来,无论是老的keyword链或是searchtpl都已经被防住了,他到底是怎么实现的。
然后,我被雷住了。。。
我也突然明白了为什么浏览器中不能复现成功的原因,#后面的任何数据,在浏览器中都会被截断,因为只是给客户端看的。而jQuery发送的ajax请求却可以带上#后面的数据,同样的,burp的功能可不止是浏览器那么简单
相应的,我也找到了二维码生成的对应函数
同样,它的调用点也在parserAfter
只要经过index控制器,就能进行漏洞触发
果不其然,就是在这个函数中生成的值
至此,整条攻击链,就已经清楚明了了
exp也没必要那么繁琐,本人略微修改了一下,删去了六个无用字符
#}{pboot:if(1)(phpin.fo)();#)}{/pboot:if
最后
好家伙,我直呼好家伙