CodeQL代码审计工具简介

codeql是一个将代码转化成类似数据库的形式,并基于该database进行分析的引擎。在 CodeQL 中,代码被视为数据。安全漏洞、Bug 和其他错误被建模为可针对从代码中提取的数据库执行的查询。

CodeQL 的整体思路是把源代码转化成一个可查询的数据库,通过 Extractor 模块对源代码工程进行关键信息分析提取,构成一个关系型数据库。CodeQL 的数据库并没有使用现有的数据库技术,而是一套基于文件的自己的实现。对于编译型语言,Extractor 会监控编译过程,编译器每处理一个源代码文件,它都会收集源代码的相关信息,如:语法信息(AST 抽象语法树)、语意信息(名称绑定、类型信息、运算操作等),控制流、数据流等,同时也会复制一份源代码文件。而对于解释性语言,Extractor 则直接分析源代码,得到类似的相关信息。关键信息提取完成后,所有分析所需的数据都会导入一个文件夹,这个就是 CodeQL database, 其中包括了源代码文件、关系数据、语言相关的 database schema(schema 定义了数据之间的相互关系)。

CodeQL 分析

CodeQL 分析包括三个步骤:

  1. 创建CodeQL 数据库
  2. 运行CodeQL 查询语句分析数据库
  3. 解析查询结果

CodeQL下载

CodeQL引擎:CodeQL ,linux下设置codeql环境变量(export PATH=/path/to/codeql:$PATH)
CodeQL查询包:CodeQL-cli,用于分析查询的库文件和基础元数据等。

数据库创建

在线创建

通过lgtm在线网站编译github或Bitbucket 项目.

  1. java-sec-code项目为例。打开网站,填入项目地址,仅支持github或Bitbucket 项目。

打开lgtm在线网站

2. 点击Follow,在下面列表会产生该项目,再点击Enable PR code reviews,进入如下界面,鼠标移到Query this project,点击下载数据库即可。

下载数据库

命令行界面

下载好对应的项目,执行codeql database create [编译后的数据库生成路径] --language=java --command="mvn clean install --file pom.xml" --source-root="./apps/WebGoat"

  • -l,--language=<lang> 创建数据库的语言
  • -s,--source-root=<dir> 项目的源代码路径,默认为当前路径
  • -j,--threads=<num> 生成数据库使用的线程数,默认为1
  • -M,--ram=<MB> 使用多大内存执行生成命令
  • -c.--command=<command> 构建项目使用的命令,如maven项目使用mvn clean package
  • --overwrite 覆盖之前生成的数据库,如果不加上该命令,若存在同名数据库,则报错。

数据库解析

VSCode解析

1. 下载CodeQL插件
附件中搜索CodeQL。

下载CodeQL插件

2. 导入database
在所下载的CodeQL插件中,点击Choose Database from Archive,选择上述生成的database导入。

导入database

3. 打开CodeQL
使用Vscode打开第一步下载的CodeQL-cli.

vscode打开文件

4. 查询
这里以java为例子,打开官方给我们编译好的demo,java->ql->src->Security->CWE,任意选择一个ql文件,在文件内容处右键CodeQL:Run Query进行查询。

Run Query

命令行分析

codeql database analyze \
  --ram=8000 --threads=4 \
  --format="sarif-latest" \
  --output="./results/sql.json" \
  ./databases/java-sec-database \
  ./CWE-089/SqlTainted.ql
  • --ram=8000 & --threads=4 分析使用的内存和线程数
  • --format=<format> (必填) 输出结果的格式,如sarif-latest可以输出json格式,csv输出csv格式
  • --output="./results/sql.json" (必填) 输出结果路径
  • ./databases/java-sec-database (必填) 分析的数据库路径
  • ./CWE-089/SqlTainted.ql (必填) 使用的查询语句

json格式结果示例:

{"$schema" : "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","version" : "2.1.0","runs" : [ {"tool" : {"driver" : {"name" : "CodeQL","organization" : "GitHub","semanticVersion" : "2.7.3","rules" : [ {"id" : "java/sql-injection",//标签@id"name" : "java/sql-injection",//标签@id"shortDescription" : {"text" : "Query built from user-controlled sources"//标签@name},"fullDescription" : {"text" : "Building a SQL or Java Persistence query from user-controlled sources is vulnerable to insertion of malicious code by the user."//标签@description},"defaultConfiguration" : {"enabled" : true,"level" : "error"},"properties" : {"tags" : [ "security", "external/cwe/cwe-089", "external/cwe/cwe-564" ],//标签@tags"description" : "Building a SQL or Java Persistence query from user-controlled sources is vulnerable to insertion of\n              malicious code by the user.",//标签@description"id" : "java/sql-injection",//标签@id"kind" : "path-problem",//标签@kind"name" : "Query built from user-controlled sources",//标签@name"precision" : "high",//标签@precision"problem.severity" : "error",//标签@problem.severity"security-severity" : "8.8"//标签@security-severity}} ]},"extensions" : [ {"name" : "legacy-upgrades","semanticVersion" : "0.0.0","locations" : [ {"uri" : "file:///Desktop/codeql/legacy-upgrades/","description" : {"text" : "The QL pack root directory."}}, {"uri" : "file:///Desktop/codeql/legacy-upgrades/qlpack.yml","description" : {"text" : "The QL pack definition file."}} ]}, {"name" : "codeql/java-queries","semanticVersion" : "0.0.4","locations" : [ {"uri" : "file:///Documents/tools/by_zii/pythonProject/resources/codeql/cli/codeql-codeql-cli-v2.7.3/java/ql/src/","description" : {"text" : "The QL pack root directory."}}, {"uri" : "file:///Documents/tools/by_zii/pythonProject/resources/codeql/cli/codeql-codeql-cli-v2.7.3/java/ql/src/qlpack.yml","description" : {"text" : "The QL pack definition file."}} ]} ]},"artifacts" : [ {"location" : {"uri" : "src/main/java/org/joychou/controller/SQLI.java","uriBaseId" : "%SRCROOT%","index" : 0}} ],"results" : [ {"ruleId" : "java/sql-injection",//@id"ruleIndex" : 0,"rule" : {"id" : "java/sql-injection","index" : 0},"message" : {"text" : "Query might include code from [this user input](1)." //查询结果中第一个string描述},"locations" : [ {//漏洞触发位置"physicalLocation" : {"artifactLocation" : {"uri" : "src/main/java/org/joychou/controller/SQLI.java",//存在漏洞文件的地址"uriBaseId" : "%SRCROOT%","index" : 0},"region" : {"startLine" : 66,//所在文件的行数"startColumn" : 51,//漏洞的参数所在行开始字符index"endColumn" : 54//漏洞的参数所在行结束字符index}}} ],"partialFingerprints" : {"primaryLocationLineHash" : "57a447cc075b363b:1","primaryLocationStartColumnFingerprint" : "38"},"codeFlows" : [ {//代码执行流"threadFlows" : [ {"locations" : [ {"location" : {//执行流中源信息"physicalLocation" : {"artifactLocation" : {"uri" : "src/main/java/org/joychou/controller/SQLI.java","uriBaseId" : "%SRCROOT%","index" : 0},"region" : {"startLine" : 51,"startColumn" : 33,"endColumn" : 74}},"message" : {"text" : "username : String"}}}, {"location" : {//执行流中sink信息"physicalLocation" : {"artifactLocation" : {"uri" : "src/main/java/org/joychou/controller/SQLI.java","uriBaseId" : "%SRCROOT%","index" : 0},"region" : {"startLine" : 66,"startColumn" : 51,"endColumn" : 54}},"message" : {"text" : "sql"}}} ]} ]} ],"relatedLocations" : [ {"id" : 1,"physicalLocation" : {"artifactLocation" : {"uri" : "src/main/java/org/joychou/controller/SQLI.java","uriBaseId" : "%SRCROOT%","index" : 0},"region" : {"startLine" : 51,"startColumn" : 33,"endColumn" : 74}},"message" : {"text" : "this user input"}} ]} ],"columnKind" : "utf16CodeUnits","properties" : {"semmle.formatSpecifier" : "sarif-latest"}} ]}

CodeQL语法

基础语法(仅包含基础非污点跟踪)

  • MethodAccess :调用的方法表达式
  • Method : class中的方法
  • getCaller :方法调用所在的方法,换句话说是哪个方法体中调用的
  • getCallee: 被调用的方法
  • hasName(param):限定名称
  • getDeclaringType():获取此变量的类型
  • hasQualifiedName("package","class"):限定类名

编写基础查询语句

以查找Contextlookup方法的查询为例。

/**
 * @name JNDI lookup with user-controlled name
 * @description Performing a JNDI lookup with a user-controlled name can lead to the download of an untrusted
 * object and to execution of arbitrary code.
 * @kind problem
 * @problem.severity error
 * @precision high
 * @id java/jndi-injection
 * @tags security
 * 
 */
import java 
from MethodAccess methodAccess
where methodAccess.getMethod().hasName("lookup") and methodAccess.getMethod().getDeclaringType().hasQualifiedName("javax.naming", "Context")
select methodAccess,methodAccess.getCaller().getName()

格式解释
注释

在使用命令行解析或vscode的时候,必须包含以下两个注释:(参考query-metadata-style-guide
@kind
定义查询的类型,可以定义的类型为以下3个

  1. problem:必须两列或其倍列,结构:element,string
  2. path-problem,必须包含四列,结构:element, source, sink, string,后续查询必须以element,string结构
  3. metric

@id
定义该查询的唯一标识,应以语言为开头,支持的语言如下:

  • C and C++: cpp
  • C#: cs
  • Go: go
  • Java: java
  • JavaScript and TypeScript: js
  • Python: py

后面建议接上一个该查询所针对的问题,比如

  • @id cs/command-line-injection
  • @id java/string-concatenation-in-loop

语句

Query partPurposeDetails
import java导入标准Code每个查询语言以一个或多个import开始
from MethodAccess methodAcces定义查询的变量,格式如下:<type> <variable name>这里定义MethodAccess 类型的变量
where methodAccess.getMethod().hasName("lookup") and methodAccess.getMethod().getDeclaringType().hasQualifiedName("javax.naming", "Context")定义变量的条件定义methodAcces的方法名为lookup,调用该方法的类为javax.naming.Context
select methodAccess,methodAccess.getCaller().getName()定义匹配后结果数据返回方法执行参数以及方法名

执行结果

CodeQL执行结果


参考:https://zhuanlan.zhihu.com/p/479431942