目录
1. 漏洞描述 2. 漏洞触发条件 3. 漏洞影响范围 4. 漏洞代码分析 5. 防御方法 6. 攻防思考
1. 漏洞描述
PHP语言作为开源社区的一员,提供了各种模板引擎,如FastTemplate,Smarty,SimpleTemplate等,而Smarty是现在使用得比较多的PHP模板引擎
ecshop的这个getshell代码执行getshell漏洞,是一种典型的模版tag语言动态解析导致的漏洞,像smarty这类的动态模版语言允许在静态页面中插入smarty定义的”标签语言(tag php)”,程序在执行的时候,会对这些标签进行对应的解析渲染,这原本是为了提高网站开发的”解耦和性”,将前端开发和后端逻辑的开发进行最大程度的分离
但是在smarty的众多的模版标签中,有一类特殊用途的标签,”代码动态执行的标签(code execute tag)”,这类标签允许在标签中插入php代码,程序会在执行时动态地执行这些代码
ecshop中有一条逻辑攻击流支,允许对网站的模版进行编辑,黑客可以输入恶意的php代码标签(例如用于getshell的写文件php代码),当访问这个模版文件、或者其他的文件包含了这个模版文件的时候,恶意代码就会得到执行
Relevant Link:
http://baike.baidu.com/view/399896.htm?fr=aladdin http://www.myhack58.com/Article/html/3/62/2010/27762.htm
2. 漏洞触发条件
0x1: 需要登录后台
这个漏洞需要黑客能够登录到ecshop的后台,进行后台的模版编辑操作
0x2: 向模版中插入php tag代码
依次按如下操作
模块管理 -> 库项目管理 -> 选择myship.lbi 配送方式 -> 插入<?php eval($_GET['op'])?>
完成模板的修改,向模板文件中插入PHP代码之后,完成修改之后,PHP代码就插入到了模板文件中
\ecshop\themes\default\library\myship.lbi
接下来需要能够触发这个模板文件中的代码执行
访问Exploit URL
http://localhost/ecshop/myship.php
编译后的静态模板文件被保存到”\temp\compiled\myship.lbi.php”中,在缓存命中期间(有一个最大过期时间),之后访问myship.php就不用重新编译,而是直接访问这个静态模版文件
这次的Exploit URL访问的攻击流程如下
1. myship.php调用"\includes\cls_template.php"中的"make_compiled()"对模板"myship.lbi"进行编译、执行 2. 被插入到"myship.lbi"中的PHP代码得到执行 3. 编译后的静态模板文件被保存到"\temp\compiled\myship.lbi.php"中 4. myship.php会包含(include)这个静态模板文件,被插入模板中的php代码得到执行 5. getshell完成
此后,这个myship.php就可以看成一个webshell文件
Relevant Link:
3. 漏洞影响范围
0x1: 存在漏洞的CMS版本
ECShop_V2.7.2 ECShop_V2.7.2 及以前版本
4. 漏洞代码分析
回顾这个漏洞,我们会发现,这个漏洞的根源在于程序没有对用户编辑的模版文件进行正确的恶意检测,就直接进行了”编译”,从而把恶意php代码带入了编译后静态模板文件中,DEDECMS中也有很多类似的模版解析漏洞
\includes\cls_template.php
/** * 编译模板函数 * * @access public * @param string $filename * * @return sring 编译后文件地址 */ function make_compiled($filename) { //编译后的静态模板文件保存的路径 $name = $this->compile_dir . '/' . basename($filename) . '.php'; //判断缓存的静态模板文件是否过期 if ($this->_expires) { $expires = $this->_expires - $this->cache_lifetime; } else { $filestat = @stat($name); $expires = $filestat['mtime']; } $filestat = @stat($filename); if ($filestat['mtime'] <= $expires && !$this->force_compile) { if (file_exists($name)) { //引入编译后的静态模板文件 $source = $this->_require($name); if ($source == '') { $expires = 0; } } else { $source = ''; $expires = 0; } } //对模板文件进行解析 if ($this->force_compile || $filestat['mtime'] > $expires) { $this->_current_file = $filename; $source = $this->fetch_str(file_get_contents($filename)); if (file_put_contents($name, $source, LOCK_EX) === false) { trigger_error('can\'t write:' . $name); } $source = $this->_eval($source); } return $source; }
代码中的关键行是:
$source = $this->fetch_str(file_get_contents($filename));
我们继续分析这个函数
/** * 处理字符串函数 * * @access public * @param string $source * * @return sring */ function fetch_str($source) { if (!defined('ECS_ADMIN')) { $source = $this->smarty_prefilter_preCompile($source); } //程序没有对即将解析的模板内容作任何的恶意检测 return preg_replace_callback("/{([^\}\{\n]*)}/", function($r) { return $this->select($r[1]); }, $source); }
从代码中可以很清楚的看到,程序直接对用户编辑后的模板文件进行了”编译(本质就是PHP的动态变量替换机制)”,并没有对模板文件进行恶意代码检测
5. 防御方法
0x1: 代码patch
ecshop v2.7.3 release 1106安全漏洞补丁[20130708]
http://bbs.ecshop.com/thread-1131753-1-1.html
通过对比官方的patch code和2.7.2 vul code,我们可以发现,patch代码在对模板代码的解析的时候
patch file
/** * 处理字符串函数 * * @access public * @param string $source * * @return sring */ function fetch_str($source) { if (!defined('ECS_ADMIN')) { $source = $this->smarty_prefilter_preCompile($source); } $source=preg_replace("/([^a-zA-Z0-9_]{1,1})+(copy|fputs|fopen|file_put_contents|fwrite|eval|phpinfo)+( |\()/is", "", $source); if(preg_match_all('~(<\?(?:\w+|=)?|\?>|language\s*=\s*[\"\']?php[\"\']?)~is', $source, $sp_match)) { $sp_match[1] = array_unique($sp_match[1]); for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) { $source = str_replace($sp_match[1][$curr_sp],'%%%SMARTYSP'.$curr_sp.'%%%',$source); } for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) { $source= str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '<?php echo \''.str_replace("'", "\'", $sp_match[1][$curr_sp]).'\'; ?>'."\n", $source); } } $resutl = preg_replace_callback("/{([^\}\{\n]*)}/", function($r) { return $this->select($r[1]); }, $source); return $resutl; }
0x2: 脏数据回滚
这个漏洞造成的影响
1. 除了可以使黑客通过myship.php将恶意PHP代码写入磁盘文件中 2. 同时恶意代码还会以静态缓存文件的形式在一定时间内保存在磁盘上,这个静态缓存文件中包含的恶意代码会在缓存有效期间一直有效
当黑客再次访问myship.php文件的时候,即使这个myship.php已经进行了代码修复,仍然可以引入这个包含恶意代码的静态缓存文件,所以要彻底修复这个漏洞,需要能够对已经被污染的磁盘文件进行清理,即脏数据删除、回滚
被黑客污染的文件有2个
1. \temp\compiled\myship.lbi.php:由myship.lbi解析后生成 2. \themes\default\library\myship.lbi
其中myship.lbi是黑客可以编辑的模板文件,不能删除,而myship.lbi.php是动态生成出来的,在有效期间内会一直有效
如果需要进行脏数据清理、回滚,就需要进行如下操作(在确认文件中有PHP恶意代码的情况下)
1. 对myship.lbi中的恶意代码进行清除:只能修改文件,不能删除文件 2. 对myship.lbi.php的恶意文件进行删除:直接删除文件
0x3: 最终修复方案
1. code patch 2. 对myship.lbi进行检测,如果发现有恶意PHP代码,则进行清除(修改文件) 3. 对myship.lbi.php进行检测,如果发现有恶意PHP代码,则直接删除文件
0x4: 基于边界防御的代码修复
针对cls_template.php进行底层代码修复是一个很好的防御手段,但是这也同时会带来问题,因为模版解析是ECSHOP CMS本身的功能,我们在cls_template.php底层对所有的模版标签中的PHP代码进行了转义,就等于禁用了网站本身的模版功能。
在这种安全和功能紧耦合的攻防场景下,底层防御往往会对正常业务造成影响,因此,我们可以采用边界防御的思路,针对模版修改的人口进行恶意防御
\admin\template.php
if ($_REQUEST['act'] == 'update_library') { check_authz_json('library_manage'); /* */ $temp_check = json_str_iconv($_POST['html']); $temp_check = preg_replace("/<\?[^><]+(\?>){0,1}|<\%[^><]+(\%>){0,1}|<\%=[^><]+(\%>){0,1}|<script[^>]+language[^>]*=[^>]*php[^>]*>[^><]*(<\/script\s*>){0,1}/iU", "", $temp_check); $temp_check = preg_replace("/([^a-zA-Z0-9_]{1,1})+(extract|parse_str|str_replace|unserialize|ob_start|require|include|array_map|preg_replace|copy|fputs|fopen|file_put_contents|file_get_contents|fwrite|eval|phpinfo|assert|base64_decode|create_function|call_user_func)+( |\()/is", "", $temp_check); $html = stripslashes($temp_check); /* */ $lib_file = '../themes/' . $_CFG['template'] . '/library/' . $_POST['lib'] . '.lbi'; $lib_file = str_replace("0xa", '', $lib_file); // 过滤 0xa 非法字符 $org_html = str_replace("\xEF\xBB\xBF", '', file_get_contents($lib_file)); if (@file_exists($lib_file) === true && @file_put_contents($lib_file, $html)) { @file_put_contents('../temp/backup/library/' . $_CFG['template'] . '-' . $_POST['lib'] . '.lbi', $org_html); make_json_result('', $_LANG['update_lib_success']); } else { make_json_error(sprintf($_LANG['update_lib_failed'], 'themes/' . $_CFG['template'] . '/library')); } }
对POST上传的入口进行恶意防御
需要特别注意的是,对于ecshop模版中插入PHP恶意代码这个攻击向量场景来说,防御代码需要考虑以下几个方面
1. PHP的起止标签具有很强的灵活性 1) <?php ... ?> 2) <? ... ?> 3) <script language="php">...</script> 4) <?=expression ... ?> 5) <% ... %> 6) <%=$variable %> 2. PHP允许半开的标签,即当PHP代码和HTML代码混编的时候,处于文件最末尾的PHP代码不需要闭合标签即可正确执行 3. ECSHOP自身支持一些模版解析语法,即ECSHOP定义了一些定界标签,ECSHOP的解析引擎会将这些标签中的代码作为PHP代码进行解释执行,并返回结果 1) 针对目前常见的webshell特征采用黑名单方式进行检测
6. 攻防思考
针对这类模版动态标签解析漏洞的防御,有2种防御思路
1. 边界防御: 对\admin\template.php、\admin\lib_template.php、\admin\mail_template.php进行修复 在可能导致模版修改的边界对POST数据包进行恶意代码检测 2. 底层防御: 对cls_template.php加上PHP代码转义 http://www.cnblogs.com/LittleHann/p/3574694.html
Copyright (c) 2014 LittleHann All rights reserved