笔者在阅读了CodeQL的官方文档,熟悉相关语法后,对CodeQL中的Java进行了一番简单的研究,本文分享这一过程的一些收获。
规则的封装
class
我们可以封装一系列的谓词方法,这方便我们组织并编写一系列的规则。递归调用
extends
关键字申明其继承来自另外一个或多个class,我们通过使用父类或父类的谓词方法,codeql就会帮我们自动递归使用其子类。同样的,我们也可以使用abstract修饰class中封装的谓词方法,这样一来该谓词方法在被使用时本身不具备谓词判断逻辑,但CodeQL会递归使用其子类的谓词。我们可根据需要选择是否使用abstract修饰词,另外子类也可被abstract修饰,更多的注解可参考官方docs/ql-language-reference/annotations/。
ModelCsv
通过ModelCsv
,我们可以简化代码定义sink、source、flow step,并通过kind
来使用它,简单说一下对应的三种ModelCsv
(可以在ExtenalFlow.qll
找到它们)。
SourceModelCsv
:定义source
,列值为namespace(); type; subtypes; name; signature; ext; output; kind
。SinkModelCsv
:定义sink
,列值为namespace; type; subtypes; name; signature; ext; input; kind
。SummaryModelCsv
:定义FlowStep
,列值为namespace; type; subtypes; name; signature; ext; input; output; kind
。namespace:包名
type:类名
subtypes:true或false,表示是否关注子类
name:方法名或构造函数名
signature(方法参数描述符,无需描述返回值)
input、output:表示流跟踪过程中上游的input(source)与下游的output(sink),值可为Argument、Argument[n1..n2]、Argument[n]、Argument[-1]、ReturnValue,其中Argument[-1]表示方法调用者qualifier,可能是变量、类名、this等
ext:值为空或Annotated,仅在library-test中出现过,暂不深究
kind:本条row的标签,同个ModelCsv中可定义多个kind值不同的row,kind值也可以是已经在其他ModelCsv中定义过的
这里的class使用了kink值为ognl-injection
的SinkModelCsv
row
流处理
讲讲笔者理解到的CodeQL的流处理。
流自动传递逻辑
另外,当流走到我们规则定义的source点后,如果source点为Parameter类型,CodeQL还会将该Parameter所在方法作为节点,尝试继续找source,而如果source点不为Parameter类型,则不会继续走流。
flow step
下面的代码的污点为 ognl;Node;false;setValue;;;Argument[-1];ognl-injection
,该污点跟踪到expr
变量后就会断开。
OgnlContext context = (OgnlContext) Ognl.createDefaultContext(null);
Node expr = Ognl.compileExpression(context, null, str);
expr.getValue(context, null);
而我们需要将数据流跟踪到str
字符串的传递上,这种跨类型(从Node到String)的情况,CodeQL默认是不支持的,所以需要添加新的流步骤。理解并编写一个flow setp的关键点就是,你需要假设n1为数据流的上方,n2为数据流的下方,而下方靠近sink,然后我们需要什么就假设n1、n2在我们想要的数据流节点位置,最后想办法通过一系列谓词说明n1、n2之间的逻辑关系即可。
如下的流步骤中,可以理解为:污点规则命中后,随后跟踪到数据类型与Node expr
类型一致的Ognl.compileExpression(context, null, str)
,该节点为MethodAccess
,然后我们假设Node n2
为该方法调用,即有 n2.asExpr() = ma
,n1
为方法的字符串参数,即有n1.asExpr() = ma.getArgument(index)
。
浅尝DataFlow
针对接口情况, CodeQL会关联Interface与其implements,这点似乎是codeql的基础机制,可能其构建节点关系的时候就会进行这一步处理(主要笔者没有在lib代码中找到这部分的逻辑,所以暂时做此猜想),后面sink到souce的流跟踪过程会触发这种节点上的逻辑关联。
下图中,sinkMethod为污点,数据流随后跟踪到 myClass.getS()
,并发现IMyClass
为Interface时,关联节时会关联到implements该接口的类MyClass
的getS
。
CodeQL甚至还会自动分析getS
与setS
两个方法间是否存在数据关联,从而决定是否进行流跳转。
本例中,CodeQL实际通过DataFlowPrivate.qll
中的storeStep
谓词判断这一情况。该谓词的逻辑方法逻辑为,上游节点node1,即this.s = s
中右边的赋值变量,下游节点node2,即myClass.get()
的myClass
,也是this.s = s
中的this
。
但目前这种功能无法针对lib库的class进行分析,如本例中的接口类与接口实现类在jar包中的话,我们就需要另外编写flow step。但相信我们在真正深入了解CodeQL后,这一问题能够解决。
规则案例
丰富传播规则
CodeQL提供了一系列通用的核心基础 source、flow,但我们在使用过程中,也需要不断完善其中的不足。
下图案例中,存在污点方法sinkMethod
,其source来源于request
,由于CodeQL关于java.io.InputStream
、ByteArrayOutputStream
的flow step不完善,所以在进行污点跟踪时会断掉(真实漏洞案例)。
此时,我们自己需要完善这块的规则,如下图中,我们在ExternalFlow.qll
中添加两条流中继规则,分别是针对InputStream#read
、ByteArrayOutputStream#write(byte[])
isSanitizer
中不能过滤int
类型(可能不能一股脑过滤这些数据类型):非典型污点
看规则学Web
@RequestMapping
用于标记一个控制器的入口方法,CodeQL判断一个注解是否为@RequestMapping
时使用的是下图中的SpringRequestMappingAnnotationType
。当笔者看到91行
这行代码时,就有点懵:难道一个注解,被@RequestMapping
注解后,也是有效的@RequestMapping
注解了?Spring框架这也认?MyMapping
,添加了@RequestMapping
与@Retention
注解,启动SrpingBoot后,确实可以访问/Mymapping应用。o.s.w.s.h.AbstractHandlerMethodMapping#detectHandlerMethods
,debug看了看,底层代码会通过 o.s.c.a.TypeMappedAnnotations#scan
方法确定是否存在@RequestMapping
注解。但后面想了想,其实就是为了兼容RequestMapping
、PostMapping
、PutMapping
等情况而已。ModelAttribute
、InitBinder
、RequestMapping
这类注解,或者该方法所在类的父类的对应方法使用了这类注解,则该方法都是有效的入口方法。BController
继承了AController
,我们给出的路径/B
与/MethodB
会进行对应的覆盖,如果不写注解Mapping,则沿用AController的路径,但URI路径不能完全一致,不能出现/A/MethodA。Spring的这种机制是为了让用户方便管理自己的Controller。o.s.c.a.AnnotationsScanner#processMethodHierarchy
,确实是有这么一回事。结语
银河实验室
往期回顾
技术
技术
技术
技术
长按识别二维码关注我们
微信号:PSRC_Team
球分享
球点赞
球在看
原文始发于微信公众号(平安集团安全应急响应中心):CodeQL进阶知识(Java)