学习审计通达OA时发现的一些有意思的事
全局变量覆盖
审计时发现前辈们提到了这个问题,跟了下存在问题的文件
比如存在变量覆盖的文件是pda\vote\list.php
,
require_once "pda/auth.php"; include_once "inc/conn.php"; include_once "inc/utility_all.php"; include_once "mobile/api/qyapp.vote.class.php";
if ($P == "") { $P = $_COOKIE["PHPSESSID"]; } else { $P = $_GET["P"]; }
|
这里包含了inc/conn.php
数据库连接文件

继续跟,发现包含了inc/td_config.php
<?php
include_once "inc/common.inc.php"; $ROOT_PATH = ($_SERVER["DOCUMENT_ROOT"] ? $_SERVER["DOCUMENT_ROOT"] : "");
if ($ROOT_PATH == "") { $ROOT_PATH = str_replace("\\", "/", realpath(dirname(__FILE__) . "/../")); }
if (substr($ROOT_PATH, -1) != "/") { $ROOT_PATH .= "/"; }
|
包含了inc/common.inc.php
<?php
if (0 < count($_COOKIE)) { foreach ($_COOKIE as $s_key => $s_value ) { if ((substr($s_key, 0, 7) == "_SERVER") || (substr($s_key, 0, 8) == "_SESSION") || (substr($s_key, 0, 7) == "_COOKIE") || (substr($s_key, 0, 4) == "_GET") || (substr($s_key, 0, 5) == "_POST")) { continue; }
if (!is_array($s_value)) { $_COOKIE[$s_key] = addslashes(strip_tags($s_value)); }
$s_key = $_COOKIE[$s_key]; }
reset($_COOKIE); }
if (0 < count($_POST)) { $arr_html_fields = array();
foreach ($_POST as $s_key => $s_value ) { if ((substr($s_key, 0, 7) == "_SERVER") || (substr($s_key, 0, 8) == "_SESSION") || (substr($s_key, 0, 7) == "_COOKIE") || (substr($s_key, 0, 4) == "_GET") || (substr($s_key, 0, 5) == "_POST")) { continue; }
if (substr($s_key, 0, 15) != "TD_HTML_EDITOR_") { if (!is_array($s_value)) { $_POST[$s_key] = addslashes(strip_tags($s_value)); }
$s_key = $_POST[$s_key]; } else { if (($s_key == "TD_HTML_EDITOR_FORM_HTML_DATA") || ($s_key == "TD_HTML_EDITOR_PRCS_IN") || ($s_key == "TD_HTML_EDITOR_PRCS_OUT") || ($s_key == "TD_HTML_EDITOR_QTPL_PRCS_SET") || ($_POST["ACTION_TYPE"] && (($_POST["ACTION_TYPE"] == "approve_center") || ($_POST["ACTION_TYPE"] == "workflow") || ($_POST["ACTION_TYPE"] == "sms") || ($_POST["ACTION_TYPE"] == "wiki")) && (($s_key == "CONTENT") || ($s_key == "TD_HTML_EDITOR_CONTENT") || ($s_key == "TD_HTML_EDITOR_TPT_CONTENT")))) { unset($_POST[$s_key]); $s_key = ($s_key == "CONTENT" ? $s_key : substr($s_key, 15)); $s_key = addslashes($s_value); $arr_html_fields[$s_key] = $s_key; } else { $encoding = mb_detect_encoding($s_value, "GBK,UTF-8"); unset($_POST[$s_key]); $s_key = substr($s_key, 15); $s_key = addslashes(rich_text_clean($s_value, $encoding)); $arr_html_fields[$s_key] = $s_key; } } }
reset($_POST); $_POST = array_merge($_POST, $arr_html_fields); }
if (0 < count($_GET)) { foreach ($_GET as $s_key => $s_value ) { if ((substr($s_key, 0, 7) == "_SERVER") || (substr($s_key, 0, 8) == "_SESSION") || (substr($s_key, 0, 7) == "_COOKIE") || (substr($s_key, 0, 4) == "_GET") || (substr($s_key, 0, 5) == "_POST")) { continue; }
if (!is_array($s_value)) { $_GET[$s_key] = addslashes(strip_tags($s_value)); }
$s_key = $_GET[$s_key]; }
reset($_GET); }
unset($s_key); unset($s_value);
?>
|
只要包含了这个文件,就会存在该问题
注入过滤规则
笔者对比了2013~2019(V11)的过滤SQL注入的代码,高版本的防注代码在inc\conn.php
的sql_injection
函数,低版本的在exequery
处理,都是基于黑名单模式进行防注处理,80sec的软waf
2013-exequery
function exequery( $C, $Q, $LOG = FALSE ) { if ( !$LOG ) { $POS = ( $Q, "union" ); if ( $POS !== FALSE && ( $Q, "select", $POS ) !== FALSE ) { exit( ); } $POS = ( $Q, "into" ); if ( $POS !== FALSE && ( ( $Q, "outfile", $POS ) !== FALSE || ( $Q, "dumpfile", $POS ) !== FALSE ) ) { exit( ); } } if ( ( $C ) ) { } if ( !( $C ) ) { if ( ( $C ) ) { ( $C ); } $C = ( $C ); } if ( !( $C ) ) { ( ( "无效的数据库连接" )."<br><b>".( "SQL语句:" )."</b> ".$Q, $LOG ); return FALSE; } $cursor = @( $Q, $C ); if ( !$cursor ) { ( "<b>".( "SQL语句:" )."</b> ".$Q, $LOG ); } return $cursor; }
|
可以看到早年的通达仅仅检测了union
、select
、into
、outfile
、dumpfile
这几个关键字,导致可绕过的方式很多,比如常用于盲注的sleep
等
2013adv-sql_injection
function sql_injection( $db_string ) { $clean = ""; $error = ""; $old_pos = 0; $pos = -1; do { $pos = ( $db_string, "'", $pos + 1 ); $clean .= ( $db_string, $old_pos, $pos - $old_pos ); do { $pos1 = ( $db_string, "'", $pos + 1 ); $pos2 = ( $db_string, "\\", $pos + 1 ); if ( !$pos2 && $pos1 < $pos2 ) { $pos = $pos1; break; } else { $pos = $pos2 + 1; } } while ( 1 ); $clean .= "\$s\$"; $old_pos = $pos + 1; } while ( 1 ); $clean .= ( $db_string, $old_pos ); $clean = ( ( ( array( "~\\s+~s" ), array( " " ), $clean ) ) ); $fail = FALSE; if ( ( $clean, "union" ) !== FALSE && ( "~(^|[^a-z])union(\$|[^[a-z])~s", $clean ) != 0 ) { $fail = TRUE; $error = ( "联合查询" ); } else if ( 2 < ( $clean, "/*" ) || ( $clean, "--" ) !== FALSE || ( $clean, "#" ) !== FALSE ) { $fail = TRUE; $error = ( "注释代码" ); } else if ( ( $clean, "sleep" ) !== FALSE && ( "~(^|[^a-z])sleep(\$|[^[a-z])~s", $clean ) != 0 ) { $fail = TRUE; $error = "sleep"; } else if ( ( $clean, "benchmark" ) !== FALSE && ( "~(^|[^a-z])benchmark(\$|[^[a-z])~s", $clean ) != 0 ) { $fail = TRUE; $error = "benchmark"; } else if ( ( $clean, "load_file" ) !== FALSE && ( "~(^|[^a-z])load_file(\$|[^[a-z])~s", $clean ) != 0 ) { $fail = TRUE; $error = ( "Load文件" ); } else if ( ( $clean, "cast" ) !== FALSE && ( "~(^|[^a-z])mid(\$|[^[a-z])~s", $clean ) != 0 ) { $fail = TRUE; $error = "cast"; } else if ( ( $clean, "ord" ) !== FALSE && ( "~(^|[^a-z])ord(\$|[^[a-z])~s", $clean ) != 0 ) { $fail = TRUE; $error = "ord"; } else if ( ( $clean, "ascii" ) !== FALSE && ( "~(^|[^a-z])ascii(\$|[^[a-z])~s", $clean ) != 0 ) { $fail = TRUE; $error = "ascii"; } else if ( ( $clean, "extractvalue" ) !== FALSE && ( "~(^|[^a-z])extractvalue(\$|[^[a-z])~s", $clean ) != 0 ) { $fail = TRUE; $error = "extractvalue"; } else if ( ( $clean, "updatexml" ) !== FALSE && ( "~(^|[^a-z])updatexml(\$|[^[a-z])~s", $clean ) != 0 ) { $fail = TRUE; $error = "updatexml"; } else if ( ( $clean, "into outfile" ) !== FALSE && ( "~(^|[^a-z])into\\s+outfile(\$|[^[a-z])~s", $clean ) != 0 ) { $fail = TRUE; $error = ( "生成文件" ); } if ( $fail ) { echo ( "不安全的SQL语句:" ).$error."<br />"; echo ( $db_string ); exit( ); } return $db_string; }
|
2013adv的使用了sql_injection
函数进行防注处理(过滤了updatexml
、into outfile
、extractvalue
、ascii
、ord
、cast
、sleep
、benchmark
、load_file
、union
关键字)
2015~2016
这两个版本没什么变化,2015相对2013adv新增exp
黑名单
2017
2017相对2016新增处理 
,查表后是对应的ascii是空格

V11
与2017相同,不放代码了
td_framework数据库查询处理流程
通达的td_framework
框架(可能看的不是太清,建议新开标签页查看)

总结
由于数据库是GBK编码(2013~V11都是),虽然使用了addslashes
函数进行处理引号等字符,但是可使用宽字节%df%27
进行绕过。通达OA这个产品由于架构比较老导致问题比较多,考虑到市场份额和影响,不放用于攻击的EXP。