Web渗透之SQL注入-二次注入(Second-Order SQL Injection)

本文仅用于网络安全技术学习与授权测试交流。本文实验皆在靶场进行,任何未经授权使用文中技术的行为均与作者无关,请务必遵守法律法规,获得许可后方可进行渗透测试。

目录

一、概念

二、二次注入的原理

步骤详解

第一步:攻击者注入恶意载荷(存储)

第二步:应用程序在其他功能中使用该数据(触发)

第三步:注入影响

三、典型应用场景

四、与普通注入的对比

五、二次注入的根本原因

六、防御措施

七、检测方法

八、真实案例

九、靶场注入示例

一、环境与代码

二、攻击步骤

[1. 正常插入一条数据(作为基准)](#1. 正常插入一条数据(作为基准))

[2. 插入恶意用户(存储 payload)](#2. 插入恶意用户(存储 payload))

[3. 触发二次注入(修改密码)](#3. 触发二次注入(修改密码))

[4. 验证结果](#4. 验证结果)

三、漏洞原理

四、总结

十、VAuditDemo靶场注入再演示

一、靶场简介

二、注入过程

第一步:

第二步:

第三步:

第四步:

第五步:

第六步:

第七步:

第八步:

三、漏洞原理深入分析

四、与普通注入的区别

五、防御措施

六、总结


一、概念

二次注入是一种存储型 SQL 注入漏洞。它与普通(一次)注入的关键区别在于:攻击载荷不是立即触发的,而是先被存储到数据库中,之后在另一个不同的数据库操作中被取出并拼接到 SQL 语句中,从而实现注入

简单说:攻击者先通过一个入口(如注册)把恶意字符串"寄存"到数据库,再通过另一个入口(如修改资料)激活该恶意字符串,完成攻击。

二、二次注入的原理

二次注入的成功依赖于两个条件:

  1. 应用程序在第一次存储数据时 使用了安全的 SQL 处理(如参数化查询或转义),使得攻击者的 payload 不会在存储阶段造成危害,而是被原样当作普通字符串存入数据库。

  2. 应用程序在第二次使用该数据时 ,错误地认为"从数据库中取出的数据是可信的",直接将其拼接到 SQL 语句中,而没有再次进行参数化查询或转义。

步骤详解

第一步:攻击者注入恶意载荷(存储)

  • 攻击者在某个输入点(例如注册用户名、发表评论、填写个人签名)提交类似 admin' -- 的字符串。

  • 应用程序在生成插入数据的 SQL 语句时,使用了安全的处理方法。例如:

    复制代码
    // 使用参数化查询(安全)
    $stmt = $conn->prepare("INSERT INTO users (username, password) VALUES (?, ?)");
    $stmt->bind_param("ss", $username, $password);

    或者使用了转义函数:

    复制代码
    $username = addslashes($_POST['username']);
    $sql = "INSERT INTO users (username, password) VALUES ('$username', '$password')";

    由于转义或参数化,admin' -- 中的单引号被转义为 \',SQL 语句变为插入 admin\' -- 字符串,实际存储到数据库中的是 admin' --(注意:转义只在 SQL 语句中起分隔作用,反斜杠不会存入数据库;或者参数化查询直接存储原始值)。

    此时数据库中的用户名字段值为 admin' --

第二步:应用程序在其他功能中使用该数据(触发)

  • 攻击者登录(或者该用户名被用于其他业务),应用程序从数据库读取该用户名,例如在"修改密码"功能中:

    复制代码
    $username = $_SESSION['username']; // 从数据库或会话中获取,值为 'admin' --'
    $newpass = $_POST['newpass'];
    // 危险:直接拼接
    $sql = "UPDATE users SET password='$newpass' WHERE username='$username'";

    由于 $username 的值是 admin' --,拼接后 SQL 变为:

    复制代码
    UPDATE users SET password='newpass' WHERE username='admin' -- '

    在 SQL 中,-- 是注释符,后面的内容被忽略,所以实际执行的语句是:

    复制代码
    UPDATE users SET password='newpass' WHERE username='admin'

    攻击者成功修改了 admin 用户的密码,造成越权。

第三步:注入影响

根据 SQL 语句的不同,攻击可能产生多种后果:绕过登录验证、修改其他用户数据、删除数据、提取敏感信息等。

三、典型应用场景

二次注入常见于以下功能组合:

存储点(第一次) 触发点(第二次) 示例 payload
用户注册 修改密码 用户名 admin' -- → 修改密码时更新 admin
发表评论 后台管理员查看评论 评论内容 ' UNION SELECT ... → 管理员查看时执行恶意查询
个人资料设置 登录验证或搜索 昵称 test' OR '1'='1 → 登录时可能绕过认证
上传文件名 文件列表显示 文件名 test'; DROP TABLE files; -- → 显示时可能执行删除
搜索记录 热门搜索统计 搜索词 ' UNION SELECT password FROM users -- → 统计时可能泄露

四、与普通注入的对比

特性 普通(一次)注入 二次注入
触发时机 同一请求立即执行 后续请求中执行
是否需要数据库存储 不需要 必须存储到数据库
攻击载荷形式 通常直接提交到注入点 先以"无害"形式存储,再被取出
防护难点 输入过滤、参数化查询 开发者容易忽略"已存储数据"的风险
检测难度 可通过扫描器发现 需要模拟完整业务流程,较隐蔽

五、二次注入的根本原因

开发人员存在错误的安全假设

  • "我们已经对输入数据进行了转义或参数化,所以存储到数据库的数据是安全的。"

  • "从数据库取出的数据是可信的,不需要再过滤。"

这种假设忽略了数据可能包含恶意 SQL 语法元素(如单引号、注释符),当这些元素被拼接到新的 SQL 语句时,就会重新激活。

六、防御措施

  1. 始终使用参数化查询(预编译语句) 无论数据来自用户输入、数据库、文件还是任何其他源,只要拼接到 SQL 语句中,就必须使用参数化。这是根本性解决方案,与数据来源无关。

    复制代码
    $stmt = $conn->prepare("UPDATE users SET password=? WHERE username=?");
    $stmt->bind_param("ss", $newpass, $username);
  2. 对存储的数据也进行转义(若无法使用参数化) 若因遗留代码无法立即整改,在拼接前对从数据库取出的数据使用数据库特定的转义函数(如 mysqli_real_escape_string),但这不如参数化可靠。

  3. 最小权限原则 数据库账户只授予必要的权限。例如,用于 Web 应用的账户不应有 DROPTRUNCATE 权限;对于 UPDATE 操作,可限制其只能更新特定字段。

  4. 输入验证 在存储前,对输入数据做严格的类型校验(如数字型用 intval,枚举值用白名单)。例如,用户名可以限制只允许字母数字和下划线,这样即使存在二次注入点也无法构造 payload。

  5. 代码审计 重点检查"数据先存储后使用"的业务逻辑,例如:注册→登录、评论→审核、上传→下载等。查找从数据库读取数据后直接拼接到 SQL 的代码模式。

  6. 使用 ORM 框架 现代 ORM(如 Laravel Eloquent、Hibernate、Entity Framework)默认使用参数化查询,能大幅降低二次注入风险。

七、检测方法

  • 黑盒测试

    1. 在可能存储的位置(注册、评论等)输入 payload,如 test' OR '1'='1admin' --' UNION SELECT 1,2,3 --

    2. 然后访问可能使用这些数据的页面(修改资料、查看评论、重置密码等),观察是否出现异常(如登录了其他账户、页面显示额外数据、报错信息)。

    3. 比较响应与正常情况,判断是否存在注入。

  • 白盒测试

    1. 搜索代码中从数据库查询结果的赋值语句(如 $row['username'])。

    2. 追踪该变量是否在后续被用于拼接 SQL 语句(如 "SELECT ... WHERE name = '$name'")。

    3. 若拼接处未使用参数化查询,且 $name 来源于数据库,则可能存在二次注入。

八、真实案例

  • 邮箱激活 :用户注册时输入 user@example.com' OR '1'='1,应用程序安全存储。激活邮件链接中使用了该邮箱作为参数并拼接到 SQL 查询用户信息,导致返回所有用户数据。

  • 商品评价 :评价内容写入 test', 'popularity'=100 --,管理员在后台统计评分时未过滤,导致所有商品的 popularity 被修改。

九、靶场注入示例

一、环境与代码

demo6.php(用户注册/插入)

复制代码
$username = addslashes($_GET['username']);
$password = addslashes($_GET['password']);
$sql = "insert into userinfo(username,password) values('{$username}','{$password}')";
  • 使用 addslashes 转义单引号等特殊字符。

  • 转义只在 SQL 语句中生效,实际存入数据库的是原始字符串(例如 admin'-- - 中的单引号不会被转义存储,但 SQL 语句中变成 admin\'-- -,数据库存储的是 admin'-- -)。

demo7.php(修改密码)

复制代码
$username = $_GET['username'];   // 直接获取,未过滤
$password = $_GET['password'];
$sql = "update userinfo set password='{$password}' where username='{$username}'";
  • 直接拼接用户输入,存在 SQL 注入漏洞。

二、攻击步骤

1. 正常插入一条数据(作为基准)
复制代码
http://127.0.0.1/demo6.php?username=admin&password=123456
复制代码
insert into userinfo(username,password) values('admin','123456')

数据库中增加用户 admin,密码 123456

2. 插入恶意用户(存储 payload)

Payload

复制代码
http://127.0.0.1/demo6.php?username=admin'-- -&password=123456

代码执行流程

  1. addslashes 将用户名中的单引号 ' 转义为 \',即 admin\'-- -

  2. 拼接后的 SQL 语句变为:

    复制代码
    insert into userinfo(username,password) values('admin\'-- -','123456')
  3. 此时字符串中的 'admin\'-- -' 被当作一个完整的字符串值,其中的 -- 不再是 SQL 注释符 ,因为它被包含在单引号内。因此该 INSERT 语句正常执行,成功向数据库插入了一条记录,用户名字段值为 admin'-- -(反斜杠仅用于 SQL 解析,实际入库的是 admin'-- -)。

数据库中存储的结果

id username password
15 admin'-- - 123456
3. 触发二次注入(修改密码)

攻击者访问 demo7.php,传入恶意用户名和密码:

复制代码
http://127.0.0.1/demo7.php?username=admin'-- -&password=111111

SQL 拼接:

复制代码
update userinfo set password='111111' where username='admin'-- -'

由于 -- 注释了后面的内容,实际执行:

复制代码
update userinfo set password='111111' where username='admin'

成功将 admin 用户的密码修改为 111111,实现了越权。

4. 验证结果

数据库中的 admin 密码被改为 111111

三、漏洞原理

  • 第一次插入 :虽然使用了 addslashes 转义,但数据库中存储的用户名是 admin' --(反斜杠仅用于 SQL 语句解析,不进入存储)。

  • 第二次更新demo7.php 直接拼接用户名,没有进行任何过滤或参数化,导致存储的 payload 中的单引号和注释符生效,改变了 SQL 语义,将原本修改"admin' --"的语句变成了修改"admin"。

四、总结

二次注入的核心是:数据在第一次被安全存储后,第二次使用时因缺乏过滤而触发注入 。本实验中,demo6.php 安全存储了恶意用户名,demo7.php 的不安全拼接导致了越权修改。

十、VAuditDemo靶场注入再演示

一、靶场简介

  • 名称:VAuditDemo(一个用于演示常见 Web 漏洞的 PHP 程序)

  • 漏洞点:用户注册 + 修改密码功能

  • 漏洞类型:二次注入(Second-Order SQL Injection)

  • 核心原因:注册时对特殊字符进行了转义安全存储,但修改密码时直接拼接数据库取出的用户名,导致 SQL 语义改变。

二、注入过程

第一步:

先注册lisi'#账号,密码123456

第二步:

注册完成点击退出,再注册

第三步:

注册个lisi账号,密码123456

第四步:

可以看到正常登录lisi账号

第五步:

可以看到正常登录lisi'#账号

第六步:

更改lisi'#账号的密码,更改为111111

第七步:

发现lisi'#用修改完的密码登录不进去

第八步:

但是lisi账号能用密码111111登录进去

三、漏洞原理深入分析

阶段 操作 SQL 上下文 是否安全 原因
注册 插入 lisi'# INSERT 语句,使用了 addslashes 转义 安全 单引号被转义,# 作为普通字符串
登录 查询 lisi'# 可能使用参数化查询或转义 安全 能正确匹配记录,无注入
修改密码 拼接 lisi'# 到 UPDATE 直接拼接,未过滤 不安全 # 成为注释符,导致修改了 lisi 的密码

根本原因 :开发人员错误地认为"从数据库取出的数据是可信的",因此没有对 $username 进行任何安全处理。然而,数据库中存储的原始字符串可能包含 SQL 元字符(如 '#--),当这些字符被拼接到新的 SQL 语句中时,就会改变语义。

四、与普通注入的区别

  • 普通注入:攻击数据直接从用户输入进入 SQL 语句,在同一请求中生效。

  • 二次注入:攻击数据先被存储(通常经过转义),然后在另一个请求中被取出并拼接到 SQL 中,由于第二次缺乏防护而触发。

五、防御措施

  1. 参数化查询(Prepared Statements) 无论是 INSERT、SELECT 还是 UPDATE,一律使用预编译语句,彻底隔离代码和数据。

    复制代码
    $stmt = $conn->prepare("UPDATE users SET password=? WHERE username=?");
    $stmt->bind_param("ss", $newpass, $username);
  2. 输入过滤与输出转义结合 即使从数据库取出数据,也要根据其用途(如拼接到 SQL)进行转义或类型转换。

  3. 最小权限原则 数据库账户仅授予必要的权限(例如,Web 应用账户不应有 DROPTRUNCATE 等权限)。

  4. 禁用危险字符 对用户名等字段限制可接受的字符集(如字母数字 + 下划线),禁止 '#- 等特殊字符。

六、总结

VAuditDemo 的二次注入漏洞展示了典型的数据流攻击:攻击者通过注册功能"预埋"一个包含注释符的恶意用户名,然后在修改密码功能中利用该用户名拼接 SQL,实现越权修改其他用户的密码。这提醒开发者:永远不要信任任何来源的数据,包括自己数据库中的内容;所有拼接 SQL 的操作都必须经过参数化查询或严格转义

相关推荐
AI帮小忙2 小时前
主机安全排查
linux·服务器·安全
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题 第102题】【并发篇】第2题:volatile 能否保证线程安全?
java·安全·面试
laoli_coding3 小时前
数据机密性保护算法汇总(国际算法)
安全·网络安全·密码学
Lyyaoo.3 小时前
【数据结构】HashMap底层存储+扩容机制+线程安全【待更新】
数据结构·安全·哈希算法
国冶机电安装3 小时前
分包工程施工方案
安全
terry6004 小时前
从流畅交互到高可用:企讯通Qcaptcha滑动拼图的毫秒级响应与容灾设计
web安全·json·asp.net·信息与通信·数据库架构
X7x54 小时前
零信任架构:重塑数字时代安全边界的战略转型
网络安全·网络攻击模型·安全威胁分析·安全架构·零信任架构
无忧智库4 小时前
某矿山井下人员精准定位与AI行为安全识别管控系统建设方案(WORD)
人工智能·安全
xiaofj1004 小时前
reglock工作机制
大数据·安全