文章目录
-
- [JDBC 注入](#JDBC 注入)
-
- 语句拼接(Statement)
- 语句拼接(PrepareStatement)
-
- [修复方案 预编译](#修复方案 预编译)
- JdbcTemplate
- MyBatis
-
- [Like 注入](#Like 注入)
- [Order By 注入](#Order By 注入)
- [In 注入](#In 注入)
寒假学了一个月 pwn,真心感觉这玩意太底层学的我生理不适应了,接下来学一段时间 java 安全缓一缓吧。
靶场来源 :j3ers3/Hello-Java-Sec: ☕️ Java Security,安全编码和代码审计 (github.com)
bewhale/JavaSec: Java安全 学习记录 (github.com)
JDBC 注入
语句拼接(Statement)
java
// 采用Statement方法拼接SQL语句,导致注入产生
public String vul1(String id) {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(db_url, db_user, db_pass);
Statement stmt = conn.createStatement();
// 拼接语句产生SQL注入
String sql = "select * from users where id = '" + id + "'";
ResultSet rs = stmt.executeQuery(sql);
...
}
这就是原始人漏洞了,这边我们报错注入。
payload
sql
1' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1)-- +
修复方案
java
// 采用黑名单过滤危险字符,同时也容易误伤(次方案)
public static boolean checkSql(String content) {
String[] black_list = {"'", ";", "--", "+", ",", "%", "=", ">", "*", "(", ")", "and", "or", "exec", "insert", "select", "delete", "update", "count", "drop", "chr", "mid", "master", "truncate", "char", "declare"};
for (String s : black_list) {
if (content.toLowerCase().contains(s)) {
return true;
}
}
return false;
}
语句拼接(PrepareStatement)
java
// PrepareStatement会对SQL语句进行预编译,但如果直接采取拼接的方式构造SQL,此时进行预编译也无用。
public String vul2(String id) {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(db_url, db_user, db_pass);
String sql = "select * from users where id = " + id;
PreparedStatement st = conn.prepareStatement(sql);
ResultSet rs = st.executeQuery();
}
虽然是预编译吧,但是没用占位符 ?,和字符拼接没什么区别
payload
id=2 or 1=1
修复方案 预编译
java
// 正确的使用PrepareStatement可以有效避免SQL注入,使用?作为占位符,进行参数化查询
public String safe1(String id) {
String sql = "select * from users where id = ?";
PreparedStatement st = conn.prepareStatement(sql);
st.setString(1, id);
ResultSet rs = st.executeQuery();
}
JdbcTemplate
java
// JDBCTemplate是Spring对JDBC的封装,如果使用拼接语句便会产生注入
public Map<String, Object> vul3(String id) {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
...
JdbcTemplate jdbctemplate = new JdbcTemplate(dataSource);
String sql_vul = "select * from users where id = " + id;
// 安全语句
// String sql_safe = "select * from users where id = ?";
return jdbctemplate.queryForMap(sql_vul);
}
化石拼接语句的锅
修复方案
java
// ESAPI 是一个免费、开源的、网页应用程序安全控件库,它使程序员能够更容易写出更低风险的程序
// 官网:https://owasp.org/www-project-enterprise-security-api/
public String safe3(String id) {
Codec<Character> oracleCodec = new OracleCodec();
Statement stmt = conn.createStatement();
String sql = "select * from users where id = '" + ESAPI.encoder().encodeForSQL(oracleCodec, id) + "'";
ResultSet rs = stmt.executeQuery(sql);
}
MyBatis
Mybatis的SQL语句可以基于注解的方式写在类方法上面,更多的是以xml的方式写到xml文件。
Mybatis中SQL语句需要我们自己手动编写或者用generator自动生成。编写xml文件时,Mybatis支持两种参数符号,一种是#,另一种是$。比如:
xml
<select id="queryAll" resultMap="resultMap"> SELECT * FROM NEWS WHERE ID = #{id}</select>
使用预编译,$使用拼接 SQL。Mybatis框架下易产生SQL注入漏洞的情况主要分为以下三种:
Like 注入
Mybatis模糊查询:
java
Select * from users where username like '%#{username}%'
在这种情况下使用 # 程序会报错,把 # 号改成 $ 可以解决
但是如果java代码层面没有对用户输入的内容做处理,那么将会产生SQL注入漏洞。
正确写法:
java
Select * from users where username like concat('%',#{username}, '%')
Order By 注入
由于使用 #{} 会将对象转成字符串(因为预编译机制,系统将我们输入的字符当作了一个字符串)根据字符串排序是不生效的
因此很多研发会采用${}来解决,从而造成SQL注入
POC:
id and (updatexml(1,concat(0x7e,(select user())),0))-- -
因此,此种情况下,安全的做法应当在 Java 代码层面来进行解决。可以设置一个字段值的白名单,仅允许用户传入白名单内的字段。
java
String sort = request.getParameter("sort");
String[] sortWhiteList = {"id", "username", "password"};
if(!Arrays.asList(sortWhiteList).contains(sort)){
sort = "id";
}
In 注入
在 IN 关键字之后使用 #{}
查询多个参数:
xml
<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username in (#{usernames})
</select>
in之后多个username查询时使用 # 同样会报错(因为预编译机制,系统将我们输入的字符当作了一个字符串,因此查询结果为空,不能满足业务功能需求),因此很多研发会采用${}来解决,从而造成SQL注入
select * from user_table where username in (${usernames})
POC:
1,2,3) and (updatexml(1,concat(0x7e,(select user())),0))-- -
此种情况下,安全的做法应当使用 foreach 标签:
xml
<select id="getUserFromList" resultType="user.NewUserDO">
select * from user_table where username in
<foreach collection="list" item="username" open="(" separator="," close=")">
#{username}
</foreach>
</select>
总结一下,碰到 jdbc 的 预编译 + 占位符 或者 mabatis 的预编译 #{} 就不用深挖了。如果碰到 jdbc 不带 预编译占位符 或者 mybatis 拼接字符 ${} 的话,值得 sql 注入一试。