CodeQL学习笔记(三、善用isAdditionalTaintStep和isSanitizer)

0.简介

利用codeql官方qll中的类(例如TaintTracking::Configuration)进行污点分析时,如果只定义source点和sink点,则可能产生一些误报和漏报。此时就需要使用isSanitizerisAdditionalTaintStep来消除这些误报和漏报:

  • isSanitizer :例如数据的传播路径中,可能进入了某个函数是专门用来过滤Sql注入的,但codeql并不能自动的识别这些函数会导致污点数据被过滤,从而产生误报,此时就需要我们自己定义isSanitizer谓词来帮助CodeQL识别过滤函数减少误报。
  • isAdditionalTaintStep:而对于某个第三方包中的函数调用,CodeQL也自然无法知道该函数调用是否会导致污点数据被过滤,因此CodeQL默认认为会被过滤,这样又会产生许多漏报,此时就需要我们自己定义isAdditionalTaintStep谓词来告诉CodeQL哪些调用还是污点传播。

1.isAdditionalTaintStep

要使用这个谓词,首先需要知道到底哪些情况会导致污点数据的传播断掉。根据一个github上的一个issues 《Can someone explain what isAdditionalTaintStep means?》 可以看到。

官方对这个isAdditionalTaintStep的作用解释是这样的:CodeQL进行代码分析时,只会分析用户的代码,对于第三方包的方法调用被将认为是不可预测的,因此就需要自己定义isAdditionalTaintStep。以下自己定义一个jar包来测试一下。首先定义jar包代码如下:

package test.jus4fun.xyz;
public class Test <T>{
    public  T test(T input){
        return input;
    }
}

只有一个Test类,该类仅定义一个test方法,这个方法的作用就是输入什么就返回什么。新建一个Springboot项目,假如需要分析的代码如下:

package com.example.xssdemo;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import test.jus4fun.xyz.Test;
@RestController
@RequestMapping("/")
public class XSSDemo {
    @RequestMapping("/xss")
    public String XSS(@RequestParam(value = "input") String input){
        return  input;
    }

    @RequestMapping("/xss2")
    public String XSS2(@RequestParam(value = "input") String input){
        String newInput;
        Test test = new Test();
        newInput = (String) test.test(input);
        return  input;
    }
}

很显然XSS1和XSS2方法都存在反射型XSS漏洞。使用如下ql进行查询。

/**
 * @kind path-problem
 */
import java
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
class XSSConfig extends TaintTracking::Configuration {
    XSSConfig() { this = "XSSConfig" }

    override predicate isSource(DataFlow::Node node) {
        node instanceof RemoteFlowSource
    }

    override predicate isSink(DataFlow::Node node) {
        node.asExpr().getEnclosingCallable() instanceof SpringRequestMappingMethod and 
        node.asExpr().getEnclosingStmt() instanceof ReturnStmt
    }
}
from XSSConfig config,DataFlow::PathNode source,DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select source.getNode(),source,sink,"test"

查询的结果确实只发现了第一处XSS。

这里原因就是XSS2方法中使用了一个外部jar包里的Test#test方法,导致数据流从@RequestParam(value = "input") String inputnewInput = (String) test.test(input);断开了。

此时就可以定义一个isAdditionalTaintStep将这两个node点连接起来。这里在XSSConfig里添加一个isAdditionalTaintStep,代码如下:

    override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
        exists(Call call |
            node1.asExpr().getType().hasName("String") and 
            node1.asExpr().getEnclosingCallable().hasName("XSS2") and 
            node2.asExpr()=call and 
            call.getCallee().hasName("test")
        )
    }

重新扫描可以看到确实可以扫出来第二个XSS了。

这个写的时候要注意,isAdditionalTaintStep可以将两个完全不相干的点连接起来,之前看别人的写的文章里面有一些定义的isAdditionalTaintStep是错误的,导致扫描出现非常奇怪的数据流。例如这里我们可以定义一个isAdditionalTaintStepXSS方法中的return inputXSS2方法中的newInput = (String) test.test(input);连接起来,代码如下:

    override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
        exists(Call call |
            node1.asExpr().getType().hasName("String") and
            node2.asExpr() = call and
            call.getCallee().hasName("test")
        )
    }

可以看到,如果你强行定义一个非常离谱isAdditionalTaintStep,就可能导致这种离谱的扫描结果出现。如果你想默认将所有函数调用都当成会传播污点的,可以定义isAdditionalTaintStep如下:

    override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
        exists(Call call |
            node2.asExpr()=call and 
            call.getAnArgument()=node1.asExpr()
        )
    }

除了上面这种函数调用污点传递到返回值的的情况,还有一些其他的典型写法。例如,如果在外部包中声明一个类TestTaintStep,代码如下:

package test.jus4fun.xyz;

public class TestTaintStep {
    String name;
    public void setName(String input){
        this.name = input;
    }
    public String getName(){
        return this.name;
    }
}

在Springboot项目中新建一个XSS4方法,代码如下:

    @RequestMapping("/xss4")
    public String XSS4(@RequestParam(value = "input") String input){
        String newInput,safeInput;
        TestTaintStep test = new TestTaintStep();
        test.setName(input);
        String outPut = test.getName();
        return  outPut;
    }

此时使用刚才的规则将无法扫描出这一条污点链路,主要原因还是因为外部方法调用导致。这里两个导致污点传播断开的节点为test.setName(input)test.getName()。上文中定义的isAdditionalTaintStep中,表示的情况是污点从参数input传播给Call test,而赋值语句默认传播污点,因此test传播给了newInput

而这里首先第一个部分test.setName(input)中,污点实际从input参数通过setName方法传播到了test这个实例。第二处污点String outPut = test.getName();是从实例test,传播到了getName这个Call中,然后从getName传播到了outPut变量。因此可以额外添加这两种特殊情况的step:

//针对第一种情况即参数通过call传播到实例
exists(Call call |
          node1.asExpr() = call.getAnArgument() and
          node2.asExpr() = call.getQualifier()
)


//针对第二种情况即从实例传播到call
exists(Call call |
          node1.asExpr()= call.getQualifier() and
          node2.asExpr() = call
)

总的来说主要就是三种情况,即外部包的调用中:

  1. 污点从参数传播到函数调用的返回结果中
  2. 污点从参数通过Call传播到实例中
  3. 污点从实例传播到Call的返回结果中

2.isSanitizer

Sanitizer,意为消毒剂。顾名思义这个谓词就是判断哪里可能会使污点数据变得清清白白。比如,有如下代码:

    @RequestMapping("/xss3")
    public String XSS3(@RequestParam(value = "input") String input){
        String newInput,safeInput;
        Test test = new Test();
        newInput = (String) test.test(input);
        safeInput = xssEncode(newInput);
        return  safeInput;
    }
    private static String xssEncode(String s){
        if (s == null || s.isEmpty()) {
            return s;
        }
        StringBuilder sb = new StringBuilder(s.length());
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c!='>' && c!='<') {sb.append(c);}
        }
        return sb.toString();
    }

使用之前的ql查询这个xss3也会被判断存在XSS。

实际上xssEncode方法已经对污点数据进行了过滤,使污点数据变成了清清白白的数据。因此就可以定义一个isSanitizer,代码如下:

    override predicate isSanitizer(DataFlow::Node node) { 
        exists(Call call|
            node.asExpr() = call and
            call.getCallee().hasName("xssEncode")
        ) 
        //认为在某个节点调用了xssEncode及污点数据被洗白
    }

此时将不会再产生这条误报。

参考

  • https://codeql.github.com/docs/
  • 同事小钾的亲身教学