首页 > 数据库 >【JAVA安全】JOOQ框架常见SQL注入场景

【JAVA安全】JOOQ框架常见SQL注入场景

时间:2024-08-25 18:29:01浏览次数:11  
标签:JOOQ JAVA name jooq DSL JooqPojo SQL String

一、关于JOOQ
    1.1 核心接口
    1.2 常见参数绑定方式

二、 常见SQL注入场景
    2.1 Plain SQL API









以下文章来源于亿人安全 ,作者tkswifty

JOOQ是一个ORM框架,利用其生成的Java代码和流畅的API,可以快速构建有类型约束的安全的SQL语句。
本文主要介绍该框架常见的SQL注入场景。給代码安全审计提供一定的思路。

一、关于JOOQ

JOOQ是一个ORM框架,利用其生成的Java代码和流畅的API,可以快速构建有类型约束的安全的SQL语句。其使用与mybatis和Hibernate ORM不同的思路来实现对象关系映射ORM 。

1.1 核心接口

通过这两个接口可以执行对应的SQL语句:

org.jooq.impl.DSL是生成所有jOOQ对象的主要类。它作为一个静态的工厂去生成数据库表表达式,列表达式,条件表达式和其他查询部分。

org.jooq.DSLContex可以理解为一个SQL执行器,通过静态方法 DSL.using,可以获取一个 DSLContext 实例,此实例抽象了所有对于SQL的操作API,可以通过其提供的API方便的进行SQL操作。

举例说明:

    public JooqPojo selectByName(String name) {
        return dslContext.select()
                .from(jooq)
                .where(jooq.NAME.eq(name)).fetchAny(r -> r.into(JooqPojo.class));

    }

查看SQL执行日志,可以看到jooq已经对name参数进行了参数绑定,避免了SQL注入的问题:

1.2 常见参数绑定方式

DSL.param()

DSL.param()创建一个绑定变量,该绑定变量的生成方式?与SQL一样。
例如如下例子,参数绑定Name:

public JooqPojo selectByName(String name, String content) {
        return dslContext.select()
                .from(jooq).where(DSL.field("name").eq(DSL.param(jooq.NAME.getName(),name))).fetchAny(r -> r.into(JooqPojo.class));

    }

查看对应的的sql日志,对应的查询已经完成了绑定,避免了SQL注入的风险:

Object... bindings参数

跟其他框架类似,均支持?和:param的方式进行参数绑定:

dslContext.select().from(jooq).where("name=?",name).fetchAny(r -> r.into(JooqPojo.class));
dslContext.select().from(jooq).where("name=:name",name).fetchAny(r -> r.into(JooqPojo.class));

对应的SQL执行日志:

除此之外还支持{index}的方式:

dslContext.select().from(jooq).where("name={0} and address={1}",name,content).fetchAny(r -> r.into(JooqPojo.class))

对应的SQL执行日志:

表达式自身处理

例如mybatis里常见的like查询,经常会出现SQL注入问题,jooq提供的表达式已经进行了相应的处理,使用也比较方便:

result=result.and(jooq.NAME.like("%" + name + "%"));

查看对应的日志已经进行了参数绑定:

二、 常见SQL注入场景

2.1 Plain SQL API

在一定的程度上,JOOQ确实解决了大部分场景的SQL注入问题。但是jOOQ并不支持每个数据库中的所有SQL功能,JOOQ还存在很多字符串sql拼接的API,例如如下的and(String s),可以看到JOOQ給对应的API标记了@PlainSQL注解,注释里也提醒了会存在SQL注入风险:

 /**
     * Combine the currently assembled conditions with another one using the
     * {@link Operator#AND} operator and proceed to the next step.
     * <p>
     * <b>NOTE</b>: When inserting plain SQL into jOOQ objects, you must
     * guarantee syntax integrity. You may also create the possibility of
     * malicious SQL injection. Be sure to properly use bind variables and/or
     * escape literals when concatenated into SQL clauses!
     *
     * @see DSL#condition(SQL)
     * @see SQL
     */
    @Support
    @PlainSQL
    SelectConditionStep<R> and(SQL sql);

举个例子,这里and()用户可控,且可以执行Plain SQL,那么直接在content参数写入恶意sql语句即可:


public JooqPojo selectByName(String name,String content) {
        return dslContext.select()
                .from(jooq)
                .where(jooq.NAME.eq(name)).and(content).fetchAny(r -> r.into(JooqPojo.class));

    }

这里直接使用updatexml尝试报错注入,成功获取数据库用户名:

对于Plain SQL的情况,官方文档也进行了说明:

下面再列举一些常见的场景:

2.1.1 执行任意 SQL、函数和存储过程

在查询where 子句中经常需要包含执行任意 SQL、函数和存储过程的需求。通过condition可以满足该需求。

org.jooq.Condition条件表达式

在Condition接口中提供了如下几个方法执行plain SQL:

and(String)               // Combine conditions with AND. Convenience for adding plain SQL to the right-hand side
and(String, Object...)    // Combine conditions with AND. Convenience for adding plain SQL to the right-hand side
and(String, QueryPart...) // Combine conditions with AND. Convenience for adding plain SQL to the right-hand side

or(String)                // Combine conditions with OR. Convenience for adding plain SQL to the right-hand side
or(String, Object...)     // Combine conditions with OR. Convenience for adding plain SQL to the right-hand side
or(String, QueryPart...)  // Combine conditions with OR. Convenience for adding plain SQL to the right-hand side

例如如下例子:

public Condition condition(String name){
    Condition result = DSL.noCondition();
    result=result.and("name like '%"+name+"%'");
    return  result;
}

在service层进行对应的调用:

return dslContext.select().from(jooq).where(condition(name)).fetchAny(r -> r.into(JooqPojo.class));

这里直接将name进行SQL拼接,存在SQL注入的风险(报错注入验证):

正确的做法应该是对对应的参数进行预编译处理:

result=result.and("name like CONCAT('%',?,'%')",name);
result=result.and(jooq.NAME.like("%" + name + "%"));

DSL.condition()

可以通过DSL创建condition,然后在where子句中执行。
例如需要执行length()函数,搜索name小于对应长度的记录:

return dslContext.select()
        .from(jooq).where(DSL.condition("length(name)<"+size)).fetchAny(r -> r.into(JooqPojo.class));

同样的这里也是直接调用的Plain SQL API:

@Support
@PlainSQL
public static Condition condition(String sql) {
    return condition(sql);
}

如果size参数是string类型且用户可控的话会存在SQL注入风险(这里执行updatexml报错注入演示):

2.1.2 动态表名

实际业务中往往有动态表名的需求,例如函数接受一个名为"entityType"的参数,并根据该参数查询表entityType_other_stuff。
使用DSL.table()即可满足类似的需求。

DSL.table()也有很多重载的方法,部分方法是@PlainSQL标注的,使用不当会存在SQL注入风险:

@Support
@PlainSQL
public static Table<Record> table(String sql) {
    return table(sql);
}

Example:

这里直接传入tableName对对应的表进行查询:

public JooqPojo selectByTableName(String tableName) {
        return dslContext.select()
                .from(DSL.table(name(tableName)))
                .limit(0,1).fetchAny(r -> r.into(JooqPojo.class));
}

可以直接在tableName写入恶意SQL,达到SQL注入的效果:

PS:参考官方文档里的

https://www.jooq.org/doc/3.16/manual/sql-building/names/

更为安全的用法是DSL.table(name(tableName)),使用org.jooq.impl.DSL.name进行处理。

同样是上面的例子,修改后具体的查询将tableName用``包裹,此时输入任意内容均会被认为是表名的一部分,从某种程度上避免了SQL注入的风险。

动态列名DSL.field()同理。

2.1.3 直接执行SQL

DSLContext/DSL包含了几个API用于执行plain SQL,如果使用不当直接使用了SQL拼接,可能会存在SQL注入的风险:

    query

    ResultQuery

    execute

    fetech(等价于resultQuery(...).fetch())

根据官方文档提供的case可以看到,实际上是直接SQL执行,如果SQL内容用户可控的话,那么可能存在SQL注入风险:

// Create a Query object and execute it:
Query query = create.query("DELETE FROM BOOK");
query.execute();
// Create a ResultQuery object and execute it, fetching results:
ResultQuery<Record> resultQuery = create.resultQuery("SELECT * FROM BOOK");
Result<Record> result = resultQuery.fetch();

同样的,可以通过参数绑定来避免对应的问题(类似orderby等动态场景可以考虑过滤输入或者白名单的方式来避免SQL注入):

String sqlTemp="select * from jooq where name ={0}";
return dslContext.resultQuery(sqlTemp,name).fetchAny(r -> r.into(JooqPojo.class));

除了上面提到的3种场景以外,还有很多由于使用不当导致SQL注入风险,本质上其实都是 @PlainSQL 方法的调用。

三、其他

一般情况下,为了避免错误使用 @PlainSQL 注解标记的 API 导致 SQL 注入问题,可以引入 jooq-checker 来进行检查:

<dependency>
    <groupId>org.jooq</groupId>
    <artifactId>jooq-checker</artifactId>
    <version>${version}</version>
</dependency>

引入依赖后,指定如下Maven编译器配置即可:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.3</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <fork>true</fork>
        <annotationProcessors>
            <annotationProcessor>org.jooq.checker.PlainSQLChecker</annotationProcessor>
        </annotationProcessors>
        <compilerArgs>
            <arg>-Xbootclasspath/p:1.8</arg>
        </compilerArgs>
    </configuration>
</plugin>

其中org.jooq.checker.PlainSQLChecker将确保不会编译使用带有@PlainSQL注释的API,并抛出对应的错误:

java: [Plain SQL usage not allowed at current scope. Use @Allow.PlainSQL.] (Plain SQL usage not allowed at current scope. Use @Allow.PlainSQL.)

如果确实需要使用jOOQ的@PlainSQL API,则可以通过@Allow.PlainSQL对该位置(scope)进行注释,此时即可通过编译:

    @Override
    @Allow.PlainSQL
    public JooqPojo selectByName(String name,String content) {
        return dslContext.select()
                .from(jooq)
                .where(jooq.NAME.eq(name)).and(content).fetchAny(r -> r.into(JooqPojo.class));
    }

也就是说,在进行代码审计的时候,可以通过检索 @Allow.PlainSQ 关键字,来查看对应的方法使用是否合理,是否通过占位符 进行预编译处理/是否对用户的输入进行安全过滤等 。

标签:JOOQ,JAVA,name,jooq,DSL,JooqPojo,SQL,String
From: https://www.cnblogs.com/o-O-oO/p/18379001

相关文章

  • Spring Boot 3.3 【五】Spring Boot 整合JPA-原生SQL支持
    在使用JPA(JavaPersistenceAPI)时,虽然它主要通过对象而不是SQL语句来操作数据库,但在处理复杂查询时,我们有时仍需要直接使用SQL。这可以通过@Query注解在Repository接口中实现。以下是一个使用SpringDataJPA和原生SQL来处理复杂数据库查询的实例。遇见即是缘分,关注......
  • C/C++、Java、Python全面比较
    以下是对C/C++、Java、Python三种编程语言的全面比较,以表格形式呈现:特性/语言C/C++JavaPython类型系统静态类型静态类型动态类型内存管理手动管理自动管理(垃圾回收)自动管理(垃圾回收)编译/解释编译型编译型(通过JVM解释执行)解释型性能高(直接编译成机器码)中等(通过JIT优化)较低(解......
  • 56个JavaScript 实用工具函数助你提升开发效率!
    今天来看看JavaScript中的一些实用的工具函数,希望能帮助你提高开发效率!整理不易,如果觉得有用就点个关注鼓励一下吧!1.数字操作(1)生成指定范围随机数export const randomNum = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;(2)数字千分......
  • 软件设计之MySQL(7)
    软件设计之MySQL(7)此篇应在JavaSE之后进行学习:路线图推荐:【Java学习路线-极速版】【Java架构师技术图谱】Navicat可以在软件管家下载``使用navicat连接mysql数据库创建数据库、表、转储sql文件,导入sql数据MySQL数据库入门到大牛,mysql安装到优化,百科全书级,全网天花......
  • zblog网站提示MySQL数据库无法连接
    前天搭建好的博客网站,在睡了一觉之后,网站竟然出现故障无法正常运行了,在此期间,没有进行任何的操作。就连主页都无法进入,页面一直提示“MySQL数据库无法连接”。不管怎样刷新页面,还是重启服务器,都毫无作用。最终,从中午1点一直忙活到凌晨2点,还好皇天不负有心人,总算把问题解决了......
  • SQLserver中的触发器和存储过程
    在SQLServer中,触发器是一种特殊的存储过程,它在指定的数据库表上发生特定的数据修改事件时自动执行。触发器可以用于执行各种任务,如数据验证、数据审计、自动更新相关表等。触发器的类型SQLServer支持以下几种类型的触发器:INSERT触发器:在向表中插入新行时触发。UPD......
  • Java 集合(小白版)
     集合是整个JavaSE乃至于JavaEE很重要的知识点基本数据类型(8个):short,int,long,float,double,byte,char,boolean引用数据类型(3个):类,接口,数组数组(3种声明方式):int[]arr={7,8,9}      int[]arr=newint[3]      int[]arr=newint[]{1,2,3}简单说集合......
  • sql server导入mysql,使用python多线程
    概述在上一篇文章中,链接:https://www.cnblogs.com/xiao987334176/p/18377915使用工具SQLyog进行导入,传输过程是单进程的,一个表一个表的传,一条条数据插入,所以传输速度会比较慢。如果sqlservermdf文件在200m左右,传输需要花费30分钟左右。如果来了一个10GB左右的mdf的文件,需要25......
  • Java中stream的详细用法
    原文地址:https://www.cnblogs.com/Ao0216/p/15319553.html一、概述Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用StreamAPI对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使......
  • Java行为型设计模式-访问者模式(含二叉树场景示例)
    1.访问者模式简介访问者模式(VisitorPattern)是一种行为型设计模式,其主要目的是将数据结构与数据操作解耦。用于将数据结构和在数据结构上的操作分离开来。‌这种模式允许在不修改数据结构的情况下,定义新的操作。2.访问者模式角色访问者模式的主要角色包括:2.1抽象访问......