Java代码审计的第一章-SQL注入

Java代码审计的第一章-SQL注入

这是一个系列

正式学习之前,先问下自己一下几个问题

1.sql注入是什么

2.在代码中具有什么特征

3.如何发现他

4.如何修复

5.如何绕过各种限制

我们正式开始解答上面的那些疑问

1.sql注入

sql是因为代码的不严谨,信任前端传输过来的参数,没有经过校验,或者不严谨,导致的sql语句被拼接,执行了意想不到的sql语句代码,泄露了不应被展示的数据。

更官方的定义是: 攻击者通过浏览器或者其他客户端将恶意SQL语句插入到网站参数中,网站应用程序未经过滤,便将恶意SQL语句带入数据库执行。

2.sql语句的特点

首先,我们需要找到常用的sql语句代码,例如select,update,delete,insert等语句,同时要注意在代码中的一些特征

Statement

createStatement

PreparStatement

like’%${

select

update

insert

同时在语句下造成漏洞的原因也不尽相同

在jdbc中的java.sql.prepared-statement中,如果使用了+进行拼接则可能存在sql注入

// 采用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);
    …

而看到这个地方我们可以想想,在id后面我们我们用“闭合,然后增加我们需要运行的sql语句1′ and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1)– +,updatexml报错注入,在错误日志中返回了规定的数据

d2b5ca33bd20240411151241

而如何修复呢,常见的方法是过滤,就像下面这样

// 采用黑名单过滤危险字符,同时也容易误伤(次方案)

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会对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();
}

d2b5ca33bd20240411151302

预编译整条语句,而正确的应该是

// 正确的使用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();
}      

使用占位符

而在一些特殊情况下 order by子句后面需要加字段名或者字段位置,而字段名是不能带引号的,否则就会被认为是一个字符串而不是字段名。Pre-pareStatement是使用占位符传入参数的,传递的字符都会有单引号包裹,“ps.setString(1,id)”会自动给值加上引号,这样就会导致order by子句失效。 所以还需要配合代码过滤一起使用,同时如果不对 %_进行转义则可能造成服务器资源占用的情况

对于框架中sql语句,例如spring框架中JdbcTemplate的sql注入代码,同样使用拼接就会造成注入

// 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);
}    

而我们的修复则可以

// 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);

ESAPI 是一个免费、开源的、网页应用程序安全控件库,它提供了一些编码和加密功能来防御一些安全威胁,如 XSS 和 SQL 注入等。

在这个例子中,首先使用 OracleCodec 来创建一个 ESAPI 编码器,然后使用 ESAPI 的 encoder() 方法对 id 参数进行编码,最后将编码后的 id 参数插入 SQL 查询语句中。这样可以确保输入的参数不会被当作 SQL 语句的一部分来执行,从而防止 SQL 注入攻击

而因为Java是强数据类型,所以当输入的参数不是string也可以防护sql注入

// 如果参数类型为boolean,byte,short,int,long,float,double等,sql语句无法拼接字符串,因此不存在注入

public Map<String, Object> safe4(Integer id) {
    String sql = “select * from users where id = ” + id;
    return jdbctemplate.queryForMap(sql);
}

而在框架myBatis中,他是通过XML描述符或者注解把对象和存储过程或者sql语句联系在一起的,支持自定义sql以及高级映射

类如

/ 由于使用#{}会将对象转成字符串,形成order by “user” desc造成错误,因此很多研发会采用${}来解决,从而造成SQL注入

@GetMapping(“/vul/order”)
public List<User> orderBy(String field, String sort) {
    return userMapper.orderBy(field, sort);
}

// xml方式
<select id=”orderBy” resultType=”com.best.hello.entity.User”>
    select * from users order by ${field} ${sort}
</select>

// 注解方式
@Select(“select * from users order by ${field} desc”)
List<User> orderBy2(@Param(“field”) String field);                    

而使用${}这种形式,就会造成拼接sql语句,而在orderby后面是不能使用#{}占位符进行排序,原因同上面orderby后面的参数不能“”一致,这时需要在代码传输过程中过滤,以及使用强类型过滤

在mybatis的like子句中使用#{}会报错 只能使用${},例如:“se-lect*from users where name like’%${user}%’”

@Select(“select * from users where user like ‘%${q}%'”)
List<User> search(String q);

// 安全代码,采用concat
@Select(“select * from users where user like concat(‘%’,#{q},’%’)”)
List<User> search(String q);

在in参数中,使用#{}与${},类似于 “’user1’,’user2’,’user3’,’user4’” ,具体原因是#{}会将多个参数当做一个整体

知道了具体特征,我们依据特征进行全局搜索,并分析数据的流转中,是否有校验,而后如何绕过即可

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享
评论 共2条

请登录后发表评论

    请登录后查看评论内容