0%

joern使用(2)

以一个小例子来说明一下joern自定义的规则用法.

安装完毕之后调用joern这个命令,会进入到一个交互式的命令行,而这个界面可以逐步的交互式分析.

joern需要分析代码,首先需要源码(废话,白盒扫的就是源码),我比较常用的是两个函数

  • importCode 字面意义,接受一个路径,传入的是源码的相对/绝对路径
  • loadCpg加载实现生成好的cpg图,需要先根据代码生成cpg图

生成cpg图的命令joern-parse <path-to-src-code> -o ./test_cpg.bin (当然如果你用的是docker版本,需要先把源码目录挂载进容器,然后分析)

-o参数可以用来修改到处的位置和名字,默认情况下是cpg.bin这个名字

待分析源码

就以log4j那个漏洞为例吧

假设我现在有个项目叫做apache-log4j-test

只是简单的演示用法,当然不能直接用于检测漏洞是否存在.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// log4j.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Scanner;

public class log4j {
// private static final Logger logger = LogManager.getLogger(log4j.class);

public static void main(String[] args) throws IOException {

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

// Reading data using readLine
String name = reader.readLine();

Log4jRCE test = new Log4jRCE();
test.recMe(name + "here we go");

}
}

1
2
3
4
5
6
7
8
9
10
11
//Log4jRCE.java
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;


public class Log4jRCE {
private static final Logger logger = LogManager.getLogger(log4j.class);
public void recMe(String text){
logger.error(text);
}
}

很显然,当时这个漏洞的原因就是因为日志函数中的内容是用户可控的.

对于这个程序而言,sink点就是logger.error(text)这个函数,而source点其实就是name这个值,当然这里其实也没有可控,我们假设他是一个外部输入.

而更为了简单起见,我们就把source点设置为“所有函数的入参”,那么这个name我们一定是可以拿到的.

交互式确定sink和soure

进入交互式命令行,首先我们导入代码,让joern为我们生成当前代码的cpg图

importCode('apache-log4j-test','test')

然后可以看看我们都能获取到一些什么结果

cpg作为一个对象,获取的是整张图的结果,我们可以用cpg.然后按tab键来获取到cpg这个对象所含有属性和方法,这些属性和方法的名字通常非常的直观易懂,而如果出现了不明白的,可以搜这个官方文档中的词典

cpg.call.toList cpg.method.toList

我们可以在最后加上toList在终端中打印出来,简写为l

常见的用法

Name Usage
Code string cpg.call.code.l
Call name cpg.call.name.l
Location cpg.call.name("foo").location.l
Calling method cpg.call.name("foo").method.l
Argument cpg.call.name("foo").argument.code.l
Filter cpg.call.filter(_.argument.code("42")).name.l

然后使用一个方法获取到了sink点 cpg.call.methodFullName("org.apache.logging.log4j.Logger.error.*").l

同样的获取到了简化的source点 cpg.method.parameter简单 粗暴的获取所有的方法参数.

然后我们可以把它定义一下,使用def这关键词,这是Scala中定义函数用的

在终端中依次执行.

def sink = cpg.call.methodFullName("org.apache.logging.log4j.Logger.error.*")
def source = cpg.method.parameter

现在我们可以在终端中借助joern的引擎帮我们进行污点分析了.

执行

sink.reachableByFlows(source).l

然后就会输出我们要的source点到sink点的经过的每一步.

这里还有一些小tips,比如可以在交互式命令行中直接让joern弹出以当前这个节点为根节点的ast,cfg等图,以可视化的形式弹出,方便我们便利

cpg.method.name("xxx").plot[xxx]

脚本化

通过交互式去逐步确定只适合我们测试或者debug的时候,当我已经完成了一个sink和source的确定之后,我希望快速的进行污点分析,joern也提供了类似的支持.

这次我们要把刚刚的规则,进行脚本化

1
2
3
4
5
6
7
// test.sc
@main def exec(outFile:String) = {
importCode("apache-log4j-test","log4j-test")
def sink = cpg.call.methodFullName("org.apache.logging.log4j.Logger.error.*")
def source = cpg.method.parameter
sink.reachableByFlows(source).toJson |> outFile
}

很简单,这里仅仅是把上面的命令串了起来,并且多了一个toJson输出,可以让我们以json文件的形式查看结果,而结果文件是由外部传入的,接下来我们来运行脚本.

joern --script test.sc --params outFile=output.json

这样我们的output.json就可以拿到json的结果了.

好了到了这,总算把这个使用joern进行sast污点分析的流程讲清楚了,虽然这个例子非常的愚蠢(因为是log4j的poc改的,懒得写了),不仅没有判断是否为真实的入参,直接暴力指定所有函数的入参都是source, 而sink点也只是针对一个具体的漏洞,并不是通用的方法,甚至这个具体的漏洞还跟log4j的组件版本有关系.

但这并不妨碍我们快速的熟悉和上手joern这个工具,有实际应用价值的sast规则因为是通用的,所以需要考虑的情况会比这个简陋的例子多很多. 虽然简单实用无需较多的Scala的相关语法知识,但深入使用显然是需要一些基本语法的. joern的官方文档还算不错,改写的都写了,虽然不是很详细和零散,但总体来说不错,如果对Scala语法大致有了解之后,并且了解joern的DSL的大致实现之后再去使用一些高级功能,可能会更加得心应手.

docker的情况

因为docker还涉及到了文件路径的挂载,所以命令看起来会比较繁琐,思路与上述完全一致

挂载需要分析的源码目录至 /test

1
docker run  -it --rm -v  $PWD/apache-log4j-test:/test joern /bin/bash /opt/joern/joern-cli/joern

即进入joern的交互式界面

然后导入代码importCode("/test","test")然后就能进行分析

  • joern-parse

joern-parse -o 指定输出文件目录(放到挂载目录中,方便本地分析)

docker run -it --rm -v $PWD/apache-log4j-test:/test joern /bin/bash /opt/joern/joern-cli/bin/joern-parse /test -o /test/log4j_cpg.bin

对于一个项目生成一次cpg之后,即可在交互式中loadCpg("cpg名称")来导入图,进行进一步分析

  • 执行脚本

同上 执行脚本,需要注意importCode或者loadCpg需要指定为正确的路径

docker run -it --rm -v $PWD/apache-log4j-test:/test joern /bin/bash /opt/joern/joern-cli/joern --script /test/test.sc --params outFile=/test/output.json