学习审计通达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数据库连接文件

image-20200723103126920

继续跟,发现包含了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.phpsql_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;
}

可以看到早年的通达仅仅检测了unionselectintooutfiledumpfile这几个关键字,导致可绕过的方式很多,比如常用于盲注的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函数进行防注处理(过滤了updatexmlinto outfileextractvalueasciiordcastsleepbenchmarkload_fileunion关键字)

2015~2016

这两个版本没什么变化,2015相对2013adv新增exp黑名单

2017

2017相对2016新增处理&#160;,查表后是对应的ascii是空格

image-20200723110633783

V11

与2017相同,不放代码了

td_framework数据库查询处理流程

通达的td_framework框架(可能看的不是太清,建议新开标签页查看)

未命名表单

总结

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