SQL 注入是一种代码注入技术,攻击者通过在输入字段中插入恶意 SQL 代码,改变数据库查询逻辑,从而绕过身份验证、访问敏感数据或对数据库进行恶意操作。这种攻击方式利用了应用程序未对用户输入进行正确定义和验证的漏洞。
许多 Web 应用程序直接将用户输入嵌入到 SQL 查询中,而没有对输入进行严格的验证和清理。例如,登录页面会根据用户输入的用户名和密码构建 SQL 查询:
SELECT * FROM users WHERE username = '<用户名>' AND password = '<密码>';
这里的 <用户名>
和 <密码>
是用户输入的值,如果应用程序没有对这些输入进行过滤和验证,攻击者就可以通过构造特殊的输入来操纵 SQL 查询。
攻击者输入的内容会被嵌入到 SQL 查询中,通过利用 SQL 语言的语法特性(如注释符号、逻辑运算符等),改变查询的逻辑。例如,攻击者可以输入:
admin' --
anything
嵌入到查询中后,查询语句变为:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything';
其中,--
是 SQL 的注释符号,后面的内容会被忽略,这样查询就变成了只验证用户名,而密码条件被注释掉了。
攻击者通过构造恶意输入,改变了原始查询的逻辑,使得查询能够绕过正常的身份验证或访问控制,从而获取敏感数据或执行未授权的操作。
这是最基本的 SQL 注入方式,攻击者直接将恶意 SQL 代码嵌入用户输入中,影响查询逻辑。例如:
admin' --
anything
嵌入后的查询语句:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything';
由于 --
注释掉了密码条件,查询只需要用户名为 admin
即可登录,密码验证被绕过。
' OR '1'='1
anything
嵌入后的查询语句:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'anything';
由于 '1'='1'
总是为真,查询会返回用户表中的所有记录,攻击者无需知道正确密码即可通过身份验证。
数字型注入主要针对数字类型的输入字段,例如用户 ID。例如,攻击者可以在 URL 中输入:
http://example.com/userinfo.asp?id=1 UNION SELECT 1,2,3--
嵌入后的查询语句:
SELECT * FROM userinfo WHERE id=1 UNION SELECT 1,2,3--
攻击者通过 UNION
操作将多个查询结果合并,从而获取额外的信息。
字符型注入主要针对字符类型的输入字段,例如用户名或密码。例如:
' OR '1'='1
anything
嵌入后的查询语句:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'anything';
攻击者通过输入恶意 SQL 代码,使数据库产生错误,并根据错误信息获取数据库的结构和数据。例如:
SELECT * FROM users WHERE username = 'admin' AND password = 'anything' AND ROWNUM = 1;
攻击者使用 UNION
操作将多个查询结果合并,从而获取额外的信息。例如:
http://example.com/userinfo.asp?id=1 UNION SELECT 1,2,3--
嵌入后的查询语句:
SELECT * FROM userinfo WHERE id=1 UNION SELECT 1,2,3--
在盲注型注入中,攻击者无法直接看到查询结果,而是通过观察数据库的响应时间或逻辑判断来推断数据库的结构和数据。例如:
http://example.com/userinfo.asp?id=1 AND 1=1
http://example.com/userinfo.asp?id=1 AND 1=2
通过比较响应结果,攻击者可以判断条件是否成立,从而获取信息。
预处理语句将 SQL 代码和用户输入分开,防止攻击者通过输入改变查询逻辑。例如,在 Java 中可以使用 PreparedStatement:
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();
对用户输入进行严格的验证,确保输入符合预期的格式。可以使用白名单验证,例如:
import re
def is_valid_username(username):
return re.match(r"^[a-zA-Z0-9_]+$", username) is not None
同时,对输入进行清理,例如移除特殊字符:
def clean_input(input_str):
return input_str.replace("'", "").replace("--", "")
为不同的应用程序使用不同的数据库账户,并限制每个账户的权限。例如,只授予读取权限,而不授予写入或删除权限:
GRANT SELECT ON users TO 'app_user'@'localhost';
定期更新数据库管理系统,应用安全补丁,防止已知的漏洞被利用。
确保应用程序使用的数据库账户只具有完成其任务所需的最小权限。例如,如果应用程序只需要读取数据,那么数据库账户不应具有写入或删除权限。
Web 应用防火墙可以检测和阻止常见的 SQL 注入攻击。例如,ModSecurity 是一个开源的 WAF,可以配置规则来阻止 SQL 注入:
SecRule ARGS "@detect_sql_injection" "id:1,deny,severity:2,msg:'SQL Injection Attack'"