目录
(五)使用 WAF(Web Application Firewall)
(五)使用 WAF(Web Application Firewall)
在当今的网络安全领域,SQL 注入漏洞仍然是一个极具威胁的问题,尤其对于企业而言,其危害不容小觑。本文将深入探讨企业 SQL 注入漏洞的相关内容,包括现状、危害、分类、挖掘思路以及防御措施等,并结合 Spring Boot 后端给出相应的代码示例。
一、SQL 注入漏洞的现状
尽管已经过了多年,SQL 注入问题依旧普遍存在。其根源在于缺乏安全编码规范,这使得攻击者有机可乘。
二、SQL 注入带来的风险
SQL 注入攻击的危害极大,它 “上得了机器权限,下得了数据”。攻击者利用此漏洞可能导致数据库被拖库,管理员和重要人员信息泄露,甚至能够直接获取 webshell 或者服务器系统权限等。例如,以下是一个可能导致数据库信息泄露的简单示例:
// 假设这是一个Spring Boot应用中的一个简单的用户登录验证接口
@RestController
public class UserController {
@Autowired
private UserRepository userRepository;
@PostMapping("/login")
public String login(@RequestBody UserLoginDto userLoginDto) {
String username = userLoginDto.getUsername();
String password = userLoginDto.getPassword();
// 这里存在SQL注入风险,直接将用户输入嵌入到SQL语句中
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
List<User> users = userRepository.findByCustomQuery(sql);
if (users!= null &&!users.isEmpty()) {
return "登录成功";
} else {
return "登录失败";
}
}
}
三、SQL 注入漏洞的分类
(一)按利用方式分类
- 盲注
- 攻击者通过构造特殊的 SQL 语句,根据数据库的响应来判断信息,即使没有直接获取到数据,也能逐步推断出数据库中的内容。例如,攻击者可能获取到数据库中的敏感信息,如用户表中的用户名和密码哈希值(如果密码是哈希存储的),或者其他关键信息,如订单信息、客户资料、财务数据等。以下是一个简单的盲注示例,假设数据库为 MySQL,通过判断页面响应时间来推断数据库中的数据:
// 模拟盲注攻击的代码片段,实际应用中攻击者会更复杂地构造请求
@GetMapping("/blind-injection")
public String blindInjection(@RequestParam("id") String id) {
String sql = "SELECT * FROM products WHERE id = " + id + " AND SLEEP(5)";
// 这里只是一个示例,实际应该有数据库操作和响应判断逻辑
return "执行查询";
}
- 报错注入
- 攻击者故意构造错误的 SQL 语句,使数据库返回错误信息,从错误信息中获取有用的线索。可能获取到的信息包括数据库的结构信息(如表名、列名)和数据库版本信息。例如:
// 模拟报错注入的代码片段
@GetMapping("/error-injection")
public String errorInjection(@RequestParam("name") String name) {
String sql = "SELECT * FROM users WHERE name = '" + name + "' AND 1/0";
// 这里只是一个示例,实际应该有数据库操作和响应判断逻辑
return "执行查询";
}
- time 盲注
- 与盲注类似,但主要通过观察数据库执行查询的时间来获取信息。攻击者可能获取到与盲注类似的用户信息、订单信息等,以及数据库中某些关键表的行数。例如:
// 模拟time盲注的代码片段
@GetMapping("/time-injection")
public String timeInjection(@RequestParam("id") String id) {
String sql = "SELECT * FROM products WHERE id = " + id + " AND IF(SUBSTR((SELECT password FROM users LIMIT 1), 1, 1) = 'a', SLEEP(5), 0)";
// 这里只是一个示例,在实际应用中,攻击者会根据数据库的响应时间来推断数据
return "执行命运";
}
4. **union注入**
- 通过使用UNION操作符将恶意查询与合法查询合并,获取额外的数据。攻击者可能获取到用户登录凭证(如用户名和密码)以及不同表中的相关数据(如产品信息和对应的用户购买记录)。例如:
```java
// 模拟union注入的代码片段
@GetMapping("/union-injection")
public String unionInjection(@RequestParam("category") String category) {
String sql = "SELECT * FROM products WHERE category = '" + category + "' UNION SELECT username, password, null FROM users";
// 这里只是一个示例,实际应该有数据库操作和响应判断逻辑
return "执行查询";
}
5. **内联查询注入**
- 攻击者通过构造复杂的内联查询语句来获取数据。可能获取到数据库中满足特定条件的数据(如某个类别下价格最高的产品信息以及对应的供应商信息)和与其他表关联的数据(如用户的购买历史与产品的详细信息的关联数据)。例如:
```java
// 模拟内联总查询注入的代码片段
@GetMapping("/inline-injection")
public String inlineInjection(@RequestParam("product") String product) {
String sql = "SELECT * FROM products WHERE name = (SELECT name FROM (SELECT name, ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS row_num FROM products) AS subquery WHERE row_num = 1) AND product = '" + product + "'";
// 这里只是一个示例,实际应该有数据库操作和响应判断逻辑
return "执行查询";
}
6. **拼接(堆)查询注入**
- 直接将用户输入拼接在SQL语句中,这种方式可能获取到的额外数据与其他注入方式类似,如用户账户信息(用户名、密码等)和系统中的关键业务数据(订单详情、客户信息、财务数据等)。例如前面登录验证接口的示例。
### (二)按攻击入口分类
1. **GET型的SQL注入**
- 通过构造带有恶意SQL语句的URL来进行攻击。可能获取的数据取决于数据库中存储的信息以及攻击的目标表。例如,如果攻击目标是产品表,可能获取产品的名称、价格、库存等详细信息;如果目标是用户表,可能获取用户的登录信息、个人资料等。以下是一个模拟GET型SQL注入的代码片段:
```java
// 模拟GET型SQL注入的代码片段
@GetMapping("/get-injection/{id}")
public String getInjection(@PathVariable("id") String id) {
String sql = "=" + id + " AND 1 = 1";
// 这里只是一个示例,实际应该有数据库操作和响应判断逻辑
return "执行查询";
}
- POST 型的注入
- 通过在 POST 请求的数据中嵌入恶意 SQL 语句进行攻击,与 GET 型注入类似,可能获取的数据取决于攻击的目标表和数据库中的存储信息。例如,在用户登录页面通过 POST 型注入可能获取用户的登录凭证,在订单提交页面可能获取订单的详细信息。如前面登录验证接口中
@PostMapping
部分的示例。
- 通过在 POST 请求的数据中嵌入恶意 SQL 语句进行攻击,与 GET 型注入类似,可能获取的数据取决于攻击的目标表和数据库中的存储信息。例如,在用户登录页面通过 POST 型注入可能获取用户的登录凭证,在订单提交页面可能获取订单的详细信息。如前面登录验证接口中
- Cookie 型的注入
- 通过修改 Cookie 中的值来嵌入恶意 SQL 语句进行下面可能获取的数据包括用户相关的个性化设置数据(如主题设置、语言偏好等)和用户的身份验证相关数据(如登录状态或身份令牌,进而可能获取到用户的账户信息和相关权限)。例如:
// 模拟Cookie型SQL注入的代码片段
@GetMapping("/cookie-injection")
public String cookieInjection(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (c款肉eakers!= null) {
for (Cookie cookie : cookies) {
if ("userdata".equals(cookie.getName())) {
String value = cookie.getValue();
String sql = "SELECT * FROM users WHERE data = '" + value + "'";
// 这里只是一个示例,实际应该有防止这种注入的安全机制
return "执行查询";
}
}
}
return "未找到相关Cookie";
}
4. **Header型SQL注入**
- 通过修改HTTP头中的值来嵌入恶意SQL语句进行攻击。可能获取的数据包括与身份验证相关的数据(如授权令牌,从而可能获取用户的账户信息和相关权限)和系统配置相关数据(某些系统可能会在HTTP头中传递一些系统配置信息,攻击者可以获取这些信息,了解系统的设置情况,进而可能利用这些信息进行更深入的攻击)。例如:
```java
// 模拟Header型SQL注入的代码片段
@GetMapping("/header-injection")
public String headerInjection(HttpServletRequest request) {
String authorization = request.getHeader("Authorization");
if (authorization!= null) {
String sql = "SELECT * FROM users WHERE token = '" + authorization + "'";
// 这里只是一个示例,实际应该有防止这种注入的安全机制
return "执行查询";
}
return "未找到相关Header";
}
### (三)按注入点类型分类
1. **整型注入**
- 当输入的是整型数据且存在漏洞时,可能发生整型注入。可能获取的数据与攻击的目标表相关,例如,如果目标是产品表,可能获取产品的库存数量、价格等整型数据。以下是一个模拟整型注入的代码片段:
```java
// 模拟整型注入的代码片段
@GetMapping("/int-injection")
public String intInjection(@RequestParam("id") int id) {
String sql = "SELECT * FROM products WHERE id = " + id + " AND 1 = 1";
// 这里只是一个示例,实际应该有数据库操作和响应判断逻辑
return "执行查询";
}
2. **字符型注入**
- 当输入的是字符型数据且存在漏洞时,可能发生字符型合作注入,如前面很多示例中的用户名和密码输入部分。可能获取的数据与字符型输入相关,例如,如果是用户名输入部分存在漏洞,可能获取用户的登录凭证(用户名和密码)以及用户的个人资料等。
## 四、常见注入漏洞的频率和危害程度
1. **出现频率**
- 以平时关注的来说,频率高的有:盲注,time盲注,报错注入,union注入。
2. **危害程度**
- 在不影响正常服务的情况下,拼接查询算最高危害的,接下来就是union。
## 五、SQL注入漏洞的挖掘思路
### (一)白盒(结合黑盒)方式
1. **大的方面**:爬虫 + 规则
2. **具体操作**
- 从输入开始一个个往逻辑里看,检查变量的值来自哪里,是否有安全校验,安全校验是否匹配当前SQL操作的具体场景(字符集编码等也要留意)。沿着变量和函数的调用,一直回溯查到输入点。
- 了解代码使用的框架或者代码结构,如看看代码对请求进行路由和分发的方式,这个路由分发方式的设计和实现是否存在隐患,记录一下,再看看是否有一些统一的安全filter,记录下他的特性(任何统一的安全filter都会因为不了解后端调用的场景而产生绕过),然后再看看是否有基础的DB库,这个库是否实现了安 全的SQL操作,最后结合这些因素和方法1,可以轻易发现SQL注入点,更能站在防御者视角有效的给出举一反三的修复建议。
### (二)黑盒方式
1. 快速方式是,关键词匹配,找到SQL语句后,往回溯,看看这些SQL语句在哪被调用,哪里被带入了变量,变量的值来自哪里,是否有安全校验,安全校验是否匹配当前SQL操作的具体场景(字符集编码等也要留意),沿着变量和函数的调用,一直回溯查到输入点就好。
2. 有意思的方式是,了解代码对请求进行路由和分发的方式,这个路由分发方式的设计和实现是否存在隐患,记录一下,再看看是否有一些统一的安全filter,记录下他的特性(任何统一的安全filter都会因为不了解后端调用的场景而产生绕过),然后再看看是否与基础的DB库,这个库是否实现了安 全的SQL操作,最后结合这些因素和方法1,可以轻易发现SQL注入点,更能站在防御者视角有效的给出举一反三的修复建议。
## 六、防御措施
### (一)输入验证
1. 在Spring Boot应用中,对于所有用户输入,都应该进行严格的验证。例如,在处理用户输入的登录信息时:
```java
// 对用户名进行验证,只允许字母和数字
@PostMapping("/login")
public String login(@RequestBody UserLoginDto userLoginDto) {
String username = userLoginDto.getUsername();
if (!username.matches("[a-zA-Z0-9]+")) {
return "用户名只能包含字母和数字";
}
String password = userLoginDto.getPassword();
// 这里可以继续对密码进行验证,比如长度、复杂度等要求
// 后续可以继续进行安全的SQL操作,如使用预编译语句等
}
- 对于可能包含特殊字符的输入,如搜索框内容等,应该进行更严格的验证,防止 SQL 注入。例如:
@GetMapping("/search")
public String search(@RequestParam("query") String query) {
if (query.matches("[a-zA-Z0-9\\s]+")) {
// 这里可以继续进行安全的SQL操作,如使用预编译语句等
return "查询合法";
} else {
return "查询包含非法字符";
}
}
(二)输出编码
当将数据输出到 HTML 页面时,应该对数据进行合适的编码,以防止脚本被意外执行。虽然在 SQL 注入防御中,输出编码主要是针对防止 XSS 等相关问题,但也是整体安全的一部分。例如,在 Spring Boot 应用中,如果使用 Thymeleaf 等模板引擎,可以使用其内置的编码功能:
// 在Thymeleaf模板中使用${}表达式输出数据时,会自动进行合适的编码
@GetMapping("/user-info")
public String userInfo(Model model) {
User user = userRepository.findById(1L).get();
model.addAttribute("user", user);
return "user-info";
}
(三)使用预编译语句
在 Spring Boot 应用中,应该尽量使用预编译语句来执行 SQL 操作,避免直接将用户输入嵌入到 SQL 语句中。例如:
// 使用JPA的预编译语句功能
@PostMapping("/login")
public String login(@RequestBody UserLoginDto userLoginDto) {
String username = userLoginDto.getUsername();
String password = userLoginDto.getPassword();
// 使用预编译语句
TypedQuery<User> query = entityManager.createQuery("SELECT u FROM User u WHERE u.username = :username AND u.password = :password", User.class);
query.setParameter("username", username);
query.setParameter("password", password);
List<User> users = query.getResultList();
if (users!= null &&!users.isEmpty()) {
return "登录成功";
} else {
return "登录失败";
}
}
(四)日志监控
- 数据库日志监控是非常重要的。可以通过解析数据库日志来发现 SQL 注入点。例如,在 Spring Boot 应用中,可以配置数据库的日志级别和格式,以便更好地进行监控。
- 应用程序的日志也应该进行监控。例如,记录用户的登录尝试、查询操作等信息,以便及时发现异常行为。以下是一个简单的示例,在 Spring Boot 应用中记录用户登录尝试信息:
// 在登录接口中记录用户登录尝试信息
@PostMapping("/login")
public String login(@RequestBody UserLoginDto userLoginDto) {
String username = userLoginDto.getUsername();
String password = userLoginDto.getPassword();
Logger logger = LoggerFactory.getLogger(UserController.class);
logger.info("用户 {} 尝试登录,密码为 {}", username, password);
// 后续进行安全的SQL操作
}
(五)使用 WAF(Web Application Firewall)
- 统一的 filter(如 WAF)也是有效的。可以通过配置 WAF 来阻止 SQL 注入攻击。例如,在 Spring Boot 应用部署环境中,可以配置一个 WAF 来对传入的请求进行过滤。
- WAF 可以初筛似的解析输入,理解语法语义,对于语法错误的可以直接放行。但是要注意,由于不同类型数据库不同版本数据库的语法语义都有一定的差异,不可能一个 filter 能实现所有的解析,太精确...
05-浅谈企业SQL注入漏洞的危害与防御.pdf
(五)使用 WAF(Web Application Firewall)这里开始继续
(五)使用 WAF(Web Application Firewall)
- 统一的 filter(如 WAF)也是有效的。可以通过配置 WAF 来阻止 SQL 注入攻击。例如,在 Spring Boot 应用部署环境中,可以配置一个 WAF 如 ModSecurity 来对传入的请求进行过滤。以下是一个简单的配置示例,假设我们要阻止包含特定 SQL 注入关键字的请求:
# 启用ModSecurity并加载核心规则集
SecRuleEngine On
SecDefaultAction "phase:1,deny,status:403"
# 定义一个规则来阻止包含SQL注入关键字的请求
SecRule REQUEST_URI|REQUEST_BODY ".*(SELECT|INSERT|UPDATE|DELETE|UNION|OR|AND|FROM|WHERE|ORDER BY|GROUP BY|HAVING).*" "id:1001,phase:1,deny,status:403"
这个配置会检查请求的 URI 和请求体,如果包含常见的 SQL 注入关键字,就会拒绝该请求并返回 403 状态码。
- WAF 可以初筛似的解析输入,理解语法语义,对于语法错误的可以直接放行。但是要注意,由于不同类型数据库不同版本数据库的语法语义都有一定的差异,不可能一个 filter 能实现所有的解析,太精确的,就只能帮助防御特定版本的数据库了。为了更好地利用 WAF,需要根据实际使用的数据库和应用场景进行定制化配置。例如,如果使用的是 MySQL 数据库,可能需要针对 MySQL 的语法特点进行一些规则调整。同时,还需要定期更新 WAF 的规则集,以应对新出现的 SQL 注入攻击方式。
(六)代码扫描和培训
- 重要业务开发完成后,必须进行代码扫描。可以使用一些代码扫描工具来检查是否存在 SQL 注入漏洞。例如,在 Spring Boot 应用中,可以使用一些开源的代码扫描工具,如 SonarQube 等。 SonarQube 可以对代码进行静态分析,检测出可能存在的 SQL 注入风险点。以下是一个简单的示例,展示如何在 Spring Boot 项目中集成 SonarQube 进行代码扫描:
- 首先,在项目的
pom.xml
文件中添加 SonarQube 的插件依赖:
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.7.0.1746</version>
</plugin>
- 然后,在项目根目录下运行以下命令进行代码扫描:
mvn sonar:sonar
SonarQube 会对项目中的代码进行分析,并在其 Web 界面上显示出检测结果,包括可能存在的 SQL 注入漏洞以及其他代码质量问题。
- 研发人员应该接受安全培训,让他们有安全意识。只有研发人员具备安全意识,才能从源头上减少 SQL 注入漏洞的出现。安全培训可以包括 SQL 注入的原理、危害以及如何在代码中进行安全的 SQL 操作等内容。例如,可以通过内部培训课程、在线学习资源或者邀请安全专家进行讲座等方式来提高研发人员的安全意识。
七、如何第一时间发现正在被 SQL 注入攻击
(一)日志监控
通过对数据库日志和应用程序日志的监控,可以及时发现异常行为。如前面提到的,记录用户登录尝试、查询操作等信息,当出现异常的情况如下:
- 出现异常的查询语句,例如包含 SQL 注入关键字的语句。假设我们在数据库日志中发现一条查询语句为
SELECT * FROM users WHERE username = 'admin' AND password = '' OR 1 = 1
,这很可能是一个 SQL 注入攻击的尝试。 - 大量失败的登录尝试。如果在应用程序日志中发现有大量来自同一个 IP 地址或者针对同一个用户账户的失败登录尝试,这也可能表明正在被 SQL 注入攻击,因为攻击者可能正在通过 SQL 注入尝试获取正确的登录凭证。
(二)蜜罐数据
在数据库里可放置一些蜜罐数据的帐号和密码。当这些帐号被登录时,立即报警。例如,在用户表里,可以在前几十行里,设置一些用户名和密码,实际上没有人用,一旦被登录,立马报警。具体实现可以在登录验证逻辑中添加额外的判断条件。以下是一个简单的示例,假设我们使用 Spring Boot 的@PostMapping
注解来处理用户登录请求:
@PostMapping("/login")
public String login(@RequestBody UserLoginDto userLoginDto) {
String username = userLoginDto.getUsername();
String password = userLoginDto.getPassword();
// 首先检查是否是蜜罐帐号
if (isHoneypotAccount(username)) {
// 如果是蜜罐帐号被登录,触发报警
triggerAlarm("蜜罐帐号被登录");
return "登录失败";
}
// 正常的登录验证逻辑
//...
}
其中isHoneypotAccount
函数用于判断是否是蜜罐帐号,triggerAlarm
函数用于触发报警机制。
(三)异常报警
结合日志监控和蜜罐数据,当发现异常行为时,可以通过邮件、短信或者其他即时通讯工具向系统管理员或者安全团队发送报警信息。例如,可以使用 Spring Boot 的@Async
注解来实现异步报警功能,确保报警信息能够及时发送出去,而不会影响到应用程序的正常运行。以下是一个简单的示例:
@Service
public class AlarmService {
private final Logger logger = LoggerFactory.getLogger(AlarmService.class);
@Async
public void sendAlarm(String message) {
// 这里可以使用邮件客户端或者短信网关等工具来发送报警信息
logger.info("发送报警信息: {}", message);
}
}
在上述示例中,当需要发送报警信息时,可以调用AlarmService
的sendAlarm
方法,它会在后台异步地发送报警信息。
希望通过对企业 SQL 注入漏洞的这些介绍,能够让开发人员和企业更加重视 SQL 注入问题,并采取有效的措施来预防和应对此类漏洞。同时,持续学习和关注网络安全领域的最新动态也是非常重要的,以确保能够及时应对新出现的安全威胁。