当前位置: 首页 > news >正文

代码审计-SQL注入

背景知识:SQL 注入是什么?

  • SQL 注入发生在应用程序将用户输入直接拼接到 SQL 语句中,导致输入被当作代码(而非数据)执行。
  • 例如,在登录功能中,如果恶意用户输入 admin' --,它可能改变 SQL 逻辑,让攻击者无需密码就能登录。
  • ​为什么危险?​​ 可能导致数据泄露、数据篡改,甚至整个数据库被破坏。

JDBC

1. 安全做法:预编译语句 + 参数化查询

String sql = "SELECT * FROM users WHERE username = ?";  // SQL 模板,使用 ? 作为占位符
PreparedStatement stmt = conn.prepareStatement(sql);   // 预编译这个 SQL 模板
stmt.setString(1, username);  // 参数绑定:将 username 输入安全地设置到第一个 ? 的位置
  • ​为什么安全?​
    • ​预编译(PreparedStatement)​​:数据库(如 MySQL、PostgreSQL)先将 SQL 语句编译成一个“模板”(只包含结构,如 SELECT * FROM users WHERE username = ?)。数据库知道 ? 是占位符,不是代码。
    • ​参数绑定(setString)​​:用户输入(如 username)在编译后被绑定到占位符。数据库引擎会处理输入,确保它被转义或作为纯数据处理(例如,输入 admin' -- 会被转义成安全字符串,而不是改变 SQL 逻辑)。
    • ​隔离输入和指令​​:用户输入永远不会成为 SQL 代码的一部分,而是作为“数据”传递。这样,攻击者无法通过输入修改 SQL 结构。
  • ​关键点​​:始终使用 ? 占位符和 setXXX 方法(如 setStringsetInt)来绑定参数。
  • ​效果​​:能有效防御 SQL 注入,是行业最佳实践。

2. 高危漏洞:直接拼接用户输入

String sql = "SELECT * FROM users WHERE id = '" + id + "'";  // 直接拼接用户输入
// 例如,如果 id 是用户输入 "1' OR '1'='1",则 SQL 变成: SELECT * FROM users WHERE id = '1' OR '1'='1'
// 这会导致返回所有用户数据,因为 '1'='1' 永远成立!
  • ​为什么危险?​
    • ​直接拼接​​:用户输入(id)被直接插入 SQL 字符串中。如果输入包含恶意代码,它会成为 SQL 的一部分。
    • ​注入示例​​:如果用户输入 1' OR '1'='1,整个 SQL 变成 SELECT * FROM users WHERE id = '1' OR '1'='1'。这不再是查询特定 ID,而是返回所有数据(因为 '1'='1' 总是真)。攻击者甚至可以输入更危险的语句,比如 1'; DROP TABLE users; --,导致数据被删除。
    • ​风险根源​​:输入和代码没有隔离。数据库引擎将整个 SQL 字符串当作一个命令解析,无法区分用户输入和真实代码。
  • ​关键点​​:任何直接拼接用户输入到 SQL 字符串中的方式都是极高风险的,必须避免。

3.  无效防护:预编译后拼接(预编译无实际效果)

String sql = "SELECT * FROM users WHERE username='" + username + "'";  // 先拼接输入到 SQL 字符串
PreparedStatement stmt = conn.prepareStatement(sql);  // 然后预编译这个完整的 SQL 字符串
  • ​为什么无效?​
    • ​顺序问题​​:这里,用户输入 username 在调用 prepareStatement 之前就被拼接到 SQL 字符串中。例如,如果输入 admin' --,SQL 字符串会变成 SELECT * FROM users WHERE username='admin' --'-- 是 SQL 注释符,会使后面的条件失效)。
    • ​预编译的作用?​​ PreparedStatement 的预编译只发生在 SQL 字符串传递给它之后。但这时,SQL 字符串已经包含用户输入(没有占位符),所以数据库引擎会直接解析和执行这个完整字符串。
    • ​为什么无防护效果?​​ 预编译只是告诉数据库“这个 SQL 可以重用”,但它无法改变 SQL 内容。如果 SQL 在预编译前就包含恶意输入,输入就成了 SQL 代码的一部分,导致注入漏洞。参数化查询的核心是占位符 ?,而这里根本没有使用它,所以 PreparedStatement 只是被误用。
  • ​简单比喻​​:就好比你用 Word 写文档。安全做法是先写模板“Hello [姓名]”,然后在打印前填入名字;无效做法是直接写“Hello John”后再告诉 Word 模板化它——名字已经写进去了,无法防止恶意修改。
  • ​关键点​​:预编译语句只有在使用参数化查询(即 SQL 中有 ? 占位符)时才有效。如果 SQL 字符串在创建前就拼接输入,预编译是徒劳的。

Mybatis

1.使用 ${} 直接拼接​

<!-- 高危!直接拼接用户输入 -->
<select id="findUsers" resultType="User">SELECT * FROM ${tableName} WHERE username = '${username}'
</select>
  • ​风险​​:相当于直接拼接 SQL
  • ​审计关键​​:全局搜索 ${ 语法

2.​​ORDER BY 动态排序漏洞

<!-- 高危!直接拼接列名 -->
ORDER BY ${sortColumn} ${sortDirection}
  • 风险:输入列名为 id; DROP TABLE users-- 可注入
  • ​安全替代​​:白名单验证列名
private static final List<String> SAFE_COLUMNS = Arrays.asList("id", "name");
if (!SAFE_COLUMNS.contains(sortColumn)) {throw new InvalidColumnException();
}

3.IN 语句错误处理

// Java代码
List<User> findUsersInIds(@Param("ids") String ids);<!-- 错误方式:直接拼接 -->
SELECT * FROM users WHERE id IN (${ids})

​安全方式​​:

SELECT * FROM users WHERE id IN
<foreach item="id" collection="ids" open="(" separator="," close=")">#{id}
</foreach>

4.​​LIKE 语句漏洞

<!-- 错误方式 -->
WHERE name LIKE '%${keyword}%'安全方式​​:
WHERE name LIKE CONCAT('%', #{keyword}, '%')

作为代码审计新手的建议

  1. ​定位所有SQL执行点​​:

    ​​JDBC​​:搜索 .createStatement(、.prepareStatement(、.executeQuery(、.executeUpdate(
    ​​MyBatis​​:检查 XML Mapper 文件中的 <select>、<insert>、<update>、<delete> 标签
  2. ​识别用户输入源​​:

    request.getParameter()   // HTTP参数
    request.getHeader()      // HTTP头
    cookies.getValue()       // Cookie
    session.getAttribute()   // Session
    new String(byteArray)    // 二进制数据转换
    
    
  3. ​验证防护机制​​:

    • 是否存在直接字符串拼接?
    • 是否使用?占位符?
    • setXXX方法是否覆盖所有参数?
    • 是否错误地在预编译后拼接?
  4. ​检查ORM框架使用​​:

    • Hibernate:检查是否使用.createQuery()+参数绑定
    • JPA:检查是否使用@Query+:param命名参数
    • MyBatis:检查是否使用#{}而非${}
  • ​通用安全原则​​:
    • 绝对避免在 SQL 中直接拼接用户输入。
    • 始终采用参数化查询(占位符 + 绑定)。
    • 使用 ORM 框架(如 Hibernate)可以进一步简化安全查询,但也需注意配置错误。
  • ​学习资源​​:如果你是代码审计新手,推荐阅读 OWASP SQL Injection Prevention Cheat Sheet(免费在线文档),或使用工具如 SonarQube 自动扫描漏洞。
http://www.dtcms.com/a/269078.html

相关文章:

  • 简单的安卓ANR与卡顿分析
  • 要将本地分支强制更新为与远程分支完全一致(以远程为主
  • c++文字游戏_闯关打怪2.0(开源)
  • paimon.disk包:磁盘处理
  • 关于Novatek B/G-R/G白平衡色温坐标系再探究
  • 谢飞机的Java高级开发面试:从Spring Boot到分布式架构的蜕变之旅
  • 安卓10.0系统修改定制化____如何修改ROM 实现开机自动开启开发者选项与隐藏开发者选项
  • 基于区块链的电子签署系统的设计与实现(源码+文档+部署讲解)
  • da y54
  • LED 闪烁 LED 流水灯 蜂鸣器
  • IROS 2025|RL vs MPC性能对比:加州理工无人机实测,谁在「变形控制」中更胜一筹?
  • pg_class 系统表信息
  • React + Express 传输加密以及不可逆加密
  • OpenCV人脸分析------绘制面部关键点函数drawFacemarks()
  • day08-Elasticsearch
  • MinIO与SpringBoot集成完整指南
  • maven 发布到中央仓库常用脚本-02
  • 视频序列和射频信号多模态融合算法Fusion-Vital解读
  • 力扣 hot100 Day37
  • C++笔记之和的区别
  • Isaac Lab:让机器人学习更简单的开源框架
  • Go defer(二):从汇编的角度理解延迟调用的实现
  • RAG实战指南 Day 8:PDF、Word和HTML文档解析实战
  • Stirling-PDF 本地化部署,建立自己的专属PDF工具箱
  • 力扣_链表(前后指针)_python版本
  • 虚幻引擎UE5 GAS开发RPG游戏-02 设置英雄角色-18 改成网络多人游戏
  • C++:string类(3)(string类的模拟实现)
  • 批量OCR的GitHub项目
  • Linux 进程控制:全面深入剖析进程创建、终止、替换与等待
  • UI自动化常见面试题