codeql从入门到入土

codeql从入门到入土

参考文章

白盒代码审计工具——CodeQL安装与使用教程【Linux+Windows】

CodeQL从入门到放弃

CodeQL 入门

漏洞发现:代码分析引擎 CodeQ

白盒代码审计:关于VsCode中CodeQL的应用

使用CodeQL分析CTF题目

CodeQL基础

生成jdk数据库

编译OpenJDK8并生成CodeQL数据库

CodeQL构建单cc依赖数据库以及构建jdk数据库

新的API

github官方指南:用于编写自定义 CodeQL 查询的新数据流 API

前言:这一部分的学习看了挺多文章的,后来才发现CodeQL 在 2023~2024 年对整个数据流/污点跟踪 API 做了彻底的重构,而网上的大部分文章都是2023年及以前的,用的都是旧版API。所以在这里写了新的污点追踪模板和新旧迁移对比。

整个学习过程东补一块西补一块的,感觉写的有点乱

安装

引擎

引擎下载地址:code引擎下载地址

下载的目录方式系统变量中

在命令行输入codeql,得到下图表示引擎安装成功

1752136452258-939bd87f-9c7f-4f39-b271-9c600be776e6.png

SDK

下载命令:git clone https://github.com/Semmle/ql

VSCode开发插件安装

在扩展中搜索codeql安装,并把之前的引擎安装路径填入codeql的可执行文件路径

生成数据库

codeql只可以对解析引擎编译生成的数据库进行扫描,所以需要先生成目标数据库。

本地生成

codeql database create 数据库名 --language=cpp --source-root=源码路径 --command="编译命令"

#因为我这里没有安装gpg,所以这里直接跳过gpg签名
codeql database create DatabaseName --language=java --command="mvn clean install -Dgpg.skip=true" --source-root=D:\xxl-job --overwrite


  • 主要参数
    • --command 参数如果不指定,会使用默认的编译命令和参数
    • --source-root 源码路径
    • --overwrite 表示create的目标database对已有的database做覆盖
    • --language要根据具体项目的编译语言指定

对应关系如下是:

Language Identity
C/C++ cpp
C# csharp
Go go
Java java
javascript/Typescript javascript
Python python

例子:

#下载源代码
git clone https://gitee.com/xuxueli0323/xxl-job 

#创建源码数据库:
codeql database create DatabaseName --language=java --command="mvn clean install -Dgpg.skip=true" --source-root=D:\xxl-job 
codeql database create DatabaseName --language=java --command="mvn clean install -Dgpg.skip=true" --source-root=D:\text\subtitles-view-main

成功生成

1752140502972-776b3838-e548-4c77-bb9d-accfcbddaeca.png

进行项目漏洞扫描

VSCode

  1. 添加之前的数据库Database
  2. 用VSCode打开扫描规则(CodeQL libraries and queries)(即解压后的sdk文件夹)
  3. 根据具体语言进行规则扫描,ql****后缀的文件是规则扫描文件

右键对应文件夹选择

1752167622434-593f98f9-1594-4bd8-b638-a02f7c57eba7.jpeg

比如这里运行了ql\java\ql\src\Security\CWE\CWE-020,共检测到 72 处可能存在此问题的代码位置。

1752167289476-86c68af1-7440-41af-abc1-a389045502da.png

命令行方法

#创建数据库
codeql database create databaseName --source-root=D:/xxl-job --language=java

#更新数据库
codeql database upgrade databaseName

#执行扫描规则

codeql database analyze Databasepath codeql-repo/java --format=csv --output=result.csv
#eg:codeql database analyze D:\xxl-job\databaseName D:\codeql\ql\java\ql\src\Security\CWE --format=csv --output=result.csv

codeql database analyze D:\text\java-sec-code-master\mybatis_3_db D:\codeql\ql\java\ql\src\Security\CWE --format=csv --output=result.csv
  • Databasepath:数据库路径
  • codeql-repo/java :java扫描规则
  • format:结果输出格式
  • output:结果文件输出路径

相对于vscode的优点在于**可以一次性扫很多条规则****,而vscode最多只支持20条**

vscode的优点在于可视化界面

基本语法

QL语法

基本语法

from [datatype] var
where condition(var = something)
select var

类库

把我们的靶场项目通过codeql引擎转换成codeql可以识别的database的过程中,

codeql引擎会把我们的java代码转换成可以识别的AST数据库

类似于这种格式

1752227169506-b60cd6b5-8ab6-4c63-9507-318121c50812.png

我们的类库实际上就是上面AST的对应关系

  • AST(抽象语法树):是源代码的结构化树形表示,保留了程序逻辑的核心结构,是编译器、静态分析工具(如codeql)和 代码转换工具的核心数据结构。

谓词

和SQL一样,where部分的查询条件如果过长,会显得很乱。CodeQL提供一种机制可以让你把很长的查询语句封装成函数。

这个函数,就叫谓词

RefType中常用的谓词

https://codeql.github.com/codeql-standard-libraries/java/semmle/code/java/Type.qll/type.Type$RefType.html

getACallable() 获取所有可以调用方法(其中包括构造方法)
getAMember() 获取所有成员,其中包括调用方法,字段和内部类这些
getAField() 获取所有字段
getAMethod() 获取所有方法
getASupertype() 获取父类
getAnAncestor() 获取所有的父类相当于递归的getASupertype*()

Callable常用谓词:\ https://codeql.github.com/codeql-standard-libraries/java/semmle/code/java/Member.qll/type.Member$Callable.html

polyCalls(Callable target) 一个Callable 是否调用了另外的Callable,这里面包含了类似虚函数的调用
hasName(name) 可以对方法名进行限制

Call 常用谓词

https://codeql.github.com/codeql-standard-libraries/java/semmle/code/java/Expr.qll/type.Expr$Call.html

getCallee() 返回函数声明的位置
getCaller() 返回调用这个函数的函数位置

设置Source和Sink

  • 什么是source和sink

在代码自动化安全审计的理论当中,有一个最核心的三元组概念,就是**(source,sink和sanitizer)**

- **source**是指漏洞污染链条的**输入点**。比如获取http请求的参数部分,就是非常明显的Source。
- **sink**是指漏洞污染链条的**执行点**,比如SQL注入漏洞,最终执行SQL语句的函数就是sink(这个函数可能叫query或者exeSql,或者其它)。
- **sanitizer**又叫**净化函数**,是指在整个的漏洞链条当中,如果存在一个方法阻断了整个传递链,那么这个方法就叫sanitizer。

只有当source和sink同时存在,并且从source到sink的链路是通的,才表示当前漏洞是存在的

1752228487669-634e046f-f7d3-40d1-9bc7-13f3eb879151.jpeg

  • 设置Source

在CodeQL中我们通过此方法设置Source

predicate isSink(DataFlow::Node sink) { sinkNode(sink, "log-injection") }

在本例中我们设置Source的代码为:

predicate isSource(DataFlow::Node src) {
     src instanceof RemoteFlowSource
   }

RemoteFlowSource表示能由远程用户操控的数据流源。

这是SDK自带的规则,里面包含了大多常用的Source入口。我们使用的SpringBoot也包含在其中,我们可以直接使用。

  • 设置Sink

在本案例中,我们的sink应该为query方法(Method)的调用(MethodAccess),所以我们设置Sink为

predicate isSink(DataFlow::Node sink) {
     exists(Method method, MethodCall call |
       method.hasName("query") and
       call.getMethod() = method and
       sink.asExpr() = call.getArgument(0)
     )
   }

这里代码的主要作用是查找一个query()方法的调用点,并把它的第一个参数设置为sink

Flow数据流

设置好Source和Sink,就相当于搞定了首尾,但是首尾是否能够连通才能决定是否存在漏洞

由于CodeQL 的数据流 API 在 2023 年后发生了重大变更,旧的 DataFlow::PathGraph** 已被废弃,现在需要使用模块化方法**导入路径图

比如以下代码:


module MyFlow = DataFlow::Global<MyConfig>;
import MyFlow::PathGraph

from MyFlow::PathNode source, MyFlow::PathNode sink
where MyFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Data flow found"
  • 在MyConfig自定义配置模块,要先实例化之后再导入查询
  • from子句:从路径图中选择source和sink
  • where子句:限定数据流路径
  • select子句:输出结果

语句优化

这里用到的靶场是 micro_service_seclab

生成数据库

codeql database create DATABASE --language="java" --source-root=D:\text\micro_service_seclab-main --command="mvn clean package -Dmaven.test.skip=true"

初步成果

这里踩了一个坑,刚开始DataFlow::PathGraph和MethodAccess都会报错,后来发现是**DataFlow::PathGraph和MethodAccess都已经被弃用**,下面是正确的写法。

/**
 * @id   java/examples/vuldemo
 * @name Sql-Injection
 * @description Sql-Injection
 * @kind path-problem
 * @problem.severity warning
 */

 import java
 import semmle.code.java.dataflow.DataFlow
 import semmle.code.java.dataflow.TaintTracking
 import semmle.code.java.dataflow.FlowSources
 import semmle.code.java.security.QueryInjection

 module SqlInjectionConfig implements DataFlow::ConfigSig {
   predicate isSource(DataFlow::Node src) {
     src instanceof RemoteFlowSource
   }
 
   predicate isSink(DataFlow::Node sink) {
     exists(Method method, MethodCall call |
       method.hasName("query") and
       call.getMethod() = method and
       sink.asExpr() = call.getArgument(0)
     )
   }
 }
 

 module SqlFlow = TaintTracking::Global<SqlInjectionConfig>;
 

 import SqlFlow::PathGraph

 from SqlFlow::PathNode source, SqlFlow::PathNode sink
 where SqlFlow::flowPath(source, sink)
 select sink.getNode(), source, sink,
        "Potential SQL-injection: user input flows to SQL query."

相关解析

根据官方更新https://github.blog/changelog/2023-08-13-new-dataflow-api-for-writing-custom-codeql-queries/,现在需要使用模块化方法导入路径图,更新后的查询应如下:


module MyFlow = TaintTracking::Global<MyConfig>;
import MyFlow::PathGraph

from MyFlow::PathNode source, MyFlow::PathNode sink
where MyFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Data flow found"

在这里MyConfig配置模块就是需要我们自己写的source和sink规则,在此例的**配置模块**SqlInjectionConfig中:

  • 定义了一个实现 DataFlow::ConfigSig 的模块
  • isSource 谓词: 将远程用户输入定义为污染源
  • isSink 谓词: 查找名为 “query” 的方法调用,并将其第一个参数定义为汇聚点

module SqlFlow = TaintTracking::Global<SqlInjectionConfig>,使用上面的配置实例化一个全局污点追踪模块(TaintTracking::Global)

查询主体:

  • from子句:从路径图中选择source和sink
  • where子句:限定数据流路径
  • select子句:输出结果

运行结果:

1753622870456-3a06ebfb-66e6-4693-b30c-163e9cccc799.png

误报解决

1753625505159-b2352068-7bed-4286-97ee-be82d058a994.png

这里的List类型由于强类型约束,而纯数字无法构成可执行的SQL片段,所以这里属于误报

这里形成误报的根本原因其实是:

SqlInjectionConfig 里没有定义任何 isBarrier 谓词,导致 任何 通过 RemoteFlowSource 获得的值,即使已经经过安全的类型转换(例如把用户输入 String 解析成 long 后放进 List<Long>),仍被当成“受污染的”数据一路向下游传播,最终在 query(...) 处被判定为漏洞,从而把完全无害的 List<Long> 参数也当作注入风险,产生误报。

1753606058201-b7dc8b6b-3205-4117-8fd0-4e04ff0aaee0.png

注意:现在的isSanitizer已经被isBarrie替代

模板


  predicate isBarrier(DataFlow::Node sanitizer) {  // 4: 'isBarrier' replaces 'isSanitizer'
    sanitizer.asExpr() instanceof LiveLiteral or
    sanitizer.getType() instanceof PrimitiveType or
    sanitizer.getType() instanceof BoxedType or
    sanitizer.getType() instanceof NumberType or
    sanitizer.getType() instanceof TypeType
  }

所以这里应该是

 predicate isBarrier(DataFlow::Node sanitizer) {  // 4: 'isBarrier' replaces 'isSanitizer'
      sanitizer.getType() instanceof PrimitiveType or//基本类型
      sanitizer.getType() instanceof BoxedType or//基本类型的对象类型
      sanitizer.getType() instanceof NumberType or//数字类型
      exists(ParameterizedType pt| sanitizer.getType() = pt and pt.getTypeArgument(0) instanceof NumberType )  
     //判断是否是泛型类型并且其泛型参数是数字类型   
 }

这里isBarrier 检测到 userIds 的类型是 List<Long>,并且泛型参数 LongNumberType,因此阻止数据流继续传播,避免误报。

漏报解决

可能是版本差异,这个漏洞点我是能扫描到的,但是还是说一下漏报解决办法

1753635252876-449beda0-c665-476c-827d-625fceda62ff.png

关于漏报可以给它直接续上

1753606148507-8f4d25de-e080-48b8-907c-0aa619c02ad6.png

现在统一用isAdditionalFlowStep方法,以下是完整代码。

/**
 * @id   java/examples/vuldemo
 * @name Sql-Injection
 * @description Sql-Injection
 * @kind path-problem
 * @problem.severity warning
 */

import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.QueryInjection

predicate isTaintedString(Expr expSrc, Expr expDest) {
    exists(Method method, MethodCall call, MethodCall call1|
           expSrc = call1.getArgument(0) and expDest = call and call.getMethod() = method
    and method.hasName("get") and method.getDeclaringType().toString() = "Optional<String>"
    and call1.getArgument(0).getType().toString() = "Optional<String>"
    )
}

module SqlInjectionConfig implements DataFlow::ConfigSig {
    predicate isSource(DataFlow::Node src) {
        src instanceof RemoteFlowSource
    }

    predicate isSink(DataFlow::Node sink) {
        exists(Method method, MethodCall call |
               method.hasName("query") and
               call.getMethod() = method and
        sink.asExpr() = call.getArgument(0)
        )
    }

    predicate isBarrier(DataFlow::Node sanitizer) {  // 4: 'isBarrier' replaces 'isSanitizer'
        sanitizer.getType() instanceof PrimitiveType or
        sanitizer.getType() instanceof BoxedType or
        sanitizer.getType() instanceof NumberType or
        exists(ParameterizedType pt| sanitizer.getType() = pt and pt.getTypeArgument(0) instanceof NumberType )  
    }
    predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
        isTaintedString(node1.asExpr(), node2.asExpr())
    }

}


module SqlFlow = TaintTracking::Global<SqlInjectionConfig>;


import SqlFlow::PathGraph

from SqlFlow::PathNode source, SqlFlow::PathNode sink
where SqlFlow::flowPath(source, sink)
select sink.getNode(), source, sink,
"Potential SQL-injection: user input flows to SQL query."

Lombok 插件漏报(在新版本中已经被解决)

在旧版的codeql中:

Lombok 的注解在编译期才生成 getter / setter / builder 等代码,而 CodeQL 在 源代码→字节码之间的 AST 阶段 进行分析,导致生成的代码不在数据库里,数据流被截断,于是把本应触发的漏洞判定为“不可达”,出现漏报。

而这个Lombok 插件漏报问题在新版本的codeql中其实已经被解决。

使用 CodeQL 进行代码扫描改进了对使用 Project Lombok 的 Java 代码库的支持:

https://github.blog/changelog/2023-09-01-code-scanning-with-codeql-improves-support-for-java-codebases-that-use-project-lombok/

从 2024-05-07 官方公告起,GitHub 已经在 CodeQL 引擎 2.17.0+ / 2.17.1+(CLI 与 GitHub.com Actions 同步更新) 中内置了对 Lombok 的“零配置”支持。

也就是说:

  • GitHub.com 的默认 CodeQL workflow(github/codeql-action 3.x)现在已经不需要任何 delombok 步骤,Lombok 生成的 getter / builder / @Slf4j 等代码会被 直接解析,数据流能够正常穿过这些节点。
  • CLI 用户只要把 CLI 升到 2.17.1 及以上(2024-05-07 发布),再用常规的codeql database create … --language=java即可,无需在 lombok-maven-plugin 里加 delombok 目标,也无需手动 java -jar lombok.jar delombok。

Java污点追踪模板总结

https://github.blog/changelog/2023-08-13-new-dataflow-api-for-writing-custom-codeql-queries/

github官方指南:用于编写自定义 CodeQL 查询的新数据流 API

module SensitiveLoggerConfig implements DataFlow::ConfigSig {  // 1: module always implements DataFlow::ConfigSig or DataFlow::StateConfigSig
  predicate isSource(DataFlow::Node source) { source.asExpr() instanceof CredentialExpr } // 3: no need to specify 'override'
  predicate isSink(DataFlow::Node sink) { sinkNode(sink, "log-injection") }

  predicate isBarrier(DataFlow::Node sanitizer) {  // 4: 'isBarrier' replaces 'isSanitizer'
    sanitizer.asExpr() instanceof LiveLiteral or
    sanitizer.getType() instanceof PrimitiveType or
    sanitizer.getType() instanceof BoxedType or
    sanitizer.getType() instanceof NumberType or
    sanitizer.getType() instanceof TypeType
  }

}

module SensitiveLoggerFlow = TaintTracking::Global<SensitiveLoggerConfig>; // 2: TaintTracking selected 

import SensitiveLoggerFlow::PathGraph  // 7: the PathGraph specific to the module you are using

from SensitiveLoggerFlow::PathNode source, SensitiveLoggerFlow::PathNode sink  // 8 & 9: using the module directly
where SensitiveLoggerFlow::flowPath(source, sink)  // 9: using the flowPath from the module 
select sink.getNode(), source, sink, "This $@ is written to a log file.", source.getNode(),
  "potentially sensitive information"

以下是新旧迁移对照表

旧 API(将被弃用) 新 API(2024 以后推荐)
class MyConfig extends TaintTracking::Configuration module MyConfig implements DataFlow::ConfigSig
TaintTracking::StateConfigSig
this = "MyConfig"
特征字符串
不再需要
override predicate isSource/isSink/isSanitizer... 直接写 predicate isSource/isSink/isBarrier...
isSanitizer
/ isSanitizerIn
统一为 isBarrier
isAdditionalTaintStep 统一为 isAdditionalFlowStep
TaintTracking::Global<MyConfig> myFlow
实例化
直接用 TaintTracking::Global<MyConfig>
config.hasFlowPath(source, sink) MyFlow::flowPath(source, sink)

使用CodeQL分析CTF题目

对类进行限制

在CodeQL中,RefType就包含了我们在Java里面使用到的Class,Interface的声明,比如我们现在需要查询一个类名为XStreamHandler的类,但是我们不确定他是Class还是Interface,我们就可以通过 RefType定义变量后进行查询

#自定义规则扫描文件
import java

from RefType c
where c.hasName("XStreamHandler")
select c

得到查询XStreamHandler类

1752226055399-c26fd0b9-4d6e-41b2-888a-99d5bb47ae02.png

结合

RefType中的常见谓词可以构造以下查询获取XStreamHandler的fromObject

import java

from RefType c,Callable cf
where 
  c.hasName("XStreamHandler") and
  cf.hasName("fromObject")and
  cf=c.getACallable()
select c,cf

自定义扫描规则

以下部分是codeql通过**(匹配**Context.lookup()**函数检测JNDI注入安全漏洞)**的自定义扫描规则示例

以下插播一下JNDI注入的概念

  • 基本概念
    • JNDI 是 Java 平台的一项技术,用于在应用程序中查找和访问命名和目录服务,通过 JNDI,应用程序可以以统一的方式访问各种资源。
    • 这些资源在命名服务中都有对应的名称,应用程序使用javax.naming.Context.lookup() 方法,根据名称来获取对应的资源对象。
  • 注入原理:
    • lookup() 方法的参数可控,且应用程序没有对输入进行严格的验证和过滤时,攻击者就可以构造恶意的 JNDI 名称,让应用程序去连接恶意的命名服务,从而执行恶意代码。

靶场:mybatis-3的下载链接:https://github.com/mybatis/mybatis-3

##因为我这里git-commit-id-maven-plugin 插件报错,所以我这里强制跳过了
##生成数据库
codeql database create mybatis_3_db --language=java --command="mvn clean compile -Dgpg.skip=true -Dmaven.gitcommitid.skip=true" --source-root=D:\mybatis-3-master --overwrite
codeql database create mybatis_3_db --language=java --command="mvn clean compile -Dgpg.skip=true -Dmaven.gitcommitid.skip=true" --source-root=D:\text\java-sec-code-master --overwrite

自定义扫描规则:

/**
* @kind path-problem
*/

import java

class LookupMethod extends Call {
    LookupMethod() {
        this.getCallee().getDeclaringType().getASupertype*().hasQualifiedName("javax.naming", "Context") and
        this.getCallee().hasName("lookup")
    }
}


class GetterCallable extends Callable {
    GetterCallable() {
        getName().matches("get%") and
        hasNoParameters() and
        getName().length() > 3
        or
        getName().matches("set%") and
        getNumberOfParameters() = 1
    }
}


query predicate edges(Callable a, Callable b) { a.polyCalls(b) }

from LookupMethod endcall, GetterCallable entryPoint, Callable endCallAble
where
endcall.getCallee() = endCallAble and
edges+(entryPoint, endCallAble)
select endcall.getCaller(), entryPoint, endcall.getCaller(), "Geter jndi"

代码解析:

  • @kind path-problem:声明这是一个路径查询,会显示源点到问题点的调用链。
  • 定义 LookupMethod 类(匹配所有 Context.lookup()方法调用)
    • 继承自Call,表示方法调用
    • 谓词逻辑
      • getCallee():获取被调用的方法
      • getDeclaringType().getASupertype():获取声明该方法的类及其所有父类/接口*
      • hasQualifiedName(“javax.naming”, “Context”):限定为 javax.naming.Context 类或其子类
      • hasName(“lookup”):方法名为 “lookup”
  • 定义 GetterCallable 类(识别所有 getter 和 setter 方法)
    • 谓词逻辑
      • 第一部分匹配 getter:

matches(“get%”):以 “get” 开头

hasNoParameters():无参数

length() > 3:方法名长度 > 3(排除 “get” 本身)

    * **第二部分匹配 setter:**

matches(“set%”):以 “set” 开头

getNumberOfParameters()=1:只有一个参数

  • 定义 edges 谓词(建立方法间的调用关系图)
    • 定义调用关系边:
      • a.polyCalls(b):表示 a 可能调用 b(考虑多态)
  • 主查询部分
    • 数据流:
      • endcall:终点(LookupMethod 调用)
      • entryPoint:起点(GetterCallable)
      • endCallAble:被调用的lookup方法
    • 条件:
      • endcall.getCallee() = endCallAble:确保 endCallAble 是被调用的 lookup 方法
      • edges+(entryPoint, endCallAble):存在从 entryPoint 到 endCallAble 的一条或多条调用路径
    • 输出
      • 显示从调用者到 lookup 的路径
      • 标记为 “Geter jndi”
  • 总结:这个查询检测的是:从任意 getter/setter 方法出发,通过一系列调用最终到达 Context.lookup() 的调用链。典型场景是检测通过 JavaBean 属性访问触发的潜在危险 JNDI 查询

执行结果:

1752258063077-8a42eef8-56a9-4a1b-ae5d-21eee7a584e7.png

在ctf中的运用

构建jdk数据库

为了解决特定环境下的依赖关系或者要深入分析Java底层机制时,且没有找到对应的jdk数据库时,就需要手动构建jdk数据库。

由于JDK的构建对系统环境有严格要求,不同系统上可能表现不同,而Docker 容器能提供与宿主环境隔离的标准化环境,确保构建过程可复现。所以这里一般需要通过docker构建

在此例中:

操作系统:ubuntu 16.04

Target_OpenJDK:OpenJDK 8u73

#拉取一个ubuntu 16.04镜像
docker pull ubuntu:16.04
# 使用镜像并且进入容器
docker run -it ubuntu:16.04 /bin/bash
#更新软件源
apt-get update
# 下载必要构建工具
apt install -y build-essential gdb  cmake openjdk-8-jdk cpio file unzip zip wget
apt install -y --no-install-recommends  libfontconfig1-dev libfreetype6-dev  libcups2-dev libx11-dev  libxext-dev  libxrender-dev  libxrandr-dev  libxtst-dev  libxt-dev libasound2-dev  libffi-dev  autoconf
# 降级 Make
wget http://ftp.gnu.org/gnu/make/make-3.81.tar.gz 
  && tar -zxvf make-3.81.tar.gz 
  && cd /make-3.81 
  && bash configure -prefix=/usr 
  && make 
  && make install
  

安装完后如图:

1753454451258-b12c71d3-f36d-4917-9b7d-567503f86a5d.png

#获取jdk源码
apt-get update && apt-get install -y git
git clone https://github.com/openjdk/jdk8u.git
#进入jdk源码目录
cd jdk8u
#运行configure进行编译配置和编译环境检查
bash configure --with-debug-level=fastdebug  \
   --with-jvm-variants=server  \
   --with-boot-jdk=/usr/lib/jvm/java-1.8.0-openjdk-amd64 \
   --with-target-bits=64 \
   --enable-debug-symbols  \
   --with-native-debug-symbols=internal

检查通过后如图

1753456088835-ccd6e157-3331-4eb4-ac3e-fa830b6af2c6.png

执行make命令开始编译OpenJDK

make images JOBS=4

编译完成后如图

1753456484015-54667b50-aa6e-49ea-ad07-58b4caa59dba.png

安装codeql

wget https://github.com/github/codeql-cli-binaries/releases/download/v2.14.6/codeql-linux64.zip
unzip codeql-linux64.zip -d /oopt
# 添加到PATH
export PATH=$PATH:/oopt/codeql

构建数据库

cd /jdk8u

codeql database create openjdk8u-db \
  --language=java \
  --command="make images JOBS=4" \
  --source-root=.

1753514972356-2a053add-01eb-41db-963a-60c13c9a124b.png

可算是跑出来了

ezchain题目

hfctf2022的ezchain题目考察的是:hessian反序列化链构造等。

题目环境:

https://github.com/waderwu/My-CTF-Challenges/tree/master/hfctf-2022/ezchain

这里说是要先构建jdk的数据库再扫的,但是构建出来之后怎么搞我还要再试试。

github上的项目漏洞扫描

项目一

试了几个就扫出来,https://github.com/slabiak/AppointmentScheduler这个项目,逐一分析一下这个项目的几个漏洞点

漏洞点

log injection(日志注入)

1753371056829-3e8d7dfe-46f9-455a-992c-05eeb410992b.png

属于log injection(日志注入),这里的漏洞是会把token直接记录到日志中,获取token后可能会被用于未授权操作。

csrf保护被禁用

1753372200779-d107887e-10db-48e9-88b9-5da7e49c9e83.png

临时文件本地信息泄露漏洞

1753373552113-5271c25c-8037-4a5f-a9fb-a5d0d81f8db2.png

@Component
public class PdfGeneratorUtil {

    private final SpringTemplateEngine templateEngine;
    private final String baseUrl;

    public PdfGeneratorUtil(SpringTemplateEngine templateEngine, @Value("${base.url}") String baseUrl) {
        this.templateEngine = templateEngine;
        this.baseUrl = baseUrl;
    }

    public File generatePdfFromInvoice(Invoice invoice) {

        Context ctx = new Context();
        ctx.setVariable("invoice", invoice);
        String processedHtml = templateEngine.process("email/pdf/invoice", ctx);

        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocumentFromString(processedHtml, baseUrl);
        renderer.layout();

        String fileName = UUID.randomUUID().toString();
        FileOutputStream os = null;
        try {
            final File outputFile = File.createTempFile(fileName, ".pdf");
            os = new FileOutputStream(outputFile);
            renderer.createPDF(os, false);
            renderer.finishPDF();
            return outputFile;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close();

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

}

此代码主要用于根据发票信息生成PDF文件

问题本质:

  • 使用File.createTempFile()创建的临时PDF文件默认权限为644(rw-r–r–)
  • 这意味着同一系统上的其他用户都可以读取这些PDF文件
  • 发票PDF可能包含敏感的商业或个人数据
SensitiveInfoLog(敏感信息日志记录)

一样的将敏感信息插入日志文件

项目二

https://github.com/dustin/java-memcached-client

CWE-327(不安全的加密算法)

1753423205902-3382fc71-e857-457c-8af6-44fae23c4e58.png

使用了 MD5 算法 (KETAMA_HASH 部分),而MD5已被证明存在严重碰撞漏洞

  • 碰撞攻击 (Collision Attacks):MD5 最严重的缺陷是它容易受到碰撞攻击。这意味着可以找到两个不同的输入数据,它们生成相同的 MD5 散列值。
  • 不可逆性受损:虽然散列函数理论上是不可逆的(无法从散列值推导出原始数据),但由于碰撞攻击的存在,MD5 在某些场景下作为安全校验或密码存储已经不再可靠。

项目三

https://github.com/xuxueli/xxl-mq

CWE-352(CSRF)

1753426515285-254796d2-e22f-4994-931a-ec009618fddc.png

漏洞点

AccessTokenController 中有几个方法执行了对系统状态进行修改的操作,例如:

  • @RequestMapping(“/insert”):新增 AccessToken
  • @RequestMapping(“/delete”):删除 AccessToken
  • @RequestMapping(“/update”):更新 AccessToken

这些方法是用来修改系统数据的,这是CSRF攻击的常见目标,但是没有任何的CSRF Token的生成和验证机制。

在一个典型的安全防御中,服务器在渲染页面时生成一个随机且唯一的CSRF Token,将其嵌入到表单的隐藏字段或页面的JavaScript变量中。当用户提交表单或发送AJAX请求时,这个Token会随请求一起发送到服务器。服务器会在处理请求前验证这个Token是否匹配,如何Token不匹配或缺失,则拒绝请求,但是此代码中缺少这一步骤。

这里系统显然依赖于**用户的会话 Cookie **来进行认证。这意味着只要用户处于登录状态,他们的浏览器就会自动将这些 Cookie 附加到发送到域名的任何请求中,包括由恶意网站触发的请求。

师傅这是我这周的笔记,主要就是对codeql再研究了一下,

对codeql新旧版本的API和污点追踪模板做了对比,然后github上找了几个项目扫描了一下,找到了几个漏洞点,但是还没有进一步的利用

更新: 2025-08-03 08:32:49
原文: https://www.yuque.com/cindahy/tdi6fy/xw8bsrkppwgif834

LICENSED UNDER CC BY-NC-SA 4.0
评论