我总结为以下三个方面:
数据与代码的混淆。
闭合引号(单引号 '、双引号 "、括号 ))的艺术。
注释符的差异(--+, #, /*, ;%00 等在不同 DB 中的用法)。
这是所有注入类漏洞(SQLi, XSS, Command Injection)的根本原因。
SELECT * FROM users WHERE name = ...。这是用来执行的命令。zhangsan。这只是一个文本字符串,不应该具备任何执行能力。假设后端代码是:
1。
SELECT * FROM users WHERE id = 1。1 是数据。1; DROP TABLE users。
SELECT * FROM users WHERE id = 1; DROP TABLE users。; DROP TABLE users 本来应该被当作“查找 id 为这一长串字符的用户”,但数据库把它解析成了“结束上一条语句,并执行删除表的操作”。数据摇身一变,成为了代码。要想让你的“数据”变成“代码”,你必须先逃逸出程序员为你设定的“数据牢笼”。这个“牢笼”通常就是引号。
为什么要闭合? 在 SQL 语句中,字符串类型的数据必须用引号包裹。 后端代码通常长这样:
注意 $user_input 两边有一对单引号。
场景模拟:
admin
... WHERE username = 'admin';admin 被单引号仅仅包裹,只是一个字符串。admin OR 1=1
... WHERE username = 'admin OR 1=1';"admin OR 1=1" 的人。你的攻击语句被当作了名字的一部分。admin' OR 1=1 --
... WHERE username = 'admin' OR 1=1 --';' 与程序员写的左边那个 ' 配对成功了!OR 1=1 就“光着身子”跑到了引号外面。复杂的闭合: 程序员可能会用各种方式包裹输入,你需要猜(Fuzzing)并闭合它:
id = '$id' -> 注入 'id = "$id" -> 注入 "id = ($id) -> 注入 )id = ('$id') -> 注入 ')id = (("$id")) -> 注入 "))技巧:当输入 ' 报错,输入 ') 也报错,但输入 ")) 正常回显时,你就探测出了后端的 SQL 结构。
当闭合了前面的引号,注入了自己的 Payload 之后,你的 Payload 后面通常还有程序员写的残留代码(比如原本用来闭合的右引号 ',或者 LIMIT 0,1 等)。
这些残留代码会破坏 SQL 语法,导致报错。注释符的作用就是把屁股擦干净,让数据库忽略掉 Payload 后面所有的东西。
不同的数据库,注释符是不同的,这在实战中非常关键:
# (井号)
SELECT * FROM users WHERE id=1 # and...# 是锚点符号(Anchor),浏览器不会把 # 发送给服务器。%23。-- (减号减号空格)
-- 后面必须跟一个空格(或者控制字符)才算注释。--+ 的由来。在 URL 中,+ 号会被解码为空格。所以 --+ 到达数据库时就变成了 -- (减号减号空格),完美符合语法。/\* ... \*/ (内联注释)
-- (双减号)
/\* ... \*/
-- (双减号)
;%00 (空字节截断)%00 (Null Byte) 代表字符串结束。id=1 and 1=1 ;%00。假设我们想绕过登录框(万能密码)。 后端代码:SELECT * FROM users WHERE user='$u' AND pass='$p';
攻击 Payload:admin' #
数据库视角解析:
SELECT * FROM users WHERE user='admin' #' AND pass='...';admin' 中的 ' 闭合了 user=' 的左引号。# 把后面所有的内容 AND pass='...'; 全部注释掉了(变灰了)。SELECT * FROM users WHERE user='admin' (密码检查逻辑被删除了,直接以 admin 身份登录。)这就是理解本质的力量。知道了它为什么生效,哪怕以后遇到了 WAF 把 # 过滤了,也能立刻反应过来:“哦,我可以用 --+ 或者 /* 来代替注释,或者我不注释了,我手动闭合后面的引号 ' OR '1'='1。”