背景
EBCMS(易贝管理系统)是一款基于PHP+MYSQL开发的网站管理系统,系统核心极致精简,所有功能都是通过应用扩展来实现。
漏洞简述
攻击链很简单
- 以Administator身份登录后台后
- 在系统设置的会员中心的有三处修改提示信息的表单(本文只验证注册成功表单),通过在其中插入
{php}phpinfo();{/php}
或者<?php phpinfo();?>
- 前台话题插件注册用户,即可获得注册成功的系统消息
- Getshell
代码分析
首先查看修改配置的控制器,index方法提交修改
<?php
namespace app\ebcms\controller;
class Config extends Common {
function index(){
if (\Ebcms::isPost()) {
$configs = \mylib\Input::post('configs');
if ($configs && is_array($configs)) {
foreach ($configs as $id => $value) {
\Ebcms::db() -> update('config', [
'value'=>$value
], [
'id'=>$id
]);
}
}
\mylib\Cache::delete('configs');
return $this -> success('提交成功!', \mylib\Route::buildUrl('/ebcms/config/index'));
}else{
$tmp = \Ebcms::db() -> select('config', '*', [
'ORDER' => [
'weight' => 'ASC',
'id' => 'ASC',
]
]);
$configs = [];
foreach ($tmp as $key => $value) {
if (!isset($configs[$value['group']])) {
$configs[$value['group']] = [];
}
$configs[$value['group']][] = $value;
}
$this -> assign('configs', $configs);
return $this -> html($this -> fetch());
}
}
}
跟进\mylib\Input::post()
后,再跟进\mylib\Input::filter_var
,发现对参数进行了htmlspecialchars过滤
<?php
...
public static function post($field, $default='', $filter=''){
if (self::has('post.'.$field)) {
return self::filter_var($_POST[$field], $filter);
}else{
return $default;
}
}
...
private static function filter_var($value, $filters=''){
if (!is_null($filters)) {
if (!$filters) {
$filters = \mylib\Config::get('sys.default_filters', ['htmlspecialchars']);
}
if (is_array($value)) {
foreach ($value as $key => $v) {
$value[$key] = self::filter_var($v, $filters);
}
}else{
foreach ($filters as $filter) {
if (is_callable($filter)) {
$value = call_user_func($filter, $value);
}
}
}
}
return $value;
}
那么,最后出现在数据库中的一定是转义后的<
和>
接着看/vendor/hetangyuese/xlphp/mylib/View.php
,这里存在可以将特定标签包围的php代码解析为原生php代码的功能。
public function parseTag($html){
$tags = [
'/{(foreach|if|for|switch)\s+(.*)}/U'=>function($matchs){
return '<?php '.$matchs[1].'('.$matchs[2].'){ ?>';
},
'/{function\s+(.*)}/U'=>function($matchs){
return '<?php function '.$matchs[1].'{ ?>';
},
'/{case\s+(.*)}/U'=>function($matchs){
return '<?php case '.$matchs[1].': ?>';
},
'/{default\s*}/U'=>function($matchs){
return '<?php default: ?>';
},
'/{php}/'=>function($matchs){
return '<?php ';
},
'/{\/php}/'=>function($matchs){
return ' ?>';
},
'/{\/(foreach|if|for|function|switch)}/'=>function($matchs){
return '<?php } ?>';
},
'/{\/(case|default)}/'=>function($matchs){
return '<?php break; ?>';
},
'/{(elseif)\s+(.*)}/U'=>function($matchs){
return '<?php }'.$matchs[1].'('.$matchs[2].'){ ?>';
},
'/{else\/?}/'=>function($matchs){
return '<?php }else{ ?>';
},
'/{include\s*([\w\.,\/]*)}/U'=>function($matchs){
$html = '';
$tpls = explode(',', $matchs[1]);
foreach ($tpls as $tpl) {
$html .= file_get_contents($this -> getTpl($tpl));
}
return $this -> parseTag($this -> buildLiteral($html));
},
'/{(\$[\w\[\'"\]]*)\.([\w\.]*)([^{}]*)}/'=>function($matchs){
return '{'.$matchs[1].'[\''.implode('\'][\'', explode('.', $matchs[2])).'\']'.$matchs[3].'}';
},
'/{(\$[\w\[\'"\]\-$]*)\?(.*):(.*)}/U'=>function($matchs){
return '{:isset('.$matchs[1].') ? '.($matchs[2]?:$matchs[1]).' : '.$matchs[3].'}';
},
'/{(\$[\w\[\'"\]\-$]*)}/U'=>function($matchs){
return '<?php echo '.$matchs[1].'; ?>';
},
'/{:(.*)\s*;?\s*}/U'=>function($matchs){
return '<?php echo '.$matchs[1].'; ?>';
},
'/\?>[\s\n]*<\?php/U'=>function($matchs){
return '';
},
];
foreach ($tags as $preg => $callback) {
$html = preg_replace_callback($preg, $callback, $html);
}
return $html;
}
那么不断向上回溯,可以发现
renderTpl函数,这个函数可以将传入的tpl模版渲染成html代码,那么接下来就找调用的控制器就可以了
public static function renderTpl($tpl, $data=[]){
$view = new \mylib\View(\mylib\Config::get('view'));
return $view -> render($tpl, $data);
}
最后分别定位到三处方法
在/app/member/controller/Auth.php
<?php
...
if ($tpl = \Ebcms::config('tpl_reg_success')) {
\Member::notice($member['id'], \Ebcms::renderTpl(htmlspecialchars_decode($tpl), [
'member'=>$member
]) ,'欢迎注册!');
}
...
if ($tpl = \Ebcms::config('tpl_password_reset')) {
\Member::notice($member['id'], \Ebcms::renderTpl(htmlspecialchars_decode($tpl),[
'member'=>$member
]) ,'密码重置提醒');
}
和/app/member/controller/Index.php中
if ($tpl = \Ebcms::config('tpl_phone_verify')) {
\Member::notice(\mylib\Session::get('member_id'), \Ebcms::renderTpl(htmlspecialchars_decode($tpl), [
'member'=>$member
]) ,'电话号码绑定成功!');
}
发现这三处,都对从数据库中取出来的数据进行了htmlspecialchars_decode
解码
那么原来实体编码导致的问题就迎刃而解了,因此也可以直接注入<?php phpinfo();?>
漏洞复现
修复方案
对后台代码进行必要的过滤
此处评论已关闭