Sublarge

// 红队渗透 · 2025-03-12

SQL注入基础:从登录框万能密码到数据库指纹识别

本文以登录场景为切入点,深入剖析SQL注入的底层原理与实战技巧。通过万能密码、反斜杠逃逸、联合查询伪造结果及长度受限下的数据库函数压缩等案例,揭示代码与数据边界混淆的本质。重点讲解运算符优先级、过滤绕过思路、不同数据库语法差异及指纹识别方法,系统梳理从基础注入到高阶绕过的完整链条,夯实Web安全核心能力。

SQL注入属于经典老牌漏洞,很多现代框架与WAF都有原生防护,但它仍是Web安全最核心的底层基石。理解注入的语法、运算符优先级、数据库语法差异、过滤逃逸思路,是读懂高阶注入、二次注入、堆叠注入、盲注、ORM注入绕过的前置基础。本文以登录场景为核心载体,从原理、代码、Payload、陷阱逐个拆解。

在Web应用开发流程中,程序接收前端传入的用户可控数据后,如果直接将输入字符串拼接进SQL语句,交由数据库引擎执行,就会产生SQL注入(SQL Injection)漏洞。 漏洞本质一句话概括:代码与数据边界混淆。开发者本意是把用户输入当作查询数据,但攻击者构造特殊字符,让输入被数据库解析成SQL指令片段,篡改原有查询逻辑。 它不是数据库本身的缺陷,而是业务代码没有隔离“代码逻辑”和“用户输入”导致的逻辑缺陷。登录框万能密码是入门最典型、最容易直观理解注入本质的场景,也是很多过滤绕过、注释、运算符优先级考点的起点。


一、登录框万能密码深入利用

登录模块的标准逻辑:接收用户名、密码,拼接WHERE条件同时匹配两个字段,数据库返回记录即判定登录成功。不同代码实现、不同过滤策略、不同校验顺序,会衍生完全不一样的注入思路,这也是很多初学者容易忽略的地方。

1. 用户名、密码参数均可注入:经典OR万能密码

最原始、最广为人知的场景,后端SQL模板:

SELECT * FROM users WHERE username='$user' AND password='$pass'
sql1 行

很多人直接记Payload ' OR '1'='1,但很容易踩坑:SQL运算符优先级 AND > OR,不能简单认为拼接后条件直接恒真。

传入Payload:用户名 ' OR '1'='1,密码任意内容,拼接得到:

SELECT * FROM users WHERE username='' OR '1'='1' AND password='任意内容'
sql1 行

按优先级等价转换:

SELECT * FROM users WHERE username='' OR ('1'='1' AND password='任意内容')
sql1 行

这里存在一个经典误区:如果只在用户名填 ' OR 1=1,很多新手会疑惑为什么有时候不成功,根源就是AND优先级更高。 稳妥写法,保证整体条件恒真: 用户名:' OR '1'='1' -- 或者:' OR 1=1#

示例漏洞PHP代码(无任何过滤,直接拼接)

攻击输入:

  • 用户名:' OR '1'='1'#
  • 密码:随意字符串

Payload逻辑:单引号闭合原有username字段的引号,OR '1'='1'制造永真条件,#注释掉后面剩余的AND password='$pass',彻底规避AND优先级带来的失效问题,是更稳定的写法。

2. 简单单引号替换过滤:反斜杠逃逸注入

开发意识到单引号危险,做了极简防护:直接使用str_replace把'替换成\',试图转义闭合符。 这种防护属于典型的无效防护:转义逻辑本身可控,攻击者可以输入\改变转义行为。

漏洞代码:

攻击输入:

  • 用户名:\
  • 密码:OR 1=1--

拼接后的SQL:

SELECT * FROM users WHERE username='\' AND password=' OR 1=1--'
sql1 行

解析过程拆解:

  1. 输入的\被代码替换为\\;
  2. MySQL解析字符串时:\\代表字面量反斜杠,紧随其后的'不再是被转义的数据,而是语句闭合引号;
  3. 引号成功逃逸,OR 1=1成为查询条件;
  4. --注释末尾多余单引号,保证语法合法。

拓展知识点:这类逃逸依赖数据库的转义规则,MySQL、MSSQL、Oracle转义行为各不相同;同时要区分addslashes和手写str_replace的差异,addslashes还会转义\0、\r\n,逃逸难度更高。

3. 注入点仅在用户名:先查密码、业务层比对 + UNION联合查询伪造结果

这是一个非常经典的业务逻辑差异场景,很多新手会混淆两种登录实现:

  • 方案A:一条SQL同时查username和password,数据库返回即放行(上面的例子)
  • 方案B:SQL只根据用户名查询数据库里存储的密码哈希,密码比对放在PHP代码层,拿到数据库哈希之后,再和用户输入密码的MD5结果对比。

方案B的特征:WHERE条件只有username,password不会拼入SQL,密码参数不再存在注入点。 漏洞示例代码:

此时目标:用UNION查询,人为构造一条假的数据库记录,返回我们可控的密码哈希。 Payload:

  • 用户名:-1' UNION SELECT '0cc175b9c0f1b6a831c399e269772661' --
  • 密码:a

拼接SQL:

SELECT password FROM users WHERE username='-1' UNION SELECT '0cc175b9c0f1b6a831c399e269772661' -- '
sql1 行

逻辑说明:

  1. -1是不存在的用户名,前面SELECT返回空;
  2. UNION后自定义一条记录,返回预先准备好的MD5值(a的md5);
  3. PHP取出这个伪造哈希,和输入密码a的md5比对相等,登录成功。

重点前提:UNION前后列数、数据类型必须匹配,这也是联合注入最核心的前置探测步骤,和盲注、脱库的前置流程一致。

4. 输入长度受限:调用数据库内置函数压缩Payload

部分前端/后端校验用户名最大长度,写死长字符串md5值的Payload会被截断,语法直接报错。 优化思路:把md5计算交给数据库本身执行,减少Payload字符总量。 Payload:

  • 用户名:-1' UNION SELECT MD5('a')--
  • 密码:a

拼接SQL:

SELECT password FROM users WHERE username='-1' UNION SELECT MD5('a')-- '
sql1 行

优势:不需要硬编码一长串哈希字符串,Payload更短,适配长度限制场景;同时规避哈希字符串中特殊字符带来的额外过滤问题。


二、快速识别后端数据库类型(注入指纹)

不同数据库SQL语法、注释、特殊字符、内置函数差异巨大,拿到注入点后第一步往往是指纹识别,决定后续Payload的写法。下面是低干扰、适合手工测试的判断手段。

1. 特殊空白/控制字符测试

原理:不同数据库对ASCII不可见控制字符的容忍度不同。 测试请求示例:

?id=1'%0B'
text1 行

拼接SQL:

SELECT * FROM table WHERE id='1'%0B''
sql1 行
  • MySQL:接受%0A换行、%0B垂直制表符、%09制表符,语句不会直接语法报错;
  • Oracle:对%00、换行敏感,不支持MySQL风格多行注释;
  • SQL Server(MSSQL):对部分控制字符解析行为和MySQL差异明显。

判定逻辑:如果插入控制字符后页面不返回数据库报错,大概率偏向MySQL。

2. MySQL专属内联注释 /*! ... */ 指纹

/*! 内容 */是MySQL独有语法:注释内部的语句仅MySQL会执行,Oracle、MSSQL、PostgreSQL都会直接当成纯注释忽略。 典型测试Payload:

?id=1 /*!and 1=2*/
text1 行
  • 如果页面结果发生变化(查询失效):说明是MySQL;
  • 如果页面无变化、和普通注释行为一致:排除MySQL。 这个语法在旧版本WAF绕过中也大量使用。

3. 注释符体系区分

数据库 单行注释 多行注释 备注
MySQL #、-- (–后面必须带空格/空白) /* */ 支持/*!*/条件注释
MSSQL -- /* */ 不支持#单行注释
Oracle -- /* */ 不支持#

坑点:很多初学者写--1=1注入失败,MySQL要求--之后必须有空白字符,也就是-- ,%20或者%09。


三、补充:容易混淆的边界知识点

  1. 万能密码不是通用:它只适用于字符串型查询、引号可控的场景;数字型注入不需要单引号闭合,思路完全不同;
  2. 过滤是分层的:前端JS过滤可以直接绕过,只有后端服务层过滤才有意义;
  3. ORM框架不是天然安全:如果ORM支持字符串拼接、raw SQL,依旧会产生注入(ORM注入);
  4. 本文的登录注入只是起点:掌握这套边界思维之后,可以延伸到布尔盲注、时间盲注、堆叠注入、二次注入、宽字节注入等分支场景。

四、防御方案

SQL注入的根治方案,核心是分离SQL逻辑和用户数据,不要依靠黑名单替换、正则过滤这类“黑名单防护”(黑名单永远存在逃逸方式)。

  1. 优先使用预编译参数化查询(PreparedStatement),PHP PDO、Java JDBC、Python pymysql均支持;参数化查询会把输入始终当作数据,不会参与SQL语法解析;
  2. 最小权限数据库账号:Web业务账号禁止FILE、高权限读写,就算注入成功,也限制危害范围;
  3. 输入校验采用白名单:对于固定格式参数(id、手机号),用正则限制格式;
  4. 应用层二次权限校验:不要把鉴权完全交给数据库查询结果;
  5. 错误信息屏蔽:关闭数据库原生报错回显,防止攻击者获取表名、列名等指纹信息。

五、总结

SQL注入的内核,是数据与代码边界的失控。万能密码、逃逸单引号、UNION伪造返回结果,本质都是在修改SQL的执行分支。 虽然现在成熟框架、WAF、参数化查询让原始拼接注入越来越少见,但它是理解数据库解析顺序、字符转义、注释语法、过滤逃逸最优质的训练样本。很多高级注入漏洞,只是在这个基础模型上增加编码、字符集、框架、中间件的复杂包装。 安全防护不能依赖“过滤危险字符”,从根源上使用参数化查询,隔离输入和SQL指令,才是稳定有效的防护手段。

原文 https://blog.csdn.net/2301_79518550/article/details/146105392