引言:SQL注入是一个老生常谈且又非常重要的漏洞,导致许多热点的数据泄露事件。尽管学习起来相对简单,但它可能用于某些高危漏洞的利用。这使得它成为初学者的兴趣点,甚至对于更有经验的用户来说,SQL注入也是基本知识。
1. 简介
SQL注入是一种利用应用程序对用户输入的处理不当,使恶意用户能够执行未经授权的SQL查询的行为。当应用程序从用户那里接收输入并将其直接用作SQL查询的一部分时,就可能发生SQL注入,使得攻击者能够干扰应用程序对数据库的正常查询,甚至可访问敏感数据,引起数据泄露。
SQL注入根源:
- 执行外部参数拼接的SQL语句;
- 执行外部传入的整条SQL语句;
- 在配置文件中的SQL语句没有使用预编译方式占位符;
- 校验函数有缺陷或占位符使用错误。
比如,通过输入"1 and version() > 0"
,若应用程序正常返回,说明version()
被数据库识别并执行。因version()
是Mysql特有的函数,所以可以推断出后台数据库为Mysql。
Note:不同数据库的注入点可以查阅https://portswigger.net/web-security/sql-injection/cheat-sheet
2. 注入步骤
2.1. 确定目标
首先,你需要确定你想要攻击的目标。它可能是一个公开的网站,或者是一个内部网络应用。简而言之,你需要先找到一个有用户输入并且包含数据库连接信息的应用。
2.2. 寻找注入点
你需要寻找应用程序中可能存在SQL注入的点。注入点通常在用户输入字段(如用户名、密码、搜索查询等)中。当应用程序将这些输入直接插入到SQL查询中时,就可能存在注入风险。
常见注入点分类:
-
- 按参数类型区分:
- 数字型注入:比如
and 1=1
, - 字符型注入:比如
and '1'='1
-
- 按请求方式区分:
- GET注入
- POST注入
- HTTP注入
-
- 按执行效果区分:
- 报错注入
- UNION注入
- 布尔盲注
- 时间盲注
- 二次注入
- …
2.3. 构造恶意输入
基于你找到的注入点,你需要构造一个恶意输入。这个输入可能包含一些特殊字符或字符串,这些字符或字符串会被应用程序误解为SQL代码。例如,如果你正在寻找用户名和密码字段,一个可能的恶意输入可能是“admin; SELECT * FROM users”
,这个输入会触发一个查询,返回所有用户的信息。
2.4. 执行攻击
当你的恶意输入被提交到应用程序时,如果应用程序没有正确地验证和清理用户输入,那么它就会执行你构造的SQL代码。这可能导致数据库被篡改,或者敏感信息被泄露。
Note:通过构造特殊的SQL语句可猜测类似表名、字段名、用户名及密码信息。
2.5. 分析结果
一旦攻击成功,你需要分析数据库中的数据,以确认你的攻击是否成功。你可能需要查看特定的表或列,或者查看数据库的整体结构。
3. 常见攻击
在不同的情况下会发生许多SQL注入漏洞、攻击和技术。一些常见的SQL注入示例包括:
- 检索隐藏数据:可以在其中修改SQL查询以返回其他结果,比如文章开关介绍的探索数据库版本信息;
- 颠覆应用程序逻辑:中可以更改查询以干扰应用程序的逻辑;
- UNION攻击:可以从不同的数据库表中检索数据;
- SQL盲注:攻击者可以操控查询语句的参数,且查询的结果不会在应用程序的响应中返回,比如注入
xxx' and '1'='1
并不影响程序正常响应,但这可以确认此处存在注入点。
4. 防御
主要有以下4种防御方法:参数化查询、存储过程、输入数据校验及最小化权限配置。
4.1. 参数化查询,使用预编译语句
比如如下代码,直接通过拼接SQL方式执行查询,存储注入问题
String query = "SELECT * FROM products WHERE category = '"+ input + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);
可修改为下面的预编译语句形式,利用预编译将SQL编译后放入缓存池,当服务器执行入参时并不会编译SQL,而是直接传参,所以可以避免SQL注入。
PreparedStatement statement = connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();
4.2. 使用存储过程
使用存储过程也在可以缓解SQL注入,效果与预编译语句类似。区别在于存储过程需要先将SQL语句定义在数据库中,而非缓存池。
当然,在使用存储过程中也可能存在注入问题,需要尽量避免在存储过程内使用动态的SQL语句。如实在无法避免,可严格过滤输入或对输入编码后再处理。
4.3. 输入数据校验
对传入的数据类型做校验,比如只能是时间类型、整型,或满足指定格式(IP、邮箱格式)
4.4. 最小化权限配置
避免对普通账号赋予过高的权限,数据库账户设计时需要遵循最小化权限原则。比如,只涉及查询类的应用,数据库账户只需要赋予只读权限即可,避免因注入问题导致下个数据库被删除;
5. 参考
[1] https://portswigger.net/web-security/sql-injection#what-is-sql-injection-sqli
前期回顾:
「 典型安全漏洞系列 」01.XSS攻击详解