CodeQL(Mac)安装与测试(Visual Studio)简明指南

一、安装部分

1、下载对应版本的codeql:Releases · github/codeql-cli-binaries · GitHub

保存到本地codeql-cli文件夹内,cli即command-line interface。主要用来执行codeql的命令。

2、安装codeql仓库保存到本地codeql-repo文件夹,codeql仓库是包含多种语言的ql查询文件

3、总之,建立一个CodeQL的文件夹,文件夹里面新建3个文件,分别是:codeql-cli、codeql-repo、databases。

codeql-cli:里面放的就是codeql-osx64.zip的解压内容。

codeql-repo:里面放的是GitHub - github/codeql: CodeQL: the libraries and queries that power security researchers around the world, as well as code scanning in GitHub Advanced Security下载的压缩包解压内容。

databases:空文件夹,后面用来放codeql生成的数据库文件,现在暂时是空文件夹。

4、安装vscode(https://code.visualstudio.com/

5、配置codeql环境变量

bash 复制代码
open -e ~/.zshrc

    exportPATH="/Users/xxx/Desktop/tools/CodeQL/codeql-cli:$PATH"

source ~/.zshrc

6、在终端直接输入codeql -h,出现下面的信息,说明成功了

7、下载maven(https://maven.apache.org/download.cgi)并且配置路径

二、测试部分

1、下载测试靶场(GitHub - l4yn3/micro_service_seclab: Java漏洞靶场

2、生成数据库文件,出现Successfully,说明生成数据库文件成功.

bash 复制代码
codeql database create /Users/xxx/Desktop/tools/9-codeql/databases/test_database --language="java" --command="mvn clean install -DskipTests --file pom.xml" --source-root=/Users/xxx/Desktop/tools/9-codeql/micro_service_seclab

生成的数据库文件:

3、vscode配置ql数据库

安装codeql插件

将生成的数据库文件夹放到vscode上面的codeql插件上,如下:

至此,实现了codeql静态代码分析

三、codeql基本语法

CodeQL 使用一种专门的查询语言,名为 QL 。它是一种声明式的、面向对象的逻辑编程语言。别被这些名词吓到,它的核心思想其实非常直观:将代码视为数据,通过写查询来从这些数据中寻找特定模式(比如漏洞)

一个标准的 CodeQL 查询结构非常类似于 SQL,主要由三个部分组成:fromwhereselect

1. 核心结构:from, where, select

这是所有查询的基础骨架,规定了"从哪里查、满足什么条件、输出什么结果"。

bash 复制代码
from /* 1. 声明变量和类型 */
where /* 2. 定义逻辑条件和约束 */
select /* 3. 指定输出内容 */
from 子句:声明变量和类型

from 的作用是引入我们想查询的"东西"的变量,并指定它们的"类型"。你可以把它理解为:"我要从代码的数据库里,找出所有类型为 A 的东西,并给它们起个名字叫 a"。

  • 类型(Classes): CodeQL 为不同语言预定义了大量的类型。例如:

    • Method:代表一个方法或函数。

    • MethodCall:代表一次方法或函数调用。

    • IfStmt:代表一个 if 语句。

    • Expr:代表一个表达式(Expression)。

    • Parameter:代表一个方法参数。

  • 变量(Variables): 你为指定类型的实例起的名字,方便在后面引用。

示例:

复制代码
// 引入两个变量:
// - m 是一个方法 (Method)
// - call 是一次方法调用 (MethodCall)
from Method m, MethodCall call
where 子句:定义逻辑条件

where 是查询的核心,用来定义变量之间必须满足的逻辑关系。只有满足 where 中所有条件的组合,才会被 select 出来。你可以使用 and, or, not 等逻辑连接词。

  • 断言(Predicates): 这是 CodeQL 的"内置函数",用来描述代码元素之间的关系。它们通常没有返回值,而是用来"断定"一个事实是否成立。

    • call.getCallee() = m: 这断定 call 这次方法调用所调用的目标方法正是 m

    • m.hasName("execute"): 这断定方法 m 的名字是 "execute"。

    • ifstmt.getCondition(): 获取 if 语句的条件表达式。

    • param.isType("string"): 判断参数 param 的类型是否是 string

示例:

复制代码
from Method m, MethodCall call
where
  call.getCallee() = m and // 这次调用所调用的方法是 m
  m.hasName("println")    // 并且 m 的名字叫 "println"

这个 where 子句筛选出了所有调用名为 "println" 方法的调用事件。

select 子句:指定输出结果

select 用来定义最终的输出内容。你可以输出在 from 中声明的变量,也可以输出一段描述性的文字,或者两者的组合。

  • select 后面跟上变量名和你想展示的信息。

  • 你可以使用 as 关键字给输出的列起一个别名。

示例:

复制代码
from Method m, MethodCall call
where
  call.getCallee() = m and
  m.hasName("println")
select call, "这是一个对 println 方法的调用"

这个查询最终会输出所有 MethodCall(即 call 变量)的实例,并在第二列附带一段固定的描述文字。


2. Putting It All Together: 一个完整的例子

让我们结合上面的知识,写一个完整的查询。

目标 :在 Java 代码中,找到所有对 System.out.println 方法的调用。

bash 复制代码
import java // 导入 Java 相关的 CodeQL 库
from MethodCall call, Method printlnMethod // 声明变量:call 和 printlnMethod
where
  // 条件1: 我们要找的方法属于类 "PrintStream"
  printlnMethod.getDeclaringType().hasQualifiedName("java.io", "PrintStream") and
  // 条件2: 这个方法的名字叫 "println"
  printlnMethod.hasName("println") and
  // 条件3: call 这次调用实际调用的就是上面找到的方法
  call.getCallee() = printlnMethod
select call, "发现一次对 System.out.println 的调用" // 输出这次调用的实例和一段描述

3. 其他重要语法概念

类型和继承 (Classes and Inheritance)

CodeQL 的类型系统是面向对象的,支持继承。这非常强大,因为你可以查询一个更抽象的类型。

  • Expr (表达式) 是一个抽象类型。

  • MethodCall (方法调用), VariableAccess (变量访问), Literal (字面量) 等都继承自 Expr

所以,如果你 from Expr e,那么 e 就可以匹配代码中任何一种表达式。

断言 (Predicates)

断言是 CodeQL 的核心。你可以把它们看作是可重用的查询片段或自定义函数。断言分为两种:

  1. 无返回值的断言 (Predicate without result) : 用来检查一个条件是否为真,常用于 where 子句。

    复制代码
    // 定义一个断言,判断一个方法是否是构造函数
    predicate isConstructor(Method m) {
      m.hasName(m.getDeclaringType().getName())
    }
    
    from Method m
    where isConstructor(m) // 在 where 中使用
    select m
  2. 有返回值的断言 (Predicate with result): 类似于一个有返回值的函数,可以返回计算结果。

    复制代码
    // 定义一个断言,返回一个方法的所有调用点
    MethodCall getACallTo(Method m) {
      result.getCallee() = m
    }
    
    from Method dangerousMethod
    where dangerousMethod.hasName("eval")
    select getACallTo(dangerousMethod) // 在 select 中使用

    这里的 result 是一个特殊关键字,代表该断言的返回值。

量词 (any, count, sum ...)

量词可以让你对一组值进行聚合计算或检查。

  • any(): 任意一个。

  • count(): 计数。

  • sum(): 求和。

  • avg(): 平均值。

  • min()/max(): 最小值/最大值。

示例:找到被调用超过10次的方法。

复制代码
from Method m
where count(MethodCall call | call.getCallee() = m) > 10
select m, count(MethodCall call | call.getCallee() = m) as "调用次数"

这里的 | 用来分隔量词内部的变量声明和逻辑条件。

总之,我们可以根据我们自己的需求写ql脚本对代码进行审计,具体可以看看codeql官方文档学学怎么写CodeQL documentation

4、简单编写

我们现在从零开始,一步步为我自己的 Java 代码写一个简单的 QL 查询,目标是学会这个流程,理解基本结构,并明白每个文件的作用。

我们将写一个查询,用来寻找你 Java 代码中所有调用 System.out.println() 的地方 。因为几乎所有 Java 项目里都有 println,而且它只涉及到最核心的"方法调用"概念,比较简单,方便我们理解;在很多生产项目中,直接使用 System.out.println 而不是专用的日志框架,通常被认为是一种不良实践。

第一步,创建一个 Java 示例项目 如果手上没有,可以快速创建一个。比如新建一个 MyTestApp 文件夹,在里面放一个 Main.java 文件

注意:我们需要一个规范的文件夹结构来存放我们的查询。CodeQL 不是单个 .ql 文件就能运行的,它需要一个"包"的概念。

第一步:创建一个MyTestApp的文件夹,在再 MyTestApp/src/main/java/com/example路径下创建一个Main.java文件。

java 复制代码
// 文件: MyTestApp/src/main/java/com/example/Main.java
package com.example;
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, CodeQL!"); // 这是我们的目标
        String user = "admin";
        System.out.println("User is: " + user); // 这是另一个目标
    }
    public void uselessLog() {
        System.out.println("This is a useless log."); // 第三个目标
    }
}
  1. 为该项目创建 CodeQL 数据库 在 VS Code 中打开这个 MyTestApp 文件夹,然后用命令面板 (⌘+⇧+P) 运行 CodeQL: Create Database,选择 Java。

  2. 创建你的查询包 (Query Pack) 这是最关键的一步。在另外一个地方 (不要在你的 Java 项目里),创建一个新的文件夹,专门用来存放你的自定义查询。比如,我们叫它 my-java-queries

在这个 my-java-queries 文件夹里,创建两个文件:

a) qlpack.yml (包配置文件) 这个文件是查询包的"身份证",它告诉 CodeQL 这个包的名字、依赖项等信息。

复制代码
# 文件: my-java-queries/qlpack.yml
name: my-custom-java-pack  # 给你的包起个名字
version: 0.0.1
library: false
dependencies:
  codeql/java-queries: ^0.8.0 # !!!至关重要:声明依赖官方Java查询库

dependencies 是最重要的部分。它表示我们的查询要构建在官方的 java-queries 库之上,这样我们才能使用像 MethodCall 这种预定义的 Java 类型。

b) FindPrintlnCalls.ql (QL 查询文件) 这是我们马上要编写查询逻辑的地方。现在可以先创建一个空文件。

  1. 将查询包添加到 VS Code 工作区

    • 在 VS Code 中,选择 File > Add Folder to Workspace...

    • 选择你刚刚创建的 my-java-queries 文件夹。

    • 现在你的 VS Code 左侧应该能同时看到 MyTestApp (你的Java代码) 和 my-java-queries (你的QL代码) 这两个文件夹了。

至此,环境准备完毕!我们知道了 qlpack.yml 是项目定义文件,.ql 是具体的查询文件。

第二步:编写 QL 查询语句("基本结构")

现在,打开 my-java-queries/FindPrintlnCalls.ql 文件,我们来一步步写查询。

  1. 导入库和元数据

在文件最上方,我们先写一些"标准开头"。

复制代码
/**
 * @name Find System.out.println calls
 * @description Finds all calls to the method System.out.println.
 * @kind problem
 * @id java/find-println-calls
 */
import java // 导入 CodeQL 的 Java 标准库
  • /** ... */ 里的内容是元数据 (Metadata)

    • @name@description 会显示在 VS Code 的界面里,方便你识别。

    • @kind problem 告诉 VS Code 这是一个寻找"问题"的查询,结果会以告警的形式展示。

    • @id 是这个查询的唯一标识符。

  • import java导入语句,它让我们能够使用所有 CodeQL 预先定义好的、用于分析 Java 的类和断言。

  1. 编写 from-where-select 核心逻辑

接下来就是我们的 from-where-select 三部曲。

复制代码
from
  MethodCall call // 1. from: 我们要找的东西是"方法调用",我们给它起个名字叫 call
where
  // 2. where: 这个"方法调用"必须满足下面的条件
  call.getCallee().hasName("println") and // a) 它调用的方法,名字必须是 "println"
  call.getCallee().getDeclaringType().hasQualifiedName("java.io", "PrintStream") // b) 并且,声明这个方法的类的全限定名是 "java.io.PrintStream"
select
  call, "Avoid using System.out.println() in production code." // 3. select: 如果满足条件,就选中这个调用(call),并附带一句提示信息

逻辑分解:

  • from MethodCall call : MethodCall 是 CodeQL Java 库里预定义的一个类,代表了代码中所有的方法调用。我们声明一个变量 call 来代表 MethodCall 的每一个实例。

  • where: 这是筛选条件。

    • call.getCallee(): 这个断言 (predicate) 可以获取到 call 这个调用所指向的具体方法

    • .hasName("println"): 这是 Method 类的一个断言,判断方法名是否是 "println"。

    • .getDeclaringType(): 获取声明这个方法的

    • .hasQualifiedName("java.io", "PrintStream"): 这是 Type 类的一个断言,判断类的完整包名和类名。我们都知道 System.outjava.io.PrintStream 类型。

  • select call, "...": 这是输出。

    • 第一个参数 call 告诉 CodeQL 高亮显示代码中找到的方法调用。

    • 第二个参数是字符串,将作为结果的描述信息显示出来。


第三步:运行查询并查看结果

  1. 确保你当前选中的数据库是 MyTestApp 的数据库。

  2. 在你编写的 FindPrintlnCalls.ql 文件编辑器窗口中,单击右键

  3. 从菜单中选择 CodeQL: Run Query on Selected Database (和你截图中高亮的那个一样)。

  4. 等待几秒钟,VS Code 的 "CodeQL Results" 窗口就会弹出结果。

  5. 你应该能看到 3 个结果,点击其中任何一个,VS Code 都会自动跳转到 Main.java 文件里对应的 System.out.println(...) 那一行代码!

恭喜!你已经成功编写并运行了你的第一个自定义 CodeQL 查询!

学习参考:zangcc的【作者踩坑总结0错版】vscode配置codeql-MacBook(M1/M2芯片-arm).

相关推荐
qq_418247882 小时前
恒源云/autodl与pycharm远程连接
ide·人工智能·python·神经网络·机器学习·pycharm·图论
爱装代码的小瓶子3 小时前
【c++进阶】在c++11之前的编译器的努力
开发语言·c++·vscode·visualstudio·编辑器·vim
chushiyunen4 小时前
javadoc规范、idea生成javadoc等
java·ide
JPX-NO4 小时前
windows下编程IDE使用docker搭建的rust开发环境(Linux)
ide·windows·docker·rust
Colinnian5 小时前
Android Studio创建新项目时需要更改哪些地方
android·ide·android studio
Moonbeam Community6 小时前
应用爆发,DeFi先行
javascript·ide·web3·区块链·polkadot
Wcowin7 小时前
Mac Shell 环境优化指南
macos·职场和发展·蓝桥杯
lanhuazui107 小时前
VScode左边和右边辅助边框的修改
vscode
止礼7 小时前
FFmpeg8.0.1 Mac环境 CMake本地调试配置
macos·ffmpeg
杨景辉7 小时前
Vscode 使用
vscode