Java企业常见漏洞
- 在某互联网二线大厂实习中遇到的:企业中一般常见的漏洞:sql、越权、ssrf、未授权、硬编码 key 等/动态加载脚本(反序列化较少)
- 在公司审计的代码就不便上传了,用一些网上能找到的例子学习举例
sql
- sql 注入的防御核心手段——预编译
- 1 将SQL语句的结构提前发送到数据库进行编译
- 参数作为数据单独传递、数据库会将占位符中的参数值视为数据,而非 sql 代码
- 2 自动转移特殊字符
- 1 将SQL语句的结构提前发送到数据库进行编译
企业 sql 简述
- 现象:企业级 java 白盒一般使用 sink 点进行告警,然后需要人工审计追踪数据流。但是其中存在大量误报
- 误报原因:
- MyBatis 中有两种拼接用户输入的占位符号,到 sql 语句中进行查询
${}是直接拼接,但是不容易报错#{}会对数据进行预编译,但是使用不规范易报错- 这就要求开发者在代码健壮性(不易报错)和 sql 注入的风险之间平衡
- 误报场景:
${}虽然直接拼接,触发了告警策略,但是可能存在以下场景,该用法是安全的- 1:在后续的传参追踪中,做了一些类型转换/白名单之类的去掉了可能的注入可能
- 2:在数据定义时做了限制——限制为整形
- 3:使用的参数
${id}是从数据库中查询出来的- 注意:这里可能存在二次注入风险,但是风险较小,一般企业中判断这种情况为误报
- 审计核心:
- 1:追踪危险传参的数据流(
Mapper.select)- 白盒 sink 点,会直接正向给出传参,正向追踪即可 post/get-service-impl…
- 代码审计(没有 sink 点可以用),需要在 idea 中反向追踪
- 这时候可以采用黑白盒结合的方式(eg:直接 idea 搜索
$)
- 这时候可以采用黑白盒结合的方式(eg:直接 idea 搜索
- 2:通过多种手段分析,是否能确定该处不存在注入
- 1:追踪危险传参的数据流(
- 存在注入核心:参数可控(不是从数据库查的数据拼接)、未预编译(
${})、未限制类型为整形
java 中执行 sql 的方式
java.sql.Statement 执行
- JDBC :是 java 中用于连接、操作数据库的 api
- Statement是Java JDBC下执行SQL语句的一种原生方式,执行语句时需要拼接
- 若拼接的语句没有有效过滤过滤,将出现SQL 注入
JDBC 使用步骤
1 | //加载驱动 |
示例
1 | Class.forName("com.mysql.cj.jdbc.Driver"); |
上面代码没有使用预编译PreparedStatement,存在 sql 注入风险

java.sql.PreparedStatement 执行
PreparedStatement是继承Statement的子接口——使用预编译处理参数- 占位符
?
- 占位符
- Sql 语句:预先定义结构、但参数值未被指定(多个
IN参数,用?作占位符) - 参数设定:
- 每个占位符的值,通过 sql 执行前的
setXXX提供setIntsetString
- 每个占位符的值,通过 sql 执行前的
- 优势:
- 执行速度更快:提前预编译执行结构、会缓存在数据库
- 防止 sql 攻击:参数只被当作查询值(而不是 sql 语句),同时转义特殊字符
案例
1 | import java.sql.*; |
预编译风险
动态拼接结构(非参数部分)
- 预编译的绕过(
Statement中的占位符是?)- 预编译仅对参数值(查询值)进行转义和隔离,sql 的结构部分(表名、列名、排序字段)无法用占位符
案例:动态拼接 sql 结构(非参数部分)
1 | String userInput = request.getParameter("orderBy"); // 用户传入 "id; DROP TABLE users--" |
- 用户传参
id; DROP TABLE users--闭合语句- 实际查询语句
SELECT * FROM users ORDER BY id; DROP TABLE users--
- 实际查询语句
- 解决:白名单-只允许合法字符
id、name、email等
Mybatis&Mybatis-Plus
- Mybatis:需要手写所有 sql ,通过 XML 描述符 or 注解把对象与 sql 语句关联
- (在需要高度自定义 sql 和效率等使用)
- Mybatis-Plus 是 Mybatis 的超集,保留灵活性的基础上,实现基础的自动化 sql 生成
- 通过
QueryWrapper、LambdaQueryWrapper等
- 通过
MyBatis 执行
(一般企业工程中 MyBtis 和 Mybatis-Plus 用的多一点,用原生的 Statement 和 PreparedStatement 很少)
- MyBatis 有两种方式自定义 SQL:
- 1 直接在
Mapper接口上加上注解 (适合简单的)@Select关联
- 2 在
XML中定义 SQL 语句(适合复杂动态的)- 通过
namespace和 sql 中的 id 关联
- 通过
- 1 直接在
假设 User 表的实体类定义和 Mapper 接口如下
1 | public class User { |
注解实现
但是不支持复杂动态 sql,eg<if>、<foreach>,需要借助@SelectProviderorxml
1 | public interface UserMapper { |
XML 实现
在 XML 文件中,通过 <mapper> 标签的 namespace 属性定义命名空间,该值必须与对应的 Mapper 接口的全限定类名一致。例如:
1 | <!-- 路径:src/main/resources/com/example/mapper/UserMapper.xml --> |
Mapper 接口的 全限定类名(即包名 + 接口名)隐式成为其命名空间,无需额外定义。例如:
1 | // 路径:src/main/java/com/example/mapper/UserMapper.java |
Mybatis占位符分析
- 两种占位符
#{}会预编译,但是使用有比较严格的规范,容易报错${}直接拼接,不容易报错,但易造成 sql 注入(以下几种情况可以使用${})- 1 是从数据库中直接查的数据 (虽然可能存在二次注入,但可能性较小)
- 2 传参限制了类型为整形
- 3 做了白名单、严格限制、编译等自定义措施
IN 查询
1 | // xml |
- 预编译了查询参数,若传参
ids = "1,2,3"最终执行 sql 如下SELECT * FROM news WHERE id IN ('1,2,3')- 数据库查询 id 值等于字符串
1,2,3的,而不是查询值等于 1/2/3——会报错
- 若改用
${},不会报错SELECT * FROM news WHERE id IN (1,2,3)- 数据库查询值等于 1/2/3,但会存在 sql 注入风险
- 解决方案:使用
<foreach>标签——用于动态生成多个IN的占位符
1 | //xml |
<font style="color:rgba(0, 0, 0, 0.85);"><foreach></font>标签用于遍历一个集合,把集合里的元素插入到 SQL 语句中item临时变量名、collection需遍历的集合名open、close指定在遍历开始和结束前插入的值、separator分隔符
- 最终执行
SELECT * FROM news WHERE id IN (1, 2, 3)- 假设 ids 集合是
<font style="color:rgba(0, 0, 0, 0.85);">[1, 2, 3]</font>
- 假设 ids 集合是
- (使用了多个占位符)
Like 查询
- like 模糊查询(多使用前缀查询可以提高效率)
1 | <select id="searchNews" resultType="News"> |
- 实际执行的 sql 应该是
SELECT * FROM news WHERE title LIKE '%?%'
- 假设传参
test,由于用了预编译,实际执行SELECT * FROM news WHERE title LIKE '%'test'%'- 这里直接语法错误了,会**报错**
- 若用
${}直接拼接,不会报错,但是会存在 sql 注入风险 SELECT * FROM news WHERE title LIKE '%test%'
- 若用
- 正确方式:使用
concat
1 | <select id="searchNews" resultType="News"> |
- 生成 sql 如下
SELECT * FROM news WHERE title LIKE CONCAT('%', ?, '%')
- 假设传参
test,由于预编译,虽然被加上单引号,但是因为 concat 机制,不会报错SELECT * FROM news WHERE title LIKE CONCAT('%', 'test', '%')
order by 查询
1 | <select id="selectTest" resultType="Test"> |
- 执行查询语句
SELECT * FROM test ORDER BY '?'
- 假设传参
ageSELECT * FROM test ORDER BY 'age'
- 问题:这里是一个字符串
'age',非列名,会报错- 若直接用
${}拼接,不会报错,但是存在 sql 注入风险 SELECT * FROM test ORDER BY age
- 若直接用
- 解决:应该使用
${}但是需要作控制- 1:转义
- 2:白名单
SSRF
- java 中 SSRF 的利用受限,不如 PHP 灵活(可以使用多种伪协议)
ftp/file上传下载文件、http(s)攻击内网 Web 应用- 在 SSRF 中利用
jar协议配合类加载漏洞实现 RCE- 1:利用 SSRF 访问远程服务器中的恶意 jar 文件
- 2:若目标应用存在类加载漏洞
URLClassLoader,可 RCE
java 中网络请求类
网络请求有很多:主要是含 http 、url、uri 的类
1 | HttpClient.execute |
- java 支持的协议
File、ftp、mailto、http、https、jar、netdoc- 含有 http 的类
HttpURLConnection、HttpClient、Request、okhttp只支持 http - 其他支持 java 支持的所有协议
- 含有 http 的类
SSRF 判断
- 人工主要判断两个
- 1:网络请求的服务器可控
- 2:没有做过滤:协议控制、ip 白名单、域名白名单、路径白名单、返回信息过滤、内网 ip 黑名单
- 自动化检测工具思路
- 1 遍历语法树找到调用点、污点数据流追踪
- (通过危险方法列表)
- 2 误报抑制:忽略白名单校验、安全过滤
- 1 遍历语法树找到调用点、污点数据流追踪

SSRF 防御
- 通用防御方法:
- 协议白名单、ip 白名单、域名白名单、路径白名单、返回信息过滤、内网 ip 黑名单
- 正则匹配:严格控制 URL 格式,避免
@和#绕过 - 严格控制出站流量
越权
- 其实主要就是看怎么做的鉴权
- 1:直接用 id 查询
- 2:用统一身份认证