全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-11 6 分钟 ✍️ juanwangdev

Java SQL注入防范

SQL注入是Web应用最常见的安全漏洞,可导致数据泄露、篡改甚至系统被控制。

SQL注入原理

正常SQL

SQL
SELECT * FROM users WHERE name = '张三' AND password = '123456'

注入攻击

Java
// 用户输入
String name = "admin' OR '1'='1";
String password = "anything";

// 拼接SQL(危险!)
String sql = "SELECT * FROM users WHERE name = '" + name +
              "' AND password = '" + password + "'";

// 实际执行的SQL:
SELECT * FROM users WHERE name = 'admin' OR '1'='1' AND password = 'anything'
-- '1'='1' 永真绕过密码验证返回所有用户

注入类型

类型说明示例
注入查询修改查询逻辑' OR '1'='1
注入删除删除数据'; DROP TABLE users; --
注入更新修改数据'; UPDATE users SET role='admin';
注入执行执行系统命令xp_cmdshell(SQL Server)
联合查询获取其他数据UNION SELECT * FROM passwords

防范方法

1. 使用PreparedStatement(首要)

Java
// 不安全:拼接SQL
String sql = "SELECT * FROM users WHERE name = '" + name + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);

// 安全:PreparedStatement参数化
String sql = "SELECT * FROM users WHERE name = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, name);  // 参数安全设置,不会注入
ResultSet rs = pstmt.executeQuery();

PreparedStatement原理:

  • SQL先预编译,结构固定
  • 参数单独传递,不参与SQL解析
  • 参数值不会被当作SQL执行

2. 所有输入参数化

Java
// 查询条件
PreparedStatement pstmt = conn.prepareStatement(
    "SELECT * FROM users WHERE name = ? AND age > ?");
pstmt.setString(1, name);
pstmt.setInt(2, age);

// INSERT
PreparedStatement pstmt = conn.prepareStatement(
    "INSERT INTO users(name, password) VALUES(?, ?)");
pstmt.setString(1, name);
pstmt.setString(2, password);

// UPDATE
PreparedStatement pstmt = conn.prepareStatement(
    "UPDATE users SET name = ? WHERE id = ?");
pstmt.setString(1, newName);
pstmt.setInt(2, id);

// DELETE
PreparedStatement pstmt = conn.prepareStatement(
    "DELETE FROM users WHERE id = ?");
pstmt.setInt(1, id);

3. 动态列名/表名处理

PreparedStatement不能参数化列名和表名:

Java
// 不安全:动态列名
String sql = "SELECT " + columnName + " FROM users";  // 可注入!

// 安全:白名单验证
Set<String> allowedColumns = Set.of("id", "name", "age");
if (!allowedColumns.contains(columnName)) {
    throw new IllegalArgumentException("非法列名");
}
String sql = "SELECT " + columnName + " FROM users";

// 或使用枚举
public enum UserColumn { ID, NAME, AGE }
String sql = "SELECT " + userColumn.name().toLowerCase() + " FROM users";

4. 输入验证过滤

Java
// 数字验证
public int parseId(String id) {
    try {
        return Integer.parseInt(id);
    } catch (NumberFormatException e) {
        throw new IllegalArgumentException("ID必须是数字");
    }
}

// 长度限制
public void validateName(String name) {
    if (name == null || name.length() > 50) {
        throw new IllegalArgumentException("名称长度不合法");
    }
}

// 特殊字符过滤(辅助措施)
public String sanitize(String input) {
    return input.replaceAll("'", "")
                .replaceAll("--", "")
                .replaceAll(";", "");
}

// 但主要还是靠PreparedStatement,过滤只是辅助

5. MyBatis安全使用

XML
<!-- 不安全:${}字符串拼接 -->
<select id="findByName">
    SELECT * FROM users WHERE name = '${name}'  <!-- 可注入! -->
</select>

<!-- 安全:#{}参数化 -->
<select id="findByName">
    SELECT * FROM users WHERE name = #{name}  <!-- 安全 -->
</select>

规则:

  • #{}:参数化,安全,相当于PreparedStatement
  • ${}:字符串拼接,危险,只用于动态列名/表名
XML
<!-- 动态排序(需验证) -->
<select id="findAll">
    SELECT * FROM users ORDER BY ${orderBy}  <!-- 需白名单验证 -->
</select>

6. JPA/Hibernate安全

Java
// 安全:参数绑定
@Entity
@Table(name = "users")
public class User { ... }

// Spring Data JPA
public interface UserRepository extends JpaRepository<User, Long> {
    // 安全:方法名查询
    User findByName(String name);

    // 安全:@Query参数化
    @Query("SELECT u FROM User u WHERE u.name = :name")
    User findByNameQuery(@Param("name") String name);
}

// 不安全:拼接JPQL(避免!)
String jpql = "SELECT u FROM User u WHERE u.name = '" + name + "'";
Query query = em.createQuery(jpql);  // 可注入!

常见注入场景

登录注入

Java
// 攻击输入
username = "admin'--"
password = "anything"

// 原SQL
SELECT * FROM users WHERE username = 'admin'--' AND password = 'anything'
-- 注释掉密码验证直接登录admin

搜索注入

Java
// 攻击输入
keyword = "'; DROP TABLE products; --"

// 原SQL
SELECT * FROM products WHERE name LIKE '%'; DROP TABLE products; --%'
-- 删除products表

排序注入

Java
// 攻击输入
orderBy = "id; DROP TABLE users"

// 原SQL
SELECT * FROM users ORDER BY id; DROP TABLE users
-- 删除users表

安全编码规范

规范清单

规范说明
使用PreparedStatement所有SQL参数使用参数化
禁止拼接SQL不要用字符串拼接SQL
白名单验证动态列名/表名用白名单
输入验证验证输入格式和长度
最小权限数据库用户只给必要权限
错误隐藏不暴露SQL错误详情

错误处理

Java
// 不安全:暴露SQL详情
try {
    stmt.executeQuery(sql);
} catch (SQLException e) {
    throw new RuntimeException("SQL错误: " + e.getMessage());  // 暴露SQL!
}

// 安全:隐藏SQL详情
try {
    stmt.executeQuery(sql);
} catch (SQLException e) {
    throw new RuntimeException("系统错误,请联系管理员");  // 隐藏细节
    // 记录日志但不返回给用户
}

检测注入漏洞

代码审查

  • 检查所有SQL拼接
  • 检查是否使用PreparedStatement
  • 检查MyBatis的${}使用
  • 检查动态列名/表名验证

渗透测试

Java
// 测试输入
1' OR '1'='1
1; DROP TABLE test
admin'--
' UNION SELECT * FROM passwords --

注意事项

PreparedStatement是防范SQL注入的首要手段

动态列名/表名需白名单验证

MyBatis用#{}不用${}

输入验证只是辅助,不能替代参数化

不要暴露SQL错误细节给用户

数据库用户权限最小化

要点总结

  1. SQL注入修改SQL逻辑,可绕过验证、泄露数据
  2. PreparedStatement参数化是防范首要手段
  3. 动态列名/表名需白名单验证
  4. MyBatis #{}安全,${}危险
  5. 输入验证+最小权限+隐藏错误是辅助措施

📝 发现内容有误?点击此处直接编辑

← 上一篇 Java PreparedStatement与批处理
下一篇 → Java事务管理与隔离级别
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库