一、CodeQL的研发背景
最早期,安全人员会通过人工审计的方式来审计项目代码,查找危险函数,并跟进危险函数的参数是否可控,如果可控,说明存在安全漏洞。
但是随着项目数量的增加,以上的纯靠人工的方式很难实现所有项目漏洞的覆盖测试。所以出现了一些辅助人工审计的工具,比如前几年比较火的rips,cobra,通过这些工具,可以把危险函数代码代码检索出来,再通过人工审计来判断是否存在安全漏洞。但是这种方式主要还是需要人来判断,工作量还是很大,并且非常依赖安全工程师的个人能力。
但是近些年出现了不少优秀的自动化代码安全审计产品,比如非常有名的Checkmarx,Fortify SCA。这些软件可以自动化的帮我们审计出安全漏洞,大大减少了人工工作量,并加快了安全审计速度。但是这些软件都是商业的,价格比较贵,一般企业可能没有这么多预算购买。
与此同时,Github为了解决其托管的海量项目的安全性问题,收购了CodeQL的创业公司,并宣布开源CodeQL的规则部分,这样全世界的安全工程师就可以贡献高效的QL审计规则给Github,帮助它解决托管项目的安全问题。对于安全工程师,也就多了一个非商业的开源代码自动化审计工具。
CodeQL支持非常多的语言,在官网有如下支持的语言和框架列表
参考链接:
https://codeql.github.com/docs/codeql-overview/supported-languages-and-frameworks/
二、CodeQL简介
CodeQL是一个可以对代码进行分析的引擎,安全人员可以用它作为挖洞的辅助或者直接进行挖掘漏洞,节省进行重复操作的精力。
在CodeQL中,代码被解析成数据,存储在数据库中。安全漏洞、错误和其他错误被建模为可以针对数据库执行的查询。我们可以运行由GitHub研究人员和社区贡献者编写的标准CodeQL查询,也可以编写自己的查询以用于自定义分析。
0x1:CodeQL安装、部署、简单使用
CodeQL本身包含两部分:
- 解析引擎:解析引擎用于解析数据库执行查询等操作。虽然不开源,但是我们可以直接在官网下载二进制文件直接使用,https://github.com/github/codeql-cli-binaries/releases
- SDK:SDK完全开源,里面包含了规则查询中涉及到的公共库文件,针对不同语言提供了很多函数和类型以方便我们编写自己的规则,https://github.com/github/codeql
CodeQL提供了命令行工具和vscode插件两个选择,vscode插件底层也是调用命令行工具,但是有图形界面并且封装了一些功能,用起来会更加方便。
注意,解析引擎和SDK要放在同级目录,CodeQL引擎会自动在上下级目录搜索库。
1、codeql-cli安装
这里我们以命令行环境下运行codeql为例,先下载codeql-cli,
项目地址 :
https://github.com/github/codeql-cli-binaries/releases
打开项目地址之后进入Releases库,下载对应操作系统的压缩包解压到任意一个文件夹。
接下来安装codeql规则库,下载开源的codeql标准库和查询库,
https://github.com/github/codeql/tree/main
保证codeql-cli(下图中codeql文件夹)和codeql SDK(下图中codeql-lib文件夹)在同一个目录下,
2、vscode-codeql安装
vscode的codeql插件,直接在插件市场安装,
3、配置环境变量
为了方便我们使用codeql-cli,我们需要将其路径放到PATH下,
同时我们最好再配置下codeql插件的可执行文件路径,打开vscode的设置,搜索codeql,修改Executable Path,
参考链接:
https://github.com/github/codeql-cli-binaries/releases https://github.com/github/codeql https://juejin.cn/post/6844903878694010893 https://ost.51cto.com/answer/5159
4、建立codeql workspace
两种方法建立codeql workspace
- 第一种就是把要审计的代码放入codeql中
- 第二种是把codeql加入要审计的代码的workspace中(较麻烦)
这里选择第一种。
下载官方给出的codeql规则库,starter,
git clone https://github.com/github/vscode-codeql-starter/
项目下载完成后,进入项目目录,确保包含需要的子模块。
git submodule update --init git submodule update --remote
在VS Code中打开starter workspace,
starter子模块中包括
- C/C++
- C#
- Java
- JavaScript
- Python
- Ruby
- GO的规则
5、创建codeql数据库
现在codeql workspace设置好了,codeql规则库库也下来好了,接下来要准备被分析的项目project了,项目project是我们做代码分析的主体。
由于CodeQL的处理对象并不是源码本身,而是中间生成的AST结构数据库,所以我们先需要把我们的项目源码转换成CodeQL能够识别的CodeDatabase。如果你之前已经针对项目project创建好了codeql数据库,在侧边栏打开CodeQL数据库,如图有四种添加数据库的方法。
当添加数据库之后,会有数据库视图,可以右击列表中的项进行数据库交互,可以利用Ctrl/Cmd+click选择多个数据库 。
这里基于codeql案例库中的java安全风险案例创建数据库,/Users/zhenghan/Projects/codeql-lib/java/ql/test/query-tests/security/CWE-020
codeql database create java-security-CWE-020 -l=java -c="javac SuspiciousRegexpRange.java" --source-root=/Users/zhenghan/Projects/codeql-lib/java/ql/test/query-tests/security/CWE-020
也可以从零新建一个maven项目,然后基于这个项目创建codeql数据库。
注意!生成数据库之前,需要先保证被分析程序可以正常跑起来。
进入到项目根目录下,执行codeql指令:
// 创建新数据库 codeql database create java-database -l=java -c="mvn clean install -file pom.xml" --source-root=/Users/zhenghan/Projects/codeql-home/hello_codeql // 更新数据库 codeql database upgrade java-database
- codeql database create java-database:利用 codeql 创建名为 java-database 的 java 数据库
- -l=java:编译语言为 java
- -c="mvn clean install -file pom.xml":利用maven命令进行编译
- --source-root=/Users/zhenghan/Projects/codeql-home/hello_codeql:设置生成codeql数据库的路径
将建好的codeql database导入vscode,
在该路径增加一个 demo.ql,即可开始编写ql查询语句,
6、导入codeql数据库,运行codeql查询
和SQL语言一样,我们执行QL查询,肯定是要先指定一个数据库才可以。
待分析源码如下,
package org.example; public class Main { public static void main(String[] args) { String a = "hello"; System.out.printf("Hello and welcome!"); if(1 == 1){ } for (int i = 1; i <= 5; i++) { System.out.println("i = " + i); } } }
将上面创建好的codeql数据库导入vdcode。
因为我们已经添加好了codeql workspace,所以在左边侧栏可以看到已经有官方内置现成的query .ql查询文件可用了,点击运行可以查看运行结果。
同时,我们也可以自行开发新的query查询文件,用于自定义漏洞挖掘。
查询程序中是否存在空代码block,
参考链接:
https://docs.github.com/zh/code-security/codeql-cli/getting-started-with-the-codeql-cli/preparing-your-code-for-codeql-analysis https://codeql.github.com/docs/codeql-for-visual-studio-code/analyzing-your-projects/ https://www.anquanke.com/post/id/266823
0x2:CodeQL开发过程总结
在使用 CodeQL 分析代码之前,需要创建一个 CodeQL 数据库,其中包含对代码运行查询所需的所有数据。 可以使用 CodeQL CLI 自行创建 CodeQL 数据库。
CodeQL 分析依赖于从代码中提取关系数据,并使用它来生成 CodeQL 数据库。 CodeQL 数据库包含有关代码库的所有重要信息,可通过执行 CodeQL 查询对其进行分析。
在生成 CodeQL 数据库之前,需要:
- 安装并设置 CodeQL CLI
- 查看要分析的代码:
- 对于分支,请查看要分析的分支的头。
- 对于拉取请求,请签出拉取请求的头部提交,或签出 GitHub 生成的拉取请求的合并提交。
- 设置代码库的环境,确保所有依赖项都可用。
- 查找代码库的生成命令(如果有)。 通常可在 CI 系统的配置文件中找到。
代码库准备就绪后,可以运行 codeql database create 以创建数据库。
0x3:CodeQL语法
CodeQL的很多语法和现在的主流高级语言有很多相似之处,但也有许多的不同。
举一个简单的例子,在CodeQL中不存在==,只有=,当一个变量定义了而没有初始化的时候,=的意思是赋值,但当其已经被赋值了之后,=的意思就变成了比较。
1、基础数据类型(Primitive types)
CodeQL 是一种静态类型的语言,因此每个变量都必须有一个声明的类型。类型是一组值。例如,int 类型是一组整数。注意,一个值可以属于这些集合中的多个,这意味着它可以有多个类型。
- 整型(int)
- 浮点型(float)
- 日期型(date)
- 字符型(stirng)
- 布尔型(boolean)
简单介绍下日期型和布尔型。
1)日期型(date)
编写一个简单的实例用于计算从今年9月1日到今天(11月2日)一共过了多久:
from date start, date end where start = "01/09/2021".toDate() and end = "02/11/2021".toDate() select start.daysTo(end)
2)布尔型(boolean)
布尔型变量用来存放布尔值,即false(假)或者 true(真)。
编写一个简单的例子来实现两个布尔之间的和关系:
from boolean a, boolean b where a = true and b = false select a.booleanAnd(b)
2、谓词(Predicates)
谓词有点类似于其他语言中的函数,但又与函数不同,谓词用于描述构成 QL 程序的逻辑关系。确切的说,谓词描述的是给定参数与元组集合的关系。
1)无结果谓词
没有结果的谓词以predicate作为开头,剩下的语法结构类似于定义函数。这种谓词只能在where语句中使用。
一个简单的例子如下:
predicate isCity(string city) { city = "Beijing" or city = "ShangHai" } from string city where city = "Beijing" and isCity(city) select city
2)结果谓词
有结果的谓词的定义类似于c/c++语言的函数定义,以返回类型替代predicate作为开头。这种谓词可以在where与select语句中使用。
一个简单的例子如下:
int addOne(int i) { result = i + 1 and i in [1 .. 10] } from int v where v = 9 select addOne(v)
3、绑定行为与绑定集
谓词所描述的集合通常不允许是无限的,换句话说,谓词只能包含有限数量的元组(It must be possible to evaluate a predicate in a finite amount of time, so the set it describes is not usually allowed to be infinite. In other words, a predicate can only contain a finite number of tuples.)
举个简单的正例和反例:
// 正例,i被限定在1到10内,或者你也可以给i赋一个确定的值如i=1 int addOne(int i) { result = i + 1 and i in [1 .. 10] } // 反例,i是无限数量值的,此时CodeQL编译器会报错: 'i' is not bound to a value int addOne(int i) { result = i + 1 and i > 0 }
1)单个绑定集
为了使上述的反例谓词能够通过编译,我们可以使用绑定集(bindingset),但是当我们去调用这个谓词时,传递的参数还是只能在有限的参数集中。
上面的反例可以修改为如下:
bindingset[i] int addOne(int i) { result = i + 1 and i > 0 } // 此时我们可以去调用这个谓词,但是需要注意传递过来的参数还是只能在有限的参数集中 from int i where i = 1 select addOne(i)
2)多个绑定集
我们同样可以添加多个绑定集,下面是一个例子:
bindingset[x] bindingset[y] predicate plusOne(int x, int y) { x + 1 = y }
这个绑定集的意思是如果x或y绑定(bound)了,那么x和y都绑定,即至少有一个参数受到约束。
如果我们想要两者都受约束,可以将例子修改一下:
bindingset[x, y] predicate plusOne(int x, int y) { x + 1 = y }
那么这个谓词就变为了一个类似于校验的函数,即x+1 == y。
4、查询(Query)
查询是CodeQL的输出。查询有两种类型,分别是
- select子句
- 查询谓词,这意味着我们可以在当前模块中定义或者从其他模块中导入
1)select子句
select子句的格式如下:
[from] /* ... variable declarations ... */ [where] /* ... logical formula ... */ select /* ... expressions ... */
其中from和where语句是可选的。我们可以在from中定义变量,在where中给变量赋值和对查询结果的过滤,最后在select中显示结果。
在select语句中我们还可以使用一些关键字:
- as关键字,后面跟随一个名字。作用相当于sql中的as,为结果列提供了一个"标签",并允许在后续的select表达式中使用它们。
- order by关键字,后面跟随一个一个结果列名。作用相当于sql中的order by,用于排序结果,并且在结果列名后可选asc(升序)或desc(降序)关键字。
一个简单的例子如下:
from int x, int y where x = 3 and y in [0 .. 2] select x, y, x * y as product, "product: " + product
2)查询谓词
查询谓词是一个非成员谓词,并在最开头使用query作为注解。它返回谓词计算结果的所有元组,下面是一个简单的示例:
query int getProduct(int x, int y) { x = 3 and y in [0 .. 2] and result = x * y }
编写查询谓词而不是select子句的好处是我们可以在代码的其他部分中调用谓词。例如,我们可以在类中的特征谓词内部调用:
query int getProduct(int x, int y) { x = 3 and y in [0 .. 2] and result = x * y } class MultipleOfThree extends int { MultipleOfThree() { this = getProduct(_, _) } } from MultipleOfThree m select m
5、类(Classes)
我们可以在CodeQL中定义自己的类型,一个方法是定义一个类。
类提供了一种简单的方法来重用和构造代码。例如,我们可以:
- 在类中定义成员谓词
- 定义子类以重写成员谓词
一个简单的例子如下:
class OneTwoThree extends int { OneTwoThree() { // characteristic predicate this = 1 or this = 2 or this = 3 } string getAString() { // member predicate result = "One, two or three: " + this.toString() } predicate isEven() { // member predicate this = 2 } }
6、Method内置方法
import java from Method method where method.hasName("main") select method.getName(), method.getDeclaringType()
- method.getName() 获取的是当前方法的名称
- method.getDeclaringType() 获取的是当前方法所属class的名称
- method.hasName() 判断是否有该方法
7、设置Source-Sink污点传播流
在代码自动化安全审计的理论当中,有一个最核心的三元组概念,就是(source,sink和sanitizer)。
- source是指漏洞污染链条的输入点。比如获取http请求的参数部分,就是非常明显的Source。
- sink是指漏洞污染链条的执行点,比如SQL注入漏洞,最终执行SQL语句的函数就是sink(这个函数可能叫query或者exeSql,或者其它)。
- sanitizer又叫净化函数,是指在整个的漏洞链条当中,如果存在一个方法阻断了整个传递链,那么这个方法就叫sanitizer。
污点追踪是CodeQL提供的一个非常强大的功能,也是进行代码审计的基础,CodeQL会分析代码得到一张有向图,参数和表达式就是里面的节点,以下面一段代码为例子。
int func(int tainted) { int x = tainted; if (someCondition) { int y = x; callFoo(y); } else { return x; } return -1; }
有了这样的图我们可以借此分析代码参数的流向来寻找漏洞,库提供了TaintTracking::Configuration这个类,我们需要继承这个类,通过覆盖实现isSource方法和isSink方法来设置起始点和终点,方法会提供dataflow::node参数,我们通过把逻辑加在节点上来设置我们想要的起点和终点,这样CodeQL分析变量的流向,如果发现了有变量从source到sink,就可能会发现潜在的漏洞,比如从getParameter到query,这可能就是一个sql注入。
CodeQL还提供了更强大的功能,isSanitizer()方法可以让我们设置净化方法,设置一个节点,当流到达这个节点后中断,比如replace()这样的过滤函数,CodeQL并不知道他的作用,我们可以中断调用了这个方法的数据流来降低误报。
同样的,CodeQL并不能识别全部的变量传递,这时候我们可以通过isAdditionalTaintStep()方法告诉污点追踪把这两个节点连起来。
1)设置source
override predicate isSource(DataFlow::Node src) {} // 通用的source入口规则 override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
2)设置Sink
override predicate isSink(DataFlow::Node sink) { } // 查找一个query()方法的调用点,并把它的第一个参数设置为sink override predicate isSink(DataFlow::Node sink) { exists(Method method, MethodAccess call | method.hasName("query") and call.getMethod() = method and sink.asExpr() = call.getArgument(0) ) }
3)FLow数据流
连通工作就是CodeQL引擎本身来完成的。我们通过使用config.hasFlowPath(source, sink)方法来判断是否连通。
from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sink where config.hasFlowPath(source, sink) select source.getNode(), source, sink, "source" //我们传递给config.hasFlowPath(source, sink)我们定义好的source和sink,系统就会自动帮我们判断是否存在漏洞了
4)使用jdbcTemplate.query方法的SQL注入
import java import semmle.code.java.dataflow.FlowSources import semmle.code.java.security.QueryInjection import DataFlow::PathGraph class VulConfig extends TaintTracking::Configuration { VulConfig() { this = "SqlinjectionConfig" } override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { exists(Method method, MethodAccess call | method.hasName("query") and call.getMethod() = method and sink.asExpr() = call.getArgument(0)) } } from VulConfig vulconfig, DataFlow::PathNode source, DataFlow::PathNode sink where vulconfig.hasFlowPath(source, sink) select source.getNode(), source, sink, "source"
参考链接:
https://longlone.top/%E5%AE%89%E5%85%A8/%E5%AE%89%E5%85%A8%E7%A0%94%E7%A9%B6/codeql/2.CodeQL%E8%AF%AD%E6%B3%95/
0x4:codeql审计代码原理图
原理:编写查询语句找出代码中的漏洞,codeql 内的编译器调用 extractor 将 java 代码编译成可查询的数据流,并以数据库的形式搭配 ql 库与编写的查询语句进行查询,得出结果并生成报告。
CodeQL的查询需要建立在一个数据库的基础之上,这个数据库是通过Extractor模块对源代码进行分析、提取后得到的。数据库建立之后,我们就可以使用CodeQL去探索源码,并发现代码中的一些已知问题。
- 对于编译型语言,CodeQL会在建立数据库时“模拟”编译的过程,在make等编译工具链调用gcc等编译器时,用相同的编译参数调用extractor模块取而代之,收集源代码的所有相关信息,如AST抽象语法树、函数变量类型、预处理器操作等等。
- 对于解释型语言,因为没有编译器的存在,CodeQL会以跟踪执行的方式获取类似的信息。
使用CodeQL CLI对代码仓库运行分析后,我们就得到了一个“快照数据库”(SnapshotDatabase),这个数据库中存储了代码仓库在特定时间点(数据库建立时)的层级表示方式,包括
- AST语法树
- CFG控制流程关系
- DFG数据流向关系
在这个数据库中,代码中的每一个要素,比如函数定义(Function)、函数调用(FunctionCall)、宏调用(MacroInvocation)都是可以被检索的实体。在这些基础上,我们再编写CodeQL语句对代码进行分析。
查询包括上图查询编译部分和执行部分,我们的查询会和库一起交给编译器编译,编译成功后会进行查询,去数据库中提取数据。
参考链接:
https://github.com/ASTTeam/CodeQL#02-codeql%E5%9F%BA%E7%A1%80 https://www.sec-in.com/article/2043 https://cloud.tencent.com/developer/article/1645870 https://www.wangan.com/p/7fy7fg448fb3b026
三、CodeQL进阶
0x1:一些开源的优秀CodeQL规则
codeql的核心在于它的规则。
- 《My CodeQL queries collection》
- https://github.com/cor0ps/codeql
- https://github.com/GeekMasher/security-queries
- https://github.com/Marcono1234/codeql-java-queries
- https://github.com/imagemlt/myQLrules
- https://github.com/advanced-security/codeql-queries
- https://github.com/jenkins-infra/jenkins-codeql
- https://github.com/ice-doom/CodeQLRule
- https://github.com/zbazztian/codeql-queries
0x2:提升挖掘效率的一些高级QL技巧
1、一些语法技巧
1)获取具体QL类型
不确定使用什么方式获取目标时,除了通过查看AST,还可以通过词getAQlClass()获取调用它实体的具体QL类型。
from Expr e, Callable c where e.getEnclosingCallable() = c select e, e.getAQlClass()
2)尽可能缩小范围
如下定义,如果项目代码量很大,则非常耗时,
override predicate isSink(DataFlow::Node sink) { sink.asExpr().getParent() instanceof ReturnStmt }
可以设置return语句在哪个函数中调用来缩小范围,乃至其Type的全限定名,
override predicate isSink(DataFlow::Node sink) { sink.asExpr().getParent() instanceof ReturnStmt and sink.asExpr().getEnclosingCallable().hasName("xxxxx") }
以某个方法的参数作为source (添加了几种过滤方式,第一个参数、该方法当前类的全限定名为xxxx),
override predicate isSource(DataFlow::Node source) { exists(Parameter p | p.getCallable().hasName("readValue") and source.asParameter() = p and source.asParameter().getPosition() = 0 and p.getCallable().getDeclaringType().hasQualifiedName("com.service.impl", "xxxxx") ) }
以某个实例的所有参数作为source(`X1 x1 = new X1(a,b)`,这里a、b作为source),过滤:调用该实例的方法名称为`Caller`,实例类型名称为`X1`,
override predicate isSource(DataFlow::Node source) { exists(ClassInstanceExpr ma | source.asExpr() = ma.getAnArgument() and ma.getTypeName().toString() = "X1" and ma.getCaller().hasName("Caller") ) }
3)调用端点路径
比如我们想知道方法A到方法G之间调用端点路径,则可以使用edges谓词,编写如下所示,如果也想找覆写的某个方法(如:接口实现类中的方法)可以将calls替换为polyCalls,
import java class StartMethod extends Method { StartMethod() { getName() = "main" } } class TargetMethod extends Method { TargetMethod() { getName() = "vulMain" } } query predicate edges(Method a, Method b) { a.calls(b) } from TargetMethod end, StartMethod entryPoint where edges+(entryPoint, end) select end, entryPoint, end, "Found a path from start to target."
待分析的源码如下,
package org.example; import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { Main error = new Main(); error.readValue("open -a Calculator"); } private void readValue(String comm) throws IOException { vulMain(comm); taintVulMain(comm); } private void taintVulMain(String comm) { Runtime rt = Runtime.getRuntime(); rt.getClass(); } private void vulMain(String comm) throws IOException { Runtime rt = Runtime.getRuntime(); rt.exec(comm); } }
生成codeql数据库,
cd /Users/zhenghan/Projects/codeql-home/hello_codeql codeql database create hello_codeql-database -l=java -c="mvn clean install -file pom.xml" --source-root=/Users/zhenghan/Projects/codeql-home/hello_codeql
4)对某接口实现
主要是通过codeql自带谓词overridesOrInstantiates判断该函数是否进行了重写。
如下,就能获取实现JSONStreamAware接口,重写的方法
class JsonInterface extends Interface{ JsonInterface(){ this.hasQualifiedName("com.alibaba.fastjson", "JSONStreamAware") } Method getJsonMethod(){ result.getDeclaringType() = this } } class CMethod extends Method{ CMethod(){ this.overridesOrInstantiates*(any(JsonInterface i).getJsonMethod()) } } from CMethod m select m, m.getDeclaringType()
2、AdditionalTaintStep
在为一些项目编写规则查询时,经常碰到数据流中断的情况,下面列出经常碰到中断的情况和解决方案。
1)setter和getter
CodeQL为减少误报很多地方都需要我们根据相应场景自己连接数据流,比如getter。
这种情况需要将调用方法的对象(通过getQualifier谓词获取限定符)和调用方法的返回值连接起来。如下操作就是从get%方法访问到它的限定符作为附加步骤重新连接起来。
class GetSetTaintStep extends TaintTracking::AdditionalTaintStep{ override predicate step(DataFlow::Node src, DataFlow::Node sink){ exists(MethodAccess ma | (ma.getMethod() instanceof GetterMethod or ma.getMethod() instanceof SetterMethod or ma.getMethod().getName().matches("get%") or ma.getMethod().getName().matches("set%")) and src.asExpr() = ma.getQualifier() and sink.asExpr() = ma ) } }
2)mapper
使用mybatis通常将接口命名为xxxxMapper或者xxxxDao这种形式,在xml配置文件中通过namespace指定其全限定名,当数据流需要经过数据库查询到这里会断开,那么需要手动将其连接起来。
3)污染源作为参数传入
如下图所示,instance作为污染源,workNode也被污染,将其传入t.setSceneKey为t对象的sceneKey属性赋值,那么这里t对象理应也是被污染的。但当我们将instance作为source,return t作为sink是获取不到路径的,
图片来自https://xz.aliyun.com/t/10852#toc-8
要解决这个问题,需要加上额外3个步骤。
- 将调用方法的所有参数作为source(图中setSceneKey方法的workNode.getSceneKey()参数),将调用方法的对象作为sink(图中的t对象),代码如下
class SrcTaintStep extends TaintTracking::AdditionalTaintStep{ override predicate step(DataFlow::Node src, DataFlow::Node sink){ exists(MethodAccess ma | (ma.getMethod() instanceof SetterMethod or ma.getMethod().getName().matches("set%")) and src.asExpr() = ma.getAnArgument() and sink.asExpr() = ma.getQualifier() ) } }
-
instance的getter
-
workNodeMapper
4)实例化
如下图,将req传入UploadFile中创建UploadFile对象,再将其传入systemService.uploadFile方法中,这种情况,uploadFile对象应该是受污染的,但是默认情况下,我们想让数据流进入systemService.uploadFile中是不行的,因为在new UploadFile就已经断开了。那么就需要将其连接起来。
代码如下,如果已经知道当前查询大概断的位置,可以缩小范围,这里将所有的都会连接起来,
class InstanceTaintStep extends TaintTracking::AdditionalTaintStep{ override predicate step(DataFlow::Node src, DataFlow::Node sink){ exists(ClassInstanceExpr cie | // cie.getTypeName().toString() = "UploadFile" src.asExpr() = cie.getAnArgument() and sink.asExpr() = cie) } }
参考链接:
https://github.com/ASTTeam/CodeQL#02-codeql%E5%9F%BA%E7%A1%80 https://xz.aliyun.com/t/10852#toc-8
四、CodeQL案例学习
这个章节,我们通过一些具体的项目,利用CodeQL挖掘复现一些已知的Nday漏洞,目的是提高对CodeQL的理解。
0x1:micro_service_seclab靶场漏洞复现
这是一个Java漏洞靶场,基于SpringBoot开发,目的是用来检测SAST工具的准确性(关注漏报和误报问题)的。可以用此靶场测试(CodeQL, CheckMarx, Fortify SCA)白盒检测工具,根据预先埋点的漏洞,与测试结果进行对比,判断在什么地方存在误报和漏报的问题。
1、靶场支持的漏洞
1)SQL注入
SQL注入这部分,会出现很多不同白盒写法导致的SQL注入。
种类 | 解释 | 伪代码 |
---|---|---|
String Source | 输入点是字符串类型 | one(@RequestParam(value = "username") String username) |
List<Long> | 输入点是Long泛型(用来测试误报) | longin(@RequestBody List<Long> user_list) |
Optional<String> | 新特性 | optionalLike(@RequestParam(value = "username") Optional<String> optinal_username) |
List<String> Source | 输入点是String泛型 | in(@RequestBody List<String> user_list) |
Object Source | 对象类型 | objectParam(@RequestBody Student user) |
MyBatis注入 | XML分离SQL检测 | myBatis(@RequestParam(value = "name") String name) |
In类型注入 | In类型注入 | 参照代码 |
Like类型 | Like类型注入 | 参照代码 |
Lombok | Lombok对注入漏洞的影响 | 参照代码 |
MyBatis注解方式注入 | MyBatis注解方式注入 | 参照代码 |
Spring Data JPA | JPA 方式 | 参照代码 |
2)RCE命令执行
种类 | 解释 | 伪代码 |
---|---|---|
processBuilder | processBuilder导致的RCE | -- |
Runtime.getRuntime().exec(args) | Runtime.getRuntime().exec(args)导致的RCE | -- |
3)FastJson反序列化漏洞
提供1.2.31版本的Fastjson供进行测试。
@RestController @RequestMapping(value = "/fastjson") public class FastJsonController { @PostMapping(value = "/create") public Teacher createActivity(@RequestBody String applyData, HttpServletRequest request, HttpServletResponse response){ Teacher teachVO = JSON.parseObject(applyData, Teacher.class); return teachVO; } }
4)SSRF漏洞
种类 | 解释 | 伪代码 |
---|---|---|
url.openConnection() | url.openConnection()引起的SSRF | 参照代码 |
Request.Get() | Request.Get()引起的SSRF | 参照代码 |
OkHttpClient | OkHttpClient引起的SSRF | 参照代码 |
DefaultHttpClient | DefaultHttpClient引起的SSRF | 参照代码 |
url.openStream() | url.openStream()引起的SSRF | 参照代码 |
5)XXE漏洞
种类 | 解释 | 伪代码 |
---|---|---|
DocumentBuilderFactory | DocumentBuilderFactory引起的SSRF | 参照代码 |
2、基本分析过程
CodeQL的核心引擎是不开源的,这个核心引擎的作用之一是帮助我们把micro-service-seclab转换成CodeQL能识别的中间层数据库。
然后我们需要编写QL查询语句来获取我们想要的数据。
由于CodeQL开源了所有的规则和规则库部分,所以我们能够做的就是编写符合我们业务逻辑的QL规则,然后使用CodeQL引擎去跑我们的规则,发现靶场的安全漏洞。
3、生成codeql数据库
- OS: Mac
- Java JDK: 1.8.0_291, vendor: Oracle Corporation
- Maven: Apache Maven 3.9.4 (dfbb324ad4a7c8fb0bf182e6d91b0ae20e3d2dd9)
cd /Users/zhenghan/Projects/codeql-home/micro_service_seclab codeql database create micro-service-seclab-database -l=java -c="mvn clean install -file pom.xml" --source-root=/Users/zhenghan/Projects/codeql-home/micro_service_seclab
参考链接:
https://github.com/l4yn3/micro_service_seclab/ https://blog.gm7.org/%E4%B8%AA%E4%BA%BA%E7%9F%A5%E8%AF%86%E5%BA%93/02.%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/03.codeql/01.codeql%E5%85%A5%E9%97%A8.html https://www.freebuf.com/articles/web/283795.html
0x2:基于CodeQL分析XXL-job
xxl-job漏洞原理及编译部署可以参阅这篇文章。
生成codeql数据库,
cd /Users/zhenghan/Projects/xxl-job_2.4.0 codeql database create xxl-job-database -l=java -c="mvn clean install -file pom.xml" --source-root=/Users/zhenghan/Projects/xxl-job_2.4.0
导入vscode,
开始构建code ql语句,
import java from MethodAccess ma, Method m where m = ma.getMethod() and m.getName().regexpMatch("equals|getResourceAsStream|getResourceAsStream|getSystemResourceAsStream|ClassPathResource|BufferedInputStream|FileInputStream|getSystemResourceAsStream|getBundle") and not m.getDeclaringType().getName().matches("SecureUtil|WhiteListedClass") select ma, "Risky method " + m.getName()
上述语句使用简单的AST匹配模式检测危险函数的方法,匹配等式判断、配置读取等函数。
通过查询结果找到读取配置的位置,即读取配置函数的定位。
继续往上追溯loadProperties的调用源头,
读取配置的属性包括addresses、accessToken、appname、addres、ip、port、logpath、logretentiondays等,如前文所述,accessToken身份绕过漏洞就是accessToken配置值和API请求的XXL-JOB-ACCESS-TOKEN一致通过的校验。
继续寻找与上述参数相关的代码,
找到上述方法的构造出POST或者GET请求方法,即可构造出漏洞。
参考链接:
https://mp.weixin.qq.com/s?__biz=Mzg4Nzk3MTg3MA==&mid=2247484600&idx=1&sn=820df60a885378f30f4ffd9a407308a8&scene=21#wechat_redirect
0x3:基于CodeQL分析Shiro
1、maven编译shiro相关问题
关于shiro源码编译及部署可以参阅这篇文章。
下载安装jdk1.7,https://www.oracle.com/java/technologies/javase/javase7-archive-downloads.html
/Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/bin/java -version
修改maven toolchains配置文件,修改maven使用jdk1.7进行编译,
cat /usr/local/Cellar/maven/3.9.4/libexec/conf/toolchains.xml
修改内容为,本地的jdk的java_home,以及对应jdk版本,注意这里可以写多个jdk版本,只要本地有:
<toolchain> <type>jdk</type> <provides> <version>1.8</version> <vendor>sun</vendor> </provides> <configuration> <jdkHome>/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/</jdkHome> </configuration> </toolchain> <toolchain> <type>jdk</type> <provides> <version>1.7</version> <vendor>sun</vendor> </provides> <configuration> <jdkHome>/Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/</jdkHome> </configuration> </toolchain>
拷贝toolchains.xml文件,
cp /usr/local/Cellar/maven/3.9.4/libexec/conf/toolchains.xml ~/.m2/toolchains.xml
接下来修改根目录下pom.xml文件中toolchains配置,修改对应版本为刚才mvn的配置文件中指定一个版本,这里必须是刚才配置的jdk版本中有的版本。
验证一下maven编译通过。
运行codeql指令生成数据库,
cd /Users/zhenghan/Projects/shiro-shiro-root-1.2.4 codeql database create shiro-samples-database -l=java -c="mvn -e clean install -Dmaven.test.skip=true -pl samples -am" --source-root=/Users/zhenghan/Projects/shiro-shiro-root-1.2.4
参考链接:
https://www.anquanke.com/post/id/256967 https://blog.csdn.net/gzt19881123/article/details/106487550 https://blog.csdn.net/qq_38376348/article/details/108962790 https://blog.csdn.net/yiqiu3812/article/details/103298980 https://stackoverflow.com/questions/40354942/maven-build-error-after-setting-toolchain-right https://blog.csdn.net/qq_20042935/article/details/106540753 https://www.anquanke.com/post/id/255721#h2-9
0x4:基于CodeQL分析Log4j
参考链接:
https://www.anquanke.com/post/id/255721 https://www.freebuf.com/articles/web/318141.html https://mp.weixin.qq.com/s/JYco8DysQNszMohH6zJEGw
0x5:基于CodeQL分析SecExample
参考链接:
https://github.com/tangxiaofeng7/SecExample
五、CodeQL和大模型技术的结合机会
参考链接:
https://mp.weixin.qq.com/s/xlUWn2oWU51NVkgB157pRw https://mp.weixin.qq.com/s/Ix2lArBzaCAJr5nyGolCwQ https://mp.weixin.qq.com/s/leLFECUaNOGbjsN_8mcXrQ https://mp.weixin.qq.com/s/Masyfq12cjaM4Zn6qxvGoA https://mp.weixin.qq.com/s/QIKvRzNlAKiqh_UMOMfDdg https://www.trendmicro.com/ja_jp/devops/23/e/chatgpt-security-vulnerabilities.html https://blog.csdn.net/ljqclqjc/article/details/133899983
标签:CodeQL,codeql,数据库,source,https,初探,com From: https://www.cnblogs.com/LittleHann/p/17859093.html