SQL注入原理与防御
SQL注入原理与防御
复习定位
SQL注入是Web安全中最经典也最致命的漏洞之一。本质原因是:用户输入的字符串被拼接到SQL语句中——用户输入的内容变成了SQL代码的一部分。因此攻击者可以闭合输入处的引号——插入自己的SQL命令——查询任意数据、修改数据库、甚至执行系统命令。唯一的防御——参数化查询:永远不让用户输入与SQL语句结构混合。
SQL注入的成因
考虑一个最简单的登录验证代码:
username = request.POST['username']
password = request.POST['password']
sql = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'"
cursor.execute(sql)攻击者在username字段输入admin' --——拼接后SQL变为:
SELECT * FROM users WHERE username='admin' --' AND password='xxx'--是SQL注释符——原密码比较部分被注释——攻击者无需密码就能以admin身份登录。
更强大的攻击——输入' OR '1'='1:
SELECT * FROM users WHERE username='' OR '1'='1' AND password='xxx''1'='1'永远为真——返回所有用户行。
注入类型
联合查询注入——利用UNION SELECT从其他表中提取数据。攻击者先查询当前表的列数——ORDER BY N试探(ORDER BY 1,2,3如果越界报错)——然后用UNION SELECT 1,2,3,4注入可控回显位置——继续指定UNION SELECT database(),user(),version()获取数据库版本、当前用户——最后从information_schema.tables枚举表名和列名拖走全库。
盲注——服务器不显示数据库的错误信息和结果——攻击者通过布尔条件或时间延迟逐个推断数据。布尔盲注——' AND ascii(substr(database(),1,1))>100 -- ——如果返回正常说明条件为真——逐字符切割得到数据库名。时间盲注——延迟函数如MySQL的sleep(5)——如果响应延迟——条件为真。
报错注入——通过构造特殊语句触发服务器的错误——将数据库信息(如表名)显示在错误消息中。MySQL——' AND extractvalue(1,concat(0x7e,database())) -- 。
防御方法
参数化查询是最彻底的防御——SQL结构骨架(含占位符?)先发送给数据库编译——再将用户输入作为纯数据绑定到占位符——数据库不会再把它解释为代码。
不安全的动态拼接(恶):
cursor.execute("SELECT * FROM users WHERE name='" + name + "'") # 危险安全的参数化查询:
cursor.execute("SELECT * FROM users WHERE name=%s", (name,)) # 安全预编译后——name被插入——即使包含' OR '1'='1——数据库只当它是普通的比较值——不会解析为SQL关键字。
复习检查
为什么SQL注入发生?用户输入的
' OR '1'='1结束字符串后改变SQL的语法结构——描述执行时的流。盲注——攻击者看不到数据库裸内容——他怎么逐步确定数据库的名字并最终提取到用户密码?
为什么存储过程(内部拼接SQL字符串)某些情况下也是不安全的——除非使用参数化调用或已嵌入的预执行SQL。
ORM框架(如Hibernate, Django ORM)能完全防御SQL注入吗——如果使用原生SQL查询方法(Raw SQL)是否还存在风险?
WAF(Web应用防火墙)通过正则表达式拦截敏感关键字(
union, select, or, 1=1)来防御注入——为什么这不是彻底的防御——攻击者如何通过编码/大小写/注释绕过WAF的检测?