CodeQL从入门到入狱(二)

Posted by Azeril on October 8, 2023

时隔n天,重蹈覆辙

解决Lombok问题

在springboot中用过这个Lombok的类库。

主要功能就是 @Data 和我们写一堆 的getter \setter方法一致

package com.l4yn3.microserviceseclab.data;
import lombok.Data;

@Data
public class Student {
    private int id;
    private String username;
    private int sex;
    private int age;
}

但是由于lombok的实现机制,导致Code QL无法获取到Lombok自动生成的代码,所以就导致使用了Lombok的代码即使存在漏洞,也无法识别出来

# get a copy of lombok.jar
wget https://projectlombok.org/downloads/lombok.jar -O "lombok.jar"
# run "delombok" on the source files and write the generated files to a folder named "delombok"
java -jar "lombok.jar" delombok -n --onlyChanged . -d "delombok"
# remove "generated by" comments
find "delombok" -name '*.java' -exec sed '/Generated by delombok/d' -i '{}' ';'
# remove any left-over import statements
find "delombok" -name '*.java' -exec sed '/import lombok/d' -i '{}' ';'
# copy delombok'd files over the original ones
cp -r "delombok/." "./"
# remove the "delombok" folder
rm -rf "delombok"

上面的代码,实现的功能是:去掉代码里的lombok注解,并还原setter和getter方法的java代码,从而使CodeQL的Flow流能够顺利走下去, 从而检索到安全漏洞。

其实不太理解这些,如果用到直接套用

本来想了想 getter/setter能有什么漏洞,但是又一想,假设路由出的参数直接setname了,然后query查询 getname不就是洞嘛

持续工程化

因为我们的micro-service-seclab项目,是按照标准生成的微服务结构,那么我们可以使用这个ql规则去跑其他的项目,来自动化检测其它项目,从而做到自动化检测,提高安全检测效率。

创建数据库

codeql database create ~/CodeQL/databases/micro-service-seclab  --language="java"  --command="mvn clean install --file pom.xml -Dmaven.test.skip=true" --source-root="~/Code/micro-service-seclab/"

通过语句可以执行我们写好的QL文件,然后将结果输出到指定csv文件。

codeql database analyze /CodeQL/databases/micro-service-seclab /CodeQL/ql/java/ql/examples/demo --format=csv --output=/CodeQL/Result/micro-service-seclab.csv --rerun

本地测试

codeql database analyze /home/jycxk/Desktop/phpstudy/micro_service_seclab/micro_service_seclab_database /home/jycxk/Desktop/CCC/codeql-main/java/ql/examples/snippets/constructor_call.ql --format=csv --output=/home/jycxk/Desktop/JYCCC.csv --rerun

真的成功了我丢

image-20231007211221337

但是生成的呃呃只有源,后面的数字没看懂啥意思(第一列是source的位置别的母鸡)

image-20231007211439315

发现了一个贼好用的显示方法

alerts这个路径就是一个真实的调用链子!!!

image-20231007211603711

但是这样大体的漏洞形式就有了,可以细化慢慢分析以后。

CodeQL进阶

终于来到了进阶了,我配?(bushi)

上面主要完成的就是一个简单的sql注入自动化审计工作

用instanceof替代复杂查询语句问题

在上面的污点分析中就用到了,instanceof是用来优化代码结构非常好的语法糖。

image-20231007212209706

我们都知道,我们可以使用exists( )这种子查询的方式定义source和sink,但是如果source/sink特别复杂(比如我们为了规则通用,可能要适配springboot,Thrift RPC, Servlet等source),如果我们把这些都在一个子查询内完成,比如condition 1 or condition 2 or condition 3,这样一直下去,太复杂了。

instanceof给我们提供了一种机制,我们只需要定义一个abstract class,比如这个案例当中的:

/** A data flow source of remote user input. */
abstract class RemoteFlowSource extends DataFlow::Node {
  /** Gets a string that describes the type of this remote flow source. */
  abstract string getSourceType();
}

然后在isSource方法里进行instanceof,判断src是 RemoteFlowSource类型就可以了

override predicate is Source(DataFlow::Node src){
    src instanceof RemoteFlowSource
}

CodeQL和Java不太一样,只要我们的子类继承了这个RemoteFlowSource类,那么所有子类就会被调用,它所代表的source也会被加载。

我们在 RemoteFlowSource定义下面会看到非常多子类,就是这个道理,它们的结果都会被用and串联加载。

这里就可以理解为,RemoteFlowSource这个类的子类,差不多就是所有的输入传参那种,也就是source,所以我们的Source要instance这个类,不然不就特别复杂啥 get post 传参等等

递归问题

CodeQL里面的递归调用语法是:在谓词方法的后面跟*或者+,来表示调用0次以上和1次以上,0次会打印自己

在Java语言里,我们可以使用class嵌套class,多个内嵌class的时候,我们需要知道最外层的class是什么怎么办?

public class StudentService {
 
    class innerOne {
        public innerOne(){}
 
        class innerTwo {
            public innerTwo(){}
 
            public String Nihao() {
                return "Nihao";
            }
        }
        public String Hi(){
            return "hello";
        }
    }
}

我们如果要根据InnerTwo类定位到最外层的StudentService类,怎么实现?

按照非递归的写法,我们可以这样做:

import java 
from Class classes
where classes.getName().toString="innerTwo"
select classes.getEnclosingType().getEnclosingType()  //getEnclosingtype获取作用域

我们通过连续2次调用getEnclosingType方法是能够拿到最外层的StudentService的

image-20231009084221765

但是实际我们并不清楚一共有多少层嵌套

我们在调用方法后面加*(从本身开始调用)或者+(从上一级开始调用),来解决此问题

from Class classes
where classes.getName().toString()="innerTwo"
select classes.getEnclosingType+()

image-20231009084751099

image-20231009084857723

非常清晰明了,其实作用域就是此时的类在哪个类的作用域里面,也就是在哪个类的内置类里
classe当前类
classes.getEnclosingType() 当前作用域
classes.getEnclosingType() 上一层

强制类型转换问题

在Codeql的规则集中,我们会看到很多类型转换的代码

image-20231009085610384

在java中是强转类型,但是在CodeQL当中的强制类型转换,相当于一个filter过滤器,只把满足条件的数据留下(比如这个就会只留下RefType类型的数据,不符合的都去掉)

测试

列举出所以的参数,和参数类型

import java
from Parameter param
select param,param.getType()

image-20231009090252621

然后使用强制类型转换(RefType就是去掉int 等基础类型之后的数据)

import java
from Parameter param
select param,param.getType().(RefType)

image-20231009090705075

我们也可以指定过滤保留所有数值类型

import java
 
from Parameter param
select param, param.getType().(IntegralType)

疑惑

在想能不能直接用codeql语法对一个java文件直接进行查询

这里尝试用了

codeql database create --language=java java-database --command="javac StudentService.java"

但是在ql查询中会爆出错误(数据库是生成功了,但是查询出错了试了几个基本的也还是不行)

image-20231007232207098

想法又又又来了,试一下能否创建一个maven项目然后创建数据库这种

codeql database create X:\codeqldatabase --language="java" --command="mvn clean install --file pom.xml" --source-root=K:\javafile\Enjoy --overwrite

发现也会出现同样的问题

如果借用` micro_service_seclab的项目工程,然后在里面添加我们自己的方法试试可以嘛(就是套个外壳)

codeql database create X:\codeqldatabase\newnew --language="java" --command="mvn clean install --file pom.xml" --source-root=X:\codeqldatabase\micro_service_seclab --overwrite

发现真的可以,这里我首先在项目(microserviceseclab中建立了自己的java文件)

image-20231009082434555

然后构造ql语句进行查询的时候发现(是可以查询到的)

image-20231009082504313

此时想法又又又来了

我项目中导入的jar包,能否查询到呢?

把我整懵了,类名能调用到,但是方法调不到

image-20231009092647118

加油继续探索!!!

参考链接:类型 — CodeQL (github.com)

CodeQL从入门到放弃 - FreeBuf网络安全行业门户


Creative Commons License
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 Azeril 及本文源链接。