什么是CodeQL?CodeQL从入门到入土

admin 2024年3月1日10:37:17评论15 views字数 11290阅读37分38秒阅读模式
什么是CodeQL?CodeQL从入门到入土
什么是CodeQL?CodeQL从入门到入土

常见语法总结    不同版本CodeQL切换的注意点    继承&实现    污点追踪    java代码和xml文件关联    获取指定注解    获取指定方法    显示源码位置    查询某个类型的变量    查询类,成员参数有集合类型

为什么在现在写这文章

CodeQL传闻找到了Log4j的漏洞,这段时间上边要求,把公司系统内部常见的问题用CodeQL写检测脚本,用于系统长期检测。主要是记录这段时间的学习,以及自己总结的常用写法。

一个需求,不同人写出来的脚本可能完全不同,各种内置库用法多样,表达式和参数等各种类型之间的转换也很麻烦,本文会提供案例一步一步从分析需求一步一步走下去。

CodeQL是什么

如果你已经了解CodeQL是什么,可以直接跳过这个章节。

当谈及代码分析和漏洞检测工具时,CodeQL无疑是一款备受推崇的解决方案。作为一种革命性的语义代码查询语言,CodeQL在软件安全领域展现出了卓越的实力。

语义分析的特点是,搜索规则能在一定程度上理解代码上下文,内置的数据流判断十分强大,也可以自行补充各种例外场景。最基础的脚本需要定义,source(输入源),sink(污染点),比如检测SSRF漏洞,source为外部用户输入,sink为创建URL链接的点,可能是new URL(),也可能是其他的不在默认库里的,可自行添加。

CodeQL是由GitHub开发的一种强大的静态代码分析工具。它基于高级数据流分析技术,可深入理解代码结构、语义和行为。这意味着CodeQL能够超越传统的基于规则或模式匹配的静态分析方法,更全面地检测代码中潜在的安全风险和缺陷。

它提供了一个灵活的查询语言,使开发者能够针对不同编程语言和框架编写自定义的代码查询。通过CodeQL,开发者可以发现诸如内存泄漏、空指针引用、SQL注入等常见的漏洞,并及时修复这些问题,从而提高代码的质量和安全性。

与其他工具相比,CodeQL具有独特的优势。首先,它不仅仅是一个工具,而是一个完整的静态分析平台,提供了丰富的内置库和查询示例,帮助开发者快速上手。其次,CodeQL支持多种编程语言,包括C、C++、Java、Python等,使其适用于各种项目和团队。此外,CodeQL还具备可扩展性,可以根据具体需求进行定制和扩展,以应对不同项目的特殊需求。

CodeQL安装

CodeQL本身包含两部分解析引擎+SDK。

1、官方规则库,各种内置的ql脚本,这部分是开源的,地址:

https://github.com/github/codeql
什么是CodeQL?CodeQL从入门到入土
默认推荐将路径添加进path环境变量(多个版本另外说)。

什么是CodeQL?CodeQL从入门到入土
命令行运行codeql -v,输出结果说明安装成功。

2、数据库编译引擎,不开源,只有二进制执行文件。将源代码转换为CodeQL脚本能识别的抽象语法树。

地址:

https://github.com/github/codeql-cli-binaries

下面以java为例,利用codeql引擎对java代码进行编译,在pom文件目录执行。

codeql database create java-database --language=java --command="mvn clean install -Dmaven.test.skip=true --settings 路径/setting.xml"
源码目录会生成 java-database目录,打开看看里面有src.zip文件才算编译成功。

因此,编写一个最简单的CodeQL脚本,需要代码中指定SDK路径,编写代码后用解析引擎运行。

编译中的注意点:

1、源码目录不能有中文

2、网络问题导致编译错误,比如公司内部网络可能要加证书啥的

CodeQL基本语法&常用语法总结

基本语法

CodeQL的查询语法有点像SQL语法,基本结构如下:

import <language> /*导入对应的语言包*/from [datatype] vat /*构建数据类型表,便于理解可以认为是声明变量*/where condition[var = something] /*设置逻辑表达式*/select var /*打印结果*/
其实就三个步骤,定义数据类型,设置条件,进行查询。

谓词

在CodeQL中谓词可以说是最常见的概念,叫做 Predicates,可以理解为类似函数。PS:谓词首字母要小写。

谓词也分为有返回词和无返回词(下面有例子)

先说无返回值的,个人比较常用。

个人拙见说下无返回值谓词的作用。CodeQL的查询大概是这么个流程,定义一张大的数据表,然后根据脚本一步一步缩小数据表的内容,同时构建各种数据之间的关系,最后把满足条件的数据查询出来。那谓词的作用,就是用来缩小数据范围,也就是做限定。最简单的例子,定义一张整数类型的数据表,然后限制数据只能在1-10之间,这就是无返回值谓词的作用,用于限定数据集。

比如:

predicate isList(Parameter p){                      //声明谓词,入参为参数类型    p.getType().toString().indexOf("List<") != -1   //要求参数的类型有List字样}//所以这个谓词的作用是,限制入参是List类型class Vul extends TaintTracking::Configuration{    override predicate isSource(DataFlow::Node source){  exists(            Parameter p | isList(p)     //无返回值谓词,通常用于限制,常配合exists使用            and p = source.asParameter()//存在参数p,满足谓词isList的限制        )    }}
有返回值的谓词。

其实就是类似函数,没有 predicate关键词,多了一个特征词 result,result的值就是这个谓词的返回结果。

int test(int i){    result = i + 1    and i in [1..19]}select test(3)  //输出4
大概是这样的形式,等用到的时候再多试试。

CodeQL的写法是很灵活的。举个例子,打印1到10之间的数字。

用类的写法是这样:

import javaclass Mynum extends int{    Mynum(){        this in [1..10]    }}from int iwhere i instanceof Mynumselect i
用无返回值谓词写法是这样:

import javapredicate myNum(int i){    i in [1..10]}from int iwhere myNum(i)select i
用返回值谓词的写法是这样:

import javaint myNum(int i){    result = i    and i in [1..10]}from int iselect myNum(i)
这几种写法结果是一样的

类的定义

CodeQL里面的类不是建立一个对象,更类似建立一个数据集,类型取决于继承的类,基本可以继承任意类,包括boolean、float、int、string等基本类型。举个例子,定义Mybatis的xml文件。

class MyBatisMapperXmlFile extends XmlFile{     //继承XmlFile,定义为一个xml文件    MyBatisMapperXmlFile(){                     //定义是一个怎么样的xml文件        count(XmlElement e|e=this.getAChild())=1 and  //要求xml文件有<mappeer>节点      this.getAChild().getName()="mapper"    }}class MyBatisMapperXmlElement extends XmlElement{          //定义为一个xml元素    MyBatisMapperXmlElement(){           //定义是怎么样的xml元素        this.getFile() instanceof MyBatisMapperXmlFile//定义xml元素属于MyBatisMapperXmlFile    }}
迭代

就是类调用自己,通常用在嵌套场景。比如类成员变量里还有类,多层嵌套。

+和*的含义如下:

parentOf+(p),表示对变量p应用一次或多次谓词parentOf,等价于ancestorOf(p)。

parentOf*(p),表示对变量p应用零次或多次谓词parentOf,要么返回p的祖先,要么是变量p自身。

示例:

比如已经定义了parentOf(),用于求出某个人的父母,那么可以借助其定义childof:

Person childOf(Person p){    P = parentOf(result)}// 在此基本上可以定义祖先Person ancestorOf(Person p){    result = parentOf(p) or    result = parentOf(ancestorOf(P))}//  再定义亲属Person relativeOf(Person p){    parentOf*(result) = parentOf*(p)}
这部分有点抽象,看不懂没关系,后续有案例

常见语法总结

方法 描述
Method 方法类,获取当前项目中所有的方法,获取的是方法声明
MethodAccess(老2.13.1) 方法调用类,获取所有的方法实际调用,比如List.add(),add就是方法调用
MethodCall(老2.13.1) 方法调用类,获取所有的方法实际调用,比如List.add(),add就是方法调用
Parameter 参数类,Parameter表示获取当前项目当中所有的参数
Annotation 注解类,获取所有注解,一般用于判断特定注解或获取切面路由
Expr 表达式类,所有的有值、有类型统称为表达式,能跟各种类型互相转换
XmlFile XML文件,很多项目都用得到,比如mybatis的$占位符

那么每个类怎么去看有什么方法呢?

1、直接看https://codeql.github.com/codeql-standard-libraries/java/index.html#E,关键词搜

2、直接看依赖库源代码的说明

两者内容基本一样,但前者比较方便查询。

什么是CodeQL?CodeQL从入门到入土
什么是CodeQL?CodeQL从入门到入土
这够直观了吧。列举几个常用的,如果看文档看没看到就找份实际跑下:

MethodCall方法 描述
getAnArgument 获取方法调用的所有参数
getArgument 获取指定参数,比如getArgument(0)获取第一个参数
getEnclosingCallable 获取包含此调用方法的类或是方法
getMethod 获取方法的实现,MethodCall是方法的调用
getQualifier 获取方法调用的主体,即谁调用的

这里吐个槽,CodeQL更新还是比较快的,真的有点佛,这就导致了很多问题,用最新版的话,很多以前的参考代码运行不了,而且每次更新最要命的是,很多类直接就删了或是改名字,不是简单的增加或删除几个谓词,是直接类名就改了,我水平低还没看出改名字的必要性。

之前让ChatGPT根据需求简单写个QL脚本,根本运行不了,很多语法都变了。写这文章的时候才发现不久前写的脚本一些类(MethodAccess)在官方文档找不到了,原来是改了名字(MethodCall)。

不同版本CodeQL切换的注意点

用于生成database的codeql.exe版本和codeql规则代码版本需要保持一致

1、新建个文件夹,放最新的codeql-cli,和codeql的依赖库

2、用最新你的codeql-cli去编译数据库

3、vscode里QL插件的设置要改,要重新配置codeql-cli的路径(没改是执行不了最新语法的)

继承&实现

在CodeQL中看源码经常有种写法,定义一个抽象类,然后由其他类去继承。

而代码中用 instanceof 判断的是抽象类,实际上判断都是所有继承抽象类的其他类的限制。

比如输入流,RemoteFlowSource,这是系统内置用来获取外部输入流的

// 定义抽象类原创输入流abstract class RemoteFlowSource extends DataFlow::Node {  abstract string getSourceType();}// 继承抽象类,限制为外部输入流private class ExternalRemoteFlowSource extends RemoteFlowSource {  ExternalRemoteFlowSource() { sourceNode(this, "remote") }  override string getSourceType() { result = "external" }}// 继承抽象类,限制为RMI类型的输入private class RmiMethodParameterSource extends RemoteFlowSource {  RmiMethodParameterSource() {    exists(RemoteCallableMethod method |      method.getAParameter() = this.asParameter() and      (        this.getType() instanceof PrimitiveType or        this.getType() instanceof TypeString      )    )  }  override string getSourceType() { result = "RMI method parameter" }}
大多都直接限制所有类为外部输入流,也可以额外自己补充特定输入流。

override predite isSource(DataFlow::Node source){ source instanceof RemoteFlowSource}
污点追踪

什么是source和sink?代码自动化审计的污点分析中,最核心的三元组概念,就是(source、sink、sanitizer)。

source是指漏洞污染链条的输入点,比如外部可控变量,入参等,就是非常明显的source。

sink是指漏洞污染链条的执行点,比如SQL注入漏洞,最终执行SQL语句的函数就是sink(这个函数可能叫query或者exesql,或是其他自定义封装的)

sanitizer又叫净化函数,是指在整个漏洞链条当中,如果存在一个方法阻断了整个传播链,就叫sanitizer。

再补充一个isAdditionalTaintStep,用于强制把两个节点关联,比如节点A是污染点,节点B是节点业务特性需求点,实际上也是污染点,但CodeQL内部的数据流关联没把这两个点关联,导致节点B没被识别为污染点。这时候就能用isAdditionalTaintStep强制关联两个节点了。

class TestVul extends TaintTracking::Configuration{    TestVul(){        this = "Test"    }    predicae isTaintedString(Expr expSrc, Expr expDest){        // 想要强制关联的两个表达式的关系    }    override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2){        isTaintedString(node1.asExpr(), node2.asExpr())    }    override predicate isSource(DataFlow::Node Source){        // source.asParameter        // source.asExpr    }    override predicate isSink(DataFlow::Node sink){        // sink.asParameter        // sink.asExpr    }}
CodeQL常用句式

这部分是个人比较常用的,以后会持续补充,做个记录,便于查询。

先谈点CodeQL脚本的编写思路,四个字由内到外,比如编写一个检测 mybatis $ 占位符的脚本,而且要关联到 java 代码的数据流,不是简单地检测所有相关 mapper xml文件有无$字符。因此需要梳理相关数据流,其中比较关键的是,怎么让 xml 对应的方法和 java 代码的方法相关联。

具体分析下需求,最习惯的思路是,获取所有的数据流,判断所有相关的方法是否有进行数据库操作,再获取相关的 mapper xml 文件,一步一步正向获取,但这种思维来写CodeQL非常容易卡壳。实际上写法是由内到外,先判断所有相关 mappe xml文件,再去找对应的方法,判断数据流,就是把小的数据先搜索到再对比是否属于那个大的目标。

java代码和xml文件关联

// java代码和xml文件关联// codeql-mainjavaqlsrcsemmlecodexmlMyBatisMapperXML.qllclass MyBatisMapperSqlOperationWithProgram extends MyBatisMapperXmlElement{    MyBatisMapperSqlOperationWithProgram(){        this instanceof MybatisMapperSqlOperation // xml 文件是数据库相关的    }    // 根据xml文件中的update、detele、select、insert元素找到对应的java方法    // <select id="findStuCount" resultType="java.lang.Integer">    Method getMethod(){        result.getName() = this.getAttribute("id").getValue() and // 获取id指定的方法        result.getDeclaringType() = this.getParent().(MyBatisMapperXmlElement).getNamespaceRefType()  // getDeclaringType 获取抽象类的接口声明  // getNamespaceRefType 获取mapper文件中对应的方法  // <mapper namespace="com.ttice.icewkment.mapper.ArticleClassMapper">    }}
获取指定注解

import javaclass PostAnn extends Annotation{    PostAnn(){this.getType().hasQualifiedName("org.springbootframework.web.bind.annotation","PostMapping")    // 指定注解类包名,注解类名    }}
获取指定方法

import javaclass JalorProgramCheckMethod extends Method{    JalorProgramCheckMethod(){    this.hasQualifiedName("com.ttice.icewkment.service","ArticleService","GetList")  // 指定包名、类名、方法名    }}
显示源码位置

import javafrom Call c, Method mwhere m = c.getCallee() and m.hasName("insert")select c,c.getLocation.getFile().getRelativePath()+":"+c.getLocation().getStartLine(),c.getCaller()
查询某个类型的变量

import javafrom Variable v, PrimitiveType ptwhere pt = v.getType() and pt.hasName("int")select v,v.getLocation()
查询类,成员参数有集合类型

import javaimport semmle.code.java.Collections// 定义一个字段,字段是Collections类型class Listfield extends Field{    Listfield(){        // CollectionType 是 java.util.Collection 参数化的引用类型        this.getType() instanceof CollectionType    }}// 带有list字段的类class ClassWithList extends Class{    ClassWithList(){        // 类中有List字段,或者继承的类有list,或者字段类有list        exists(Field f | this.getAField() = f and f instanceof Listfield) or   exists(ClassWithList svo | this.getAnAncestor = svo) or   exists(Field f, ClassWithList svo | this.getAField() = f and f.getType()=svo)    }}from Class cwhere c instanceof ClassWithListselect c
上面这些组装来组装去也差不多,实在找不到的话去看看官方文档。

CodeQL实际案例

由上面可知,CodeQL是强项在于,你十分了解漏洞或是不规范代码的写法,并且中间还要有数据流向的判断,不然直接用IDEA全局搜不也挺方便,CodeQL还要编译。

所以这里就选了个非常清晰的小需求。批量操作场景下的参数个数检测,分析需求如下:

1、任意入参可当作 source,因为中间可能有字符串分割,比如通过逗号分隔成 List类型

2、最终是要进行数据库操作(增删查改),这里假定源码是springboot框架,数据库操作用的是mybatis,设定的场景是集合类型入参(有一种场景是String入参,在mybatis进行分割查询,这类型先不讨论)

3、需要分析入参跟最后数据库操作的数据流,并分析是否有@size注解

代码如下:

/** * @id java/examples/vuldemo * @name Dos * @description Dos * @kind path-problem * @problem.severity recommendation * @tags security */ import java import semmle.code.java.dataflow.FlowSources import DataFlow::PathGraph // 去除set方法 class NoSetClass extends Parameter{    NoSetClass(){        not(            this.getCallable().getReturnType().toString() = "void"            and this.getCallable().toString().matches("set%")            and this.getCallable().getNumberOfParameters() = 1         )    } } // 获取参数,要求没有注解或是有注解但注解不是Size class NoAnnOrnotAn extends Parameter{    NoAnnOrnotAn(){        exists(            Annotation an | not an.getType().hasName("Size")            and this=an.getTarget()        )        or not exists(            Annotation an|this=an.getTarget()        )    } } // 实际获取的入参,用上面的条件限制 class ListParameter extends Parameter{    ListParameter(){        this.getType() instanceof CollectionType        and this instanceof NoAnnOrnotAn        and this instanceof NoSetClass    } } class Batch_Dos extends TaintTracking::Configuration{    Batch_Dos(){        this = "FuckDos"    }    // source类型声明    override predicate isSource(DataFlow::Node source){        source.asParameter() instanceof ListParameter    }  // sink定义为dao操作,涉及集合类型    override predicate isSink(DataFlow::Node sink){        exists(MethodAccess m,Expr e | sink.asExpr()=e            and m.getAnArgument().getType() instanceof CollectionType            and m.getAChildExpr().toString().indexOf("Dao") = m.getAChildExpr().toString().length() - 3            and e = m.getAnArgument()        )    }}from Batch_Dos batch, DataFlow::PathNode source, DataFlow::PathNode sinkwhere batch.hasFlowPath(source,sink)select source.getNode(),source,sink,"source"
首发Freebuf,作者授权原文链接:https://www.freebuf.com/articles/web/391242.html

往期推荐什么是CodeQL?CodeQL从入门到入土

想考安全证书嫌太贵?CISP等认证强势登场

漏洞情报1day-蓝凌EIS智慧协同平台多处SQL注入漏洞

小程序一次一密流量解密

漏洞情报-用友 GRP-U8 多种 SQL 注入漏洞

什么是CodeQL?CodeQL从入门到入土
关于我们:

感谢各位大佬们关注-不秃头的安全,后续会坚持更新渗透漏洞思路分享、安全测试、好用工具分享以及挖挖掘SRC思路等文章,同时会组织不定期抽奖,希望能得到各位的关注与支持。

关注福利:

回复“google工具" 获取 google语法生成工具

回复“burp插件" 获取 bp常用插件打包。

回复“暴力破解字典" 获取 各种常用密码字典打包

回复“XSS利用文件" 获取 现成XSS利用文件.pdf

回复“蓝队工具箱”即可获取一款专业级应急响应的集成多种工具的工具集

知识星球

星球里有什么?

web思路及SRC赏金,攻防演练资源分享(免杀,溯源,钓鱼等),各种新鲜好用工具,poc定期更新

限时优惠立减,提前续费有优惠,好用不贵很实惠

什么是CodeQL?CodeQL从入门到入土

什么是CodeQL?CodeQL从入门到入土
交流群

关注公众号回复“加群”,QQ群可直接扫码添加,内部交流,以及发布多个岗位要求,基本上每天都会发布数量不等的岗位招聘信息。

免费帮助正规安全单位发布招聘信息,需要的可以加我联系方式什么是CodeQL?CodeQL从入门到入土

什么是CodeQL?CodeQL从入门到入土
什么是CodeQL?CodeQL从入门到入土
安全考证

需要考以下各类安全证书的可以联系我,绝对低价绝对优惠、组团更便宜,报名成功先送星球一年,CISP、PTE、PTS、DSG、IRE、IRS、NISP、PMP、CCSK、CISSP......

巨优惠想考安全证书嫌太贵?CISP等认证强势登场

什么是CodeQL?CodeQL从入门到入土
什么是CodeQL?CodeQL从入门到入土
球分享

什么是CodeQL?CodeQL从入门到入土
球在看

什么是CodeQL?CodeQL从入门到入土
球点赞

原文始发于微信公众号(不秃头的安全):什么是CodeQL?CodeQL从入门到入土

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年3月1日10:37:17
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   什么是CodeQL?CodeQL从入门到入土https://cn-sec.com/archives/2523276.html

发表评论

匿名网友 填写信息