目录
浅谈企业 SQL 注入漏洞的危害与防御
一、SQL 注入漏洞的现状
二、SQL 注入带来的风险
三、SQL 注入漏洞的分类
(一)按利用方式分类
(二)输出编码
(三)使用预编译语句
(四)日志监控
(五)使用 WAF(Web Application Firewall)
(五)使用 WAF(Web Application Firewall)
(六)代码扫描和培训
七、如何第一时间发现正在被 SQL 注入攻击
(一)日志监控
(二)蜜罐数据
(三)异常报警
在当今的网络安全领域,SQL 注入漏洞仍然是一个极具威胁的问题,尤其对于企业而言,其危害不容小觑。本文将深入探讨企业 SQL 注入漏洞的相关内容,包括现状、危害、分类、挖掘思路以及防御措施等,并结合 Spring Boot 后端给出相应的代码示例。
尽管已经过了多年,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 users = userRepository.findByCustomQuery(sql);
if (users!= null &&!users.isEmpty()) {
return "登录成功";
} else {
return "登录失败";
}
}
}
// 模拟盲注攻击的代码片段,实际应用中攻击者会更复杂地构造请求
@GetMapping("/blind-injection")
public String blindInjection(@RequestParam("id") String id) {
String sql = "SELECT * FROM products WHERE id = " + id + " AND SLEEP(5)";
// 这里只是一个示例,实际应该有数据库操作和响应判断逻辑
return "执行查询";
}
// 模拟报错注入的代码片段
@GetMapping("/error-injection")
public String errorInjection(@RequestParam("name") String name) {
String sql = "SELECT * FROM users WHERE name = '" + name + "' AND 1/0";
// 这里只是一个示例,实际应该有数据库操作和响应判断逻辑
return "执行查询";
}
// 模拟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 "执行查询";
}
@PostMapping
部分的示例。// 模拟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操作,如使用预编译语句等
}
@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 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 users = query.getResultList();
if (users!= null &&!users.isEmpty()) {
return "登录成功";
} else {
return "登录失败";
}
}
// 在登录接口中记录用户登录尝试信息
@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操作
}
05-浅谈企业SQL注入漏洞的危害与防御.pdf
(五)使用 WAF(Web Application Firewall)这里开始继续
# 启用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 状态码。
pom.xml
文件中添加 SonarQube 的插件依赖:
org.sonarsource.scanner.maven
sonar-maven-plugin
3.7.0.1746
mvn sonar:sonar
SonarQube 会对项目中的代码进行分析,并在其 Web 界面上显示出检测结果,包括可能存在的 SQL 注入漏洞以及其他代码质量问题。
通过对数据库日志和应用程序日志的监控,可以及时发现异常行为。如前面提到的,记录用户登录尝试、查询操作等信息,当出现异常的情况如下:
SELECT * FROM users WHERE username = 'admin' AND password = '' OR 1 = 1
,这很可能是一个 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 注入问题,并采取有效的措施来预防和应对此类漏洞。同时,持续学习和关注网络安全领域的最新动态也是非常重要的,以确保能够及时应对新出现的安全威胁。