JavaSec 之 SQL 注入简单了解

文章目录

寒假学了一个月 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 注入一试。

相关推荐
AI_56789 小时前
阿里云OSS成本优化:生命周期规则+分层存储省70%
运维·数据库·人工智能·ai
送秋三十五9 小时前
一次大文件处理性能优化实录————Java 优化过程
java·开发语言·性能优化
choke2339 小时前
软件测试任务测试
服务器·数据库·sqlserver
龙山云仓9 小时前
MES系统超融合架构
大数据·数据库·人工智能·sql·机器学习·架构·全文检索
雨中飘荡的记忆9 小时前
千万级数据秒级对账!银行日终批处理对账系统从理论到实战
java
IT邦德9 小时前
OEL9.7 安装 Oracle 26ai RAC
数据库·oracle
jbtianci10 小时前
Spring Boot管理用户数据
java·spring boot·后端
Sylvia-girl10 小时前
线程池~~
java·开发语言
魔力军10 小时前
Rust学习Day3: 3个小demo实现
java·学习·rust
时艰.10 小时前
java性能调优 — 高并发缓存一致性
java·开发语言·缓存