全版本Pbootcms Rce分析

PS:此rce仍未公布,属于0day状态,请各位务必小心

起因

在某个风和日丽的早晨,群里某位大佬丢给了我两个pboot的0day,一个是sql,一个是rce。群里某位大佬又通过此sql挖出了另一个sql,而我这个菜鸡在旁边不知道干什么,为了不让自己尴尬,随手就开始分析Pboot的全版本rce

附上群里某佬学有所成对我的嘲笑
Screenshot_2021-08-13-14-31-11-400_com.tencent.mobileqq.jpg

对,没错,就是那个粉色头像的家伙

经过

  1. exp: #}{pboot:if(1==1)(@phpin.@fo)();//)}test{/pboot:if

这种语句很奇怪吧,反正我是没见到过。于是,我去查了一下pboot的开发手册,发现是这样的

  1. 注意:条件语句中字符串需要用单引号或双引号;
  2. 所有对其它标签的调用都为字符串,需要加单引号。
  3. {pboot:if('a'=='b')}
  4. 内容1
  5. {else}
  6. 内容2
  7. {/pboot:if}
  8. 示例一:在IF中使用PHP函数示例:
  9. {pboot:if(date('Y')==2018)}2018年{/pboot:if}

pboot专门设置了一个if标签,方便模板的输出

从exp我们不难可以看出,是围绕if标签操作的,相应的,肯定要经过对if标签解析的函数

来到app/home/controller/ParserController.php文件下,转到parserIfLabel()函数

1.png

源码就不放了,着实太长了

这个函数会解析当前页面的所有if标签,并进行循环处理

直接转到关键点

2.png

可以看到,这里eval执行了相关标签。

至于matches数组,来自于前面匹配的输出值,也就是来自于content

  1. preg_match_all(表达式,内容,结果(数组形式))

接下来我们看看哪儿调用了parserIfLabel()函数
3.png
通过搜索可以得知,同控制器下的parserAfter方法调用了parserIfLabel方法

那哪儿又调用了parserAfter呢?
4.png
index控制器中,有多处调用parserAfter

而进入getIndex()的条件就是$pathinfo、$path以及p参数都为空

道理我都懂,但为什么这个exp能成功呢?现在debug试一下

我们的exp在第21个,在数组中也就是20

等到i(也就是计数器)为20时,我们的exp已经变样了
6.png
语句就理所当然的变成了

  1. eval('if(1==1)(phpin.fo)();//){$flag="if";}else{$flag="else";}');

只要是个明眼人,那都能看明白了,为什么能执行phpinfo了,字符串拼接绕过+if语句条件闭合

眼尖的人发现,这中间还有个decode_string函数,会不会对exp造成影响呢?

答案是,不会

5.png6.png
至于exp为什么会变成这样,那是因为正则匹配的缘故

而经过本地测试以上的语句,是能单独执行成功的
7.png
最后一个问题,我们是如何把exp写入主页的?

从上面的步骤可以得知,攻击链大概是这样一个情况

主页内容——>parserAfter($content)———->parseIfLable()处理标签———->eval()执行,最重要的点就在于如何把if标签写入主页内容中

刚开始我也很疑惑,明明pboot的点写的比较死,无论怎么传参都会被拦下来,无论是老的keyword链或是searchtpl都已经被防住了,他到底是怎么实现的。

然后,我被雷住了。。。

8.png
我也突然明白了为什么浏览器中不能复现成功的原因,#后面的任何数据,在浏览器中都会被截断,因为只是给客户端看的。而jQuery发送的ajax请求却可以带上#后面的数据,同样的,burp的功能可不止是浏览器那么简单

相应的,我也找到了二维码生成的对应函数
9.png
同样,它的调用点也在parserAfter

10.png
只要经过index控制器,就能进行漏洞触发

果不其然,就是在这个函数中生成的值
11.png
至此,整条攻击链,就已经清楚明了了

exp也没必要那么繁琐,本人略微修改了一下,删去了六个无用字符

  1. #}{pboot:if(1)(phpin.fo)();#)}{/pboot:if

最后

好家伙,我直呼好家伙

全版本Pbootcms Rce分析 - 图14