安全问题的本质就是信任问题
例如对一个网站的开发来说
信任普通用户的输入——前台漏洞
信息管理员用户的输入——后台漏洞
信任离线升级/在线升级/自动化升级/升级包——供应链攻击
不信任任何输入——信任代码逻辑——逻辑漏洞
对应的安全方案有很多,但都可以简化为
输入——检测是否存在风险——输出
可以看到,一切都与输入息息相关,毕竟对于黑客来说,能控制的也只有输入,所以挖掘漏洞最好的入口点,也就是从输入开始
可以将程序员的代码分割为两个部分
一部分是控制代码走向的控制流代码
另一部分是用来被展示,被存储,被流转的数据流(包括输入的数据和程序员设定的数据)
为什么要强调数据流操作的这个"被"字?
“主谓宾”层面,数据流的数据不应该有主动权
这个控制流和数据流如何帮助我们更好地去理解输入产生的漏洞?
程序员希望用户输入的一定是数据流,而不是控制流
一旦我们输入的数据(数据流)能够以某种方式侵入到控制流时,漏洞就随之产生了
先来看看这样一段存在SQL注入的PHP的代码
<?php
$db \= init\_db();
$username \= $\_GET\['username'\];
$db\->query("select \* from users where username = '$username'");
?>
看看这段代码的输入流转:
输入——php字符变量——SQL语句——数据库
程序员如果没考虑到这里的安全问题的话,他的控制流程想法如下
option: select
object: users
subject: *
condition:
key:username
value:$username
如上,程序员的想法是用户的输入只能影响结构中的value位置,但是我们通过输入恶意代码,就能够跳出数据流,从而影响到控制流,例如我们输入的username为
admin' and 1=1 #
此时数据库执行的语句
select * from users where username='admin’ and 1=1 #'
思考:那现在实际执行的控制流程跟程序员想法中的控制流程一样吗?
option:select
object:users
subject:*
condition:
expression: and
key1:username
value1:admin
key2:1
value2:1
如上,我们输入的数据被带入数据库中执行,从而导致数据流入侵到了控制流
所以程序员在编程时应该保证用户的数据只能影响数据流,例如上面的value,如果不能保障,那么就会出现漏洞
拓展:
这里就引出了代码审计的两个核心
能否让数据流逃逸到控制流
业务逻辑可能产生的点在哪(后面再介绍)
想方设法去执行一条完整的SQL语句,把数据带出来或把命令传进去
编程语言
不同编程原因最终目的都是为了将payload送进数据库层进行执行,能看到注入点即可,语言不重要
注入类型
比编程语言更重要一些,但其中所有的注入类型都是sql语句不同的写法而已
产生注入的输入点
条件?
客体(字段)?
对象(表名)?
......
可以理解为我们输入的数据处于SQL语句的哪个位置
仅仅抓住输⼊
当数据流⼊侵到控制流时,漏洞就产⽣了
“数据流⼊侵控制流”产⽣的⻛险点,在于不同层⾯组件的交汇处(如:代码层与数据库层)
是不是有点看不懂?没关系,更简单的总结如下
找输入点——哪些位置可能存在注入
构造payload——如何写入或者执行自己的恶意代码
找数据库交互的位置——哪些功能可能存在注入?
先来看看PHP预编译防止SQL注入的案例的
// 创建连接
$conn = new mysqli($servername, $username, $password, $dbname);
// 获取用户输入
$user_input = $_GET['user_input'];
// 使用预编译语句和参数绑定来防止 SQL 注入
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ?");
$stmt->bind_param("s", $user_input);
$stmt->execute();
$result = $stmt->get_result();
来看一段Java预编译防止SQL注入的案例
// 创建数据库连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "root", "password");
// 使用预编译语句
String query = "SELECT * FROM users WHERE username = ?";
PreparedStatement pstmt = conn.prepareStatement(query);
pstmt.setString(1, username); // 设置第一个占位符的值
// 执行查询并处理结果
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
System.out.println("User found: " + rs.getString("username"));
}
定了了正常的SQL语句之后,控制逻辑如下:
option: select
object: users
subject: *
condition:
key:username
value:$username
为什么需要使用预编译?——因为要防止SQL注入
为什么要防止SQL注入?——防止数据流入侵到控制流
怎样防止数据流入侵到控制流?——将用户的输入限制到输入流
预编译为什么能防止SQL注入
预编译语句通过将SQL查询语句与参数分开,使用占位符来代表参数,然后将用户输入的数据绑定到占位符上,确保了输入数据被正确地解释为字符串而不是SQL代码。因此,无论用户输入什么数据,都不会影响原始查询的结构和意图,从而有效地防止了SQL注入攻击。
也就相当于把下面这部分进行固定了,不允许改动
option: select
object: users
subject: *
condition:
key:username
value:
只允许用户控制$username,这样就保证了用户的输入只在数据流生效,而无法影响到控制流,这样自然就能防止SQL注入
好了,关于SQL注入的研究暂时就到这里了,后面有新的理解的话会在这篇文章中更新,有不对的地方欢迎各位师傅指正