前面了解了 SQL 注入产生的原因之后,我一直有个疑问:
为什么有的教程说是数字型注入?有的又说是字符型注入?
难道 SQL 注入还有不同种类?
今天专门学习了一下,发现其实并不复杂,本质上就是程序员拼接 SQL 语句的方式不同而已。
什么是 SQL 注入
先回顾一下 SQL 注入为什么会产生。
正常情况下,用户输入的数据应该只是数据。
例如:
SELECT * FROM users WHERE id = 1;
这里的 1 只是一个普通数字。
但是如果程序没有对用户输入进行过滤,攻击者就可以把原本应该是数据的内容变成 SQL 代码。
例如输入: 1 OR 1=1
最终拼接出来的 SQL:SELECT * FROM users WHERE id = 1 OR 1=1;
由于 1=1 永远成立,数据库就会返回大量数据。
这就是 SQL 注入最核心的思想:用户输入的数据被当成了 SQL 代码执行。
一、数字型注入
数字型注入通常出现在参数本身就是数字的场景。
例如:
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id=$id";
正常访问:?id=1
后台执行:SELECT * FROM users WHERE id=1;
如果攻击者输入:?id=1 OR 1=1
后台执行:SELECT * FROM users WHERE id=1 OR 1=1;
由于条件恒成立,就可能查询出所有用户数据。
这里最大的特点就是:SQL 语句中没有单引号,参数直接参与计算。
所以称为数字型注入。
二、字符型注入
字符型注入出现得更加普遍。
例如登录功能:
$username = $_POST['username'];
$sql = "SELECT * FROM users WHERE username='$username'";
正常输入:admin
生成 SQL:SELECT * FROM users WHERE username='admin';
看起来没什么问题。
但是如果输入:admin' OR '1'='1
拼接后:SELECT * FROM users WHERE username='admin' OR '1'='1';
因为:'1'='1' 永远成立。
这样就可能绕过正常验证逻辑。
这里最大的特点是:参数被单引号包裹,因此需要先处理单引号。
所以被称为字符型注入。
三、单引号为什么这么重要
很多新手刚接触 SQL 注入的时候都会发现一个现象。
教程里总是在输入内容后面加一个单引号。
例如:
1'
这是因为攻击者想测试当前参数是否被单引号包裹。
正常 SQL:
SELECT * FROM users WHERE username='admin';
如果输入: admin'
会变成:SELECT * FROM users WHERE username='admin'';
SQL 语法被破坏,数据库通常会报错。
如果此时出现 SQL 错误信息,就说明当前位置很可能存在字符型注入点。
所以很多渗透测试人员第一步都会尝试添加单引号进行测试。
四、注释符为什么能利用
学习过程中我还有一个疑问。
为什么很多 Payload 后面都喜欢跟着:
--
或者:
#
例如:
1' OR '1'='1' --
原因很简单。
注释符后面的内容会被数据库忽略。
原始 SQL:SELECT * FROM users WHERE username='admin' AND password='123456';
攻击者输入:admin' --
最终 SQL:SELECT * FROM users WHERE username='admin' -- ' AND password='123456';
后面的内容被注释掉。
数据库实际上执行的是:SELECT * FROM users WHERE username='admin';
从而影响原本的验证逻辑。
五、数字型和字符型到底有什么区别
其实区别非常简单。
数字型
id=1
对应:SELECT * FROM users WHERE id=1;
字符型
username=admin
对应: SELECT * FROM users WHERE username='admin';
数字型参数没有引号。
字符型参数有引号。
攻击时需要考虑的闭合方式不同。
除此之外,两者本质都是让用户输入影响 SQL 语句逻辑。
六、总结
今天学习 SQL 注入分类之后,最大的收获并不是记住了几个 Payload。
而是终于明白:SQL 注入的本质从来不是那些复杂的攻击语句。
真正的问题在于:程序把用户输入直接拼接进了 SQL 语句。
数字型注入和字符型注入只是表现形式不同。
理解了这个原理之后,再去学习后面的联合查询、报错注入、盲注等内容会轻松很多。