以一个小例子来说明一下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 | // log4j.java |
1 | //Log4jRCE.java |
很显然,当时这个漏洞的原因就是因为日志函数中的内容是用户可控的.
对于这个程序而言,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 | // test.sc |
很简单,这里仅仅是把上面的命令串了起来,并且多了一个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