SQL注入常见攻击点与防御详解
SQL注入是一种非常常见且危险的Web安全漏洞。攻击者通过将恶意的SQL代码插入到应用程序的输入参数中,欺骗后端数据库执行这些非预期的命令,从而可能窃取、篡改、删除数据或获得更高的系统权限。
以下是详细、准确的SQL注入点分类、说明及举例:
一、 按注入点位置分类
1. GET 参数注入
描述: 通过URL中的查询字符串(即
?
后面的参数)进行注入。这些参数通常用于GET请求,如过滤、搜索、分页等。原理: 参数值被直接拼接到SQL查询中,未经过滤或转义。
举例:
正常URL:
https://example.com/products.php?id=1
对应SQL:
SELECT * FROM products WHERE id = 1
攻击URL:
https://example.com/products.php?id=-1' UNION SELECT username, password FROM users -- -
最终SQL:
SELECT * FROM products WHERE id = -1' UNION SELECT username, password FROM users -- -
解释:
id=-1
确保原查询不返回结果,UNION SELECT
用于窃取用户表数据,-- -
用于注释掉原查询的剩余部分。
2. POST 参数注入
描述: 通过POST请求的正文(Body)进行注入。常见于登录表单、搜索框、注册等任何用户提交数据的场景。
原理: 与GET注入类似,只是数据通过请求体传输,不在URL中显示。
举例(登录表单):
正常输入: 用户名
admin
,密码secret
对应SQL:
SELECT * FROM users WHERE username = 'admin' AND password = 'secret'
恶意输入: 用户名
admin' --
,密码任意值
最终SQL:
SELECT * FROM users WHERE username = 'admin' -- ' AND password = '任意值'
解释:
--
注释掉了密码验证部分,使攻击者能以admin身份登录,无需知道密码。
3. Cookie 注入
描述: 应用程序将Cookie值(如
user_id
,session_id
)直接用于数据库查询而未经验证。原理: 攻击者通过修改自己的Cookie值为恶意Payload来实现注入。
举例:
正常Cookie:
user_id=5
对应SQL:
SELECT * FROM users WHERE user_id = 5
恶意Cookie:
user_id=5 UNION SELECT @@version
最终SQL:
SELECT * FROM users WHERE user_id = 5 UNION SELECT @@version
解释: 攻击者通过修改Cookie,联合查询了数据库的版本信息。
4. HTTP 头部注入
描述: 应用程序将HTTP请求头部的值(如
User-Agent
,X-Forwarded-For
,Referer
)记录到数据库或用于查询。原理: 攻击者在这些头部字段中构造恶意Payload。
举例(记录User-Agent到数据库):
正常User-Agent:
Mozilla/5.0 ...
对应SQL:
INSERT INTO access_log (user_agent) VALUES ('Mozilla/5.0 ...')
恶意User-Agent:
Mozilla/5.0 ...', (SELECT @@version)) --
最终SQL:
INSERT INTO access_log (user_agent) VALUES ('Mozilla/5.0 ...', (SELECT @@version)) -- ')
解释: 攻击者将数据库版本信息注入到了
access_log
表中。
二、 按注入技术(漏洞成因)分类
1. 基于联合查询(UNION-based)
描述: 利用
UNION
或UNION ALL
操作符将恶意查询结果附加到原始查询之后,从而在应用程序响应中直接显示数据。前提: 需要知道原始查询的列数(通过
ORDER BY
或UNION SELECT NULL
试探)以及各列的数据类型。举例: 如上文GET参数注入的例子。
2. 基于错误(Error-based)
描述: 故意构造Payload使数据库报错,并从错误信息中提取敏感数据(如数据库结构、数据内容)。即使页面不回显查询结果,但会回显错误信息时,此方法有效。
举例(MySQL):
Payload:
id=1' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT @@version),0x3a,FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a) --
解释: 此Payload利用MySQL的
RAND()
和GROUP BY
子句制造一个重复键错误,并将@@version
信息包含在错误信息中返回。
3. 基于布尔盲注(Boolean Blind)
描述: 当页面没有数据回显和错误回显,但会根据SQL查询结果为真或为假返回不同的页面状态(如“存在”/“不存在”、“正常”/“404”)时使用。
原理: 通过构造逻辑判断(如
AND 1=1
,AND 1=2
)观察页面差异,逐位(bit)猜测数据。举例(猜测数据库名长度):
id=1' AND (SELECT LENGTH(database())) = 8 --
如果页面返回正常,说明当前数据库名长度为8;如果返回异常(或无结果),则不是8。然后继续猜测每个字符是什么。
4. 基于时间盲注(Time Blind)
描述: 当页面没有任何回显(包括错误和布尔差异)时使用。通过构造Payload,根据查询条件的真假让数据库执行延时操作,通过观察页面响应时间来判断条件真假。
原理: 利用数据库的延时函数(如MySQL的
SLEEP()
,BENCHMARK()
,PostgreSQL的pg_sleep()
)。举例(MySQL,猜测数据库名首字母):
id=1' AND IF(SUBSTRING(database(),1,1)='a', SLEEP(5), 0) --
如果页面响应延迟了5秒,说明数据库名的第一个字母是'a';否则不是。
5. 堆叠查询(Stacked Queries)
描述: 利用分号
;
在一次数据库调用中执行多条SQL语句。这允许执行任意SQL命令,而不仅仅是查询。前提: 后端数据库驱动需要支持多语句执行(如PHP中的
mysqli_multi_query()
,而mysql_query()
不支持)。举例:
id=1'; DROP TABLE users; --
解释: 此Payload会先执行原始查询,然后立即执行
DROP TABLE users
语句删除用户表。
6. 二次注入(Second-Order)
描述: 一种更隐蔽的注入。恶意Payload首先被存储到数据库中(例如,在注册用户名时注入),当时并未触发。之后,当应用程序从数据库取出该数据并用于另一个SQL查询时,注入才被触发。
原理: 输入在第一次插入时可能被转义,但存储后被认为是“干净”的数据,第二次使用时未经过滤。
举例:
注册阶段: 攻击者注册一个用户,用户名为
admin' --
插入SQL:
INSERT INTO users (username) VALUES ('admin'' -- ')
(经过转义,安全)
后续操作: 管理员在后台查看用户列表,程序执行:
SELECT * FROM users WHERE username = 'admin' -- '
解释: 从数据库取出的用户名
admin' --
被直接拼接进新的查询,注释掉了后续条件,可能导致列出所有用户或其他敏感操作。
三、 其他特定场景的注入点
1. 搜索功能(LIKE子句)
描述: 搜索框通常使用
LIKE '%keyword%'
,这里的引号和百分号是常见的注入点。举例:
正常输入:
apple
SQL:
SELECT * FROM products WHERE name LIKE '%apple%'
恶意输入:
%' UNION SELECT 1,2,3 --
最终SQL:
SELECT * FROM products WHERE name LIKE '%%' UNION SELECT 1,2,3 -- %'
2. ORDER BY 子句注入
描述:
ORDER BY
后面通常接列名或列索引,不能直接使用UNION。但可以利用IF
语句或 CASE WHEN 进行盲注。举例(盲注):
https://example.com/products?sort=IF(1=1, name, price)
https://example.com/products?sort=IF((SELECT SUBSTRING(@@version,1,1))='5', name, price)
解释: 通过
IF
条件控制排序依据的列,观察排序结果的不同来判断条件真假。
3. 宽字节注入
描述: 主要针对使用GBK、BIG5等宽字符集的PHP应用程序。由于转义函数(如
addslashes
或magic_quotes_gpc
)和字符集编码配合不当,导致转义符\
(%5C
)被“吃掉”,从而使得单引号逃逸。原理: 攻击者输入一个特殊字符(如
%bf%27
),%bf
与转义符%5c
组合成一个合法的宽字符(如縗
),从而使后面的单引号%27
成功逃逸。举例:
输入:
id=%bf%27
转义后:
%bf%5c%27
(PHP的addslashes
将'
转义为\'
即%5c%27
)数据库以GBK解码:
%bf%5c
被解码为汉字“縗”,剩下的%27
(单引号)则留在原地,成功闭合了查询。
总结与防御
防御原则(永远不要信任用户输入):
使用参数化查询(预编译语句): 这是最有效、根本的防御手段。将SQL代码与数据完全分离,用户输入永远被视为数据,而非代码。几乎所有编程语言和框架(如Java的PreparedStatement, PHP的PDO, Python的sqlite3)都支持。
使用ORM框架: 如Hibernate, Entity Framework,它们通常内置了参数化查询。
最小权限原则: 数据库账户不应拥有过高权限(如DROP, FILE权限)。
输入验证和过滤: 对输入进行严格的白名单验证(如只允许字母数字)。但绝不能仅依赖此方法。
转义: 如果万不得已必须拼接SQL,必须使用数据库特定的转义函数(如
mysql_real_escape_string
),但要注意字符集问题(宽字节注入)。错误处理: 向用户展示友好的错误页面,而不是详细的数据库错误信息。
通过了解这些常见的注入点和攻击技术,开发者和安全人员可以更有针对性地进行代码审计和安全防护。