前言
受形式所迫,ai被污染的比较强,因此,很多东西,最佳的选择是去官方文档进行学习,搭配实战训练,新旧版本的差别较大,这里,尽量多用Codeql去分析代码,提炼习惯,纯英文,逼自己一句一句的好好读,好好理解
java+python(以后会更)
此前学习过一遍Codeql的基本语法,但是不太满意,感觉没有完全学明白,且时常在新旧之间搞混,所以,为了提高熟练度,以及给后来者一些稍稍正确的方向,还是记录一下这个过程
简单的例子
注意
官方文档这个MethodAccess错了()之后已经重写为MethodCall了,望周知
学习
嗯,打算好好推推进度,从一个例子开始学习
1 | import java |
这是一个合法的查询语句
在初始导入语句之后,这个简单的查询包含三个部分,功能类似于SQL查询中的FROM、WHERE和SELECT部分,鄙人虽然sql没咋学,基本的还是能理解的MethodCall: 某个方法被调用的位置 ,可以通过它获得调用的目标方法 getMethod() ,表达式 getQualifier() ,参数 getArgument(i) ,位置getLocation()
在where处即进行进一步过滤where ma.getMethod().hasName("equals") and ma.getArgument(0).(StringLiteral).getValue()=""
可以看到,这里的过滤规则有两条
1 | 首先,该调用的方法名是"equals";其次,该方法的第一个参数是字符串字面量,并且值为空 |
这里用的hasName是一个谓词:强调条件是否成立;而getName是返回一个字符值,需区分,明显用谓词更简洁方便
select ma,"This comparison to empty string is in efficient,use isEmpty() instead."
这里就是输出了,ma是 代码位置(AST 节点) ,后续是描述
这里我们知道,Codeql的结果必须是一个代码位置,一个相关信息描述,这是codeql的规范
如上,我们看到了,整个功能是在优化代码,找到.equals("")的冗余代码,转为isEmpty()
我们知道,我们现在只是 为了检测 _String.equals(“”) _
1 | public class TestJava { |
这里这种也会被检测,如何提高查询精度呢?
添加一句即可ma.getQualifier().getType() instanceof TypeString_ __ _
继续介绍这几个新家伙
- getQualifier(),我们知道ma是调用,getMethod()是方法,而这个,就是调用该方法的对象
- getType(),返回Java编译器看到的该对象的类型,String or Object
- TypeString, Java 中的 java.lang.String 类型,是RefType( Any reference type(类、接口);Type是 any type )的一个特化
上述,我们就完成了一个简短的ql查询,还是很简单的~学会这些,我们已经可以开始辅助我们的审计了
一些数据类型的查询方式
跟进第二讲,我们学习一下,在Codeql中,有哪些查询方式
算基础,帮助我们更精准的找到想要的结果
首先,关于Java库里,有五大类
1 | 用于表示“语言结构”的类 |
简单跟着学习一些,熟悉熟悉
Type
1 | PrimitiveType represents a primitive type, that is, one of boolean, byte, char, double, float, int, long, short; QL also classifies void and <nulltype> (the type of the null literal) as primitive types. |
查询int类型的值
1 | import java |
- PrimitiveType为基本类型,可以对一个变量getType获取
如果想要查询一些特定类呢?
这里,我们可以借助Codeql的查询
| CodeQL 类 | 含义 |
|---|---|
| TopLevelClass | Java 文件里最外层的 class |
| NestedClass | 在别的类里声明的 class |
| LocalClass | 在方法或构造函数内部定义的 class |
| AnonymousClass | new 接口() 或 new 类() { … } 这种匿名类 |
我们也可以通过下述方法查询单例类
CodeQL 提供了如:
TypeStringTypeObjectTypeCloneableTypeSerializableTypeSystemTypeClassTypeRuntime
这些类是 单例(singleton),每个都对应 Java 标准库中一个具体的类。
例如:
TypeString↔java.lang.StringTypeObject↔java.lang.Object
你不能 new 它们,但可以用它们做类型判断。
看几个例子
1 | import java |
- getCompilationUnit()这个方法,很好用,就是,找到一个元素所在的源码文件
我们知道一个类的public类和文件名一致,如果不一致,即
1 | 找所有不是与文件名相同的顶层类 |
下一个嘞
1 | import java |
所有“嵌套类(NestedClass)”中,那些“直接继承 Object” 的类。
- instanceof,判断前一个对象是否符合后面的类型
- getASupertype(),拿到这个类extends的那个父类
我们学Java免不了泛型(Generics)吧,关于它的查询语句又是怎么样的呢?
Map这个泛型接口的声明 =GenericInterface- 或者如果是 class,就叫
GenericClass - 二者合起来叫 GenericType=> 泛型类型的声明
- TypeVariable(类型参数):代表泛型参数本身
- ParameterizedType:参数化后的实例类型
假设一个p,是 ParameterizedType ,我们可以.getSourceDeclaration() 会返回它的 GenericType
现在有一个例子
1 | import java |
这里就是查找 all parameterized instances of java.util.Map
- hasQualifiedName():属于谓词, 检查某个类、方法、包的完全限定名是否匹配指定名称,此匹配更加唯一,限定路径名
- getSourceDeclaration():将一个参数实参拿到它的泛型并返回
也会有些关于泛型的更为复杂的情况,其实学到这里思考,真的有必要学吗?不过我是强迫症,跟了跟了
1 | class StringToNumMap<N extends Number> implements Map<String, N> { |
Java 中可以限制某个类型参数的上界
N是一个 TypeVariable(类型变量)- 它有一个 上界(upper bound)Number
1 | import java |
查找所有边界为 Number 的类型参数
到此为止,后面也是不太理解,且与实际不太符合
AST
这里介绍Codeql里操作AST的基本方法
Expr(表达式)与 Stmt(语句)
这是 CodeQL 里“程序执行单位”的两个基类:
- Expr →
x + y,foo(),o.equals(""), 字面量等 - Stmt →
if,while,return,{}, 赋值语句等
它们构成了整个 Java 程序的 AST。
子节点
Expr.getAChildExpr()
Stmt.getAChild()
父节点
Expr.getParent()
Stmt.getParent()
例子
1 | from Expr e |
这里会返回return的子表达式
1 | from Stmt s |
熟悉这么一些方法就行,也不是很会用到,实际感受还得在实战上
调用图(call graph)
后续我们想打污点追踪就必须学会方法调用关系图,介绍一个元素
- Callable,表示所有可以被调用的东西,方法 & 构造函数
- Call,表示所有调用的表达式,如obj.foo();new Foo();this(),super()
- getCallee(),根据调用找到被调用的方法
例子
1 | from Call c, Method m |
这里c是一个实际的调用,通过getCallee找到它的方法并匹配指定方法名,即可找到全部该方法被调用的位置
反之,我们可以查找一个方法是否存在调用情况
1 | from Callable c |
- getAReference():根据Callable找到有哪些调用指向它
MethodAccess&Call
可以注意一下二者区别,Call表示一切调用,不局限于方法调用,可以是构造,方法引用,或者super调用等等
而前者仅限于方法调用object.equals("")这种
所有 MethodAccess 都是 Call,但不是所有 Call 都是 MethodAccess。
初阶污点追踪
也是一口气学到这里了~
希望可以做一个漂亮的今日份收尾
学习Codeql我们肯定希望把它学通,而不是仅作为一个特定查询工具,学会污点,写好查询,刷的一下出了一个洞,这才是最有意思的地方
https://github.blog/changelog/2023-08-14-new-dataflow-api-for-writing-custom-codeql-queries/
有更新,未免不必要的麻烦,我们在这个上面进行一些基础的修改,如下
- 以前,数据流分析配置是通过继承类
DataFlow::Configuration或TaintTracking::Configuration来实现的,现在用 模块(例如DataFlow::ConfigSig或TaintTracking::Global)来定义配置。 - 移除了
override关键字:新的 API 不需要像旧版那样使用override。 isSanitizer和isSanitizerIn被isBarrier和isBarrierIn替代:这两个函数现在适用于数据流和污点跟踪配置。- 不再需要“虚拟字符串值的特征谓词”。
原文如下
1 | To convert the query to the new API: |
旧版的查询语句是如何呢?
1 | class SensitiveLoggerConfiguration extends TaintTracking::Configuration { |
新版的查询语句如下
1 | module SensitiveLoggerConfig implements DataFlow::ConfigSig { // Module now implements the signature, no inheritance |
我们简单运行一下,看看效果
1 | import java |
嗯,这里仍有四处错误
- CredentialExpr
- sinkNode
- LiveLiteral
- TypeType
实际学习还是可以跟官方文章的,不过记得新版本的改变就行,这里切记,不要问ai,也减少翻阅太早的博客,基本只会误导自己,总之做好心理准备
上述报错我们直接进这个网站https://codeql.github.com/codeql-standard-libraries/java/,学python的分析也是同理噢
我们发现CredentialExpr&LiveLiteral需要import semmle.code.java.security.SensitiveLoggingQuery
同理
- sinkNode: import semmle.code.java.dataflow.ExternalFlow
- TypeType:这个没找到,直接删了吧(气急败坏
如上,完成了修改,开始运行(时间稍微有点长)

效果还不错,揪住了一些可疑的地方~
上述代码:
1 | 旨在分析 敏感数据流,特别是检查是否有敏感数据(例如凭证、密码)被写入日志文件。 |
上面我们用了一个例子进行了相关说明,但是真正查询语句的撰写还差了一些,不过没事,抓住几个要点就行了,多实战
此处刚学了一点,介绍了一些查询语句以及基本的污点追踪,之后的内容还有很多,每天学一点点趴,bye~
参考
https://blog.dvkunion.cn/2022/03/24/CodeQL-%E8%B8%A9%E5%9D%91%E6%8C%87%E5%8D%97/
https://codeql.github.com/docs/codeql-language-guides/analyzing-data-flow-in-java/
https://codeql.github.com/codeql-standard-libraries/java/
https://github.blog/changelog/2023-08-14-new-dataflow-api-for-writing-custom-codeql-queries/