首先话多两句 学习一门语言看文档是最烦的事情没有之一,但是不看文档又不能很全面的覆盖方方面面,说一下我的思路吧。啃文档恼火,但是需要结合文档的结构去确定学习的方向。对于一些生涩难懂的地方可以合理利用google去单个搜索。然后能看懂别人写的codeql查询语句后,结合别人的思路,进行学习。同时自己也去拓展不同的地方,结合起来,应该会轻松一点。有些地方具体的还未涉及,后期还会补发。
下载 codeql-cli:
https://github.com/github/codeql-cli-binaries/releases
ql库:https://github.com/github/codeql/releases
安装插件 VSCode
使用
(1)生成审计数据库 database create test --language="java" --command="mvn clean install --file pom.xml" --source-root=../seccode (2)选择合适的工作区,这里我直接引用导入的库
(3)编写合适的QL查询语句,位于qljavaqlexamples,文件命名为*.ql
(4)执行简单查询并查看结果
查询多个数据库
查看AST语法树
(1)基本语法
查询
import java
from Class B
where B.getName() in["Index","Login"]
select B
from 声明变量
where限制条件
select 输出结果
Filed 字段
Method 方法
Class 类名
Package 包名
(2)函数(谓词)
谓词分类
(1)非成员谓词 Non-member predicate(定义在class之外的谓词)
(2)成员谓词 定义在class之内的谓词
(3)特征谓词 可以理解为类构造方法
对上面的查询更改关键词 predicate 首字母必须小写
(1)这是没有类型的函数 还能利用 int,float,string等类型定义函数
predicate getAllClass(Class b) {
b.getName() in["Index","Login"]
}
from Class B
where getAllClass(B)
select B
(2)有返回值的谓词保存在result中
import java
Class add(Class i ){
i.getName() ="XStreamHandler" and result =i
}
from Class B
select add(B)
(3)当我们需要直接执行一个谓词的时候 ,可以添加一个query
query Class add(Class i ){
i.getName() ="XStreamHandler" and result =i
}
(3)类
要求
(1)首字母大写
(2)必须继承一个类
(3)关键字为class
(4)this代表父类而不是本身
(5)与类名相同的叫特征谓词
(6)重载父类方法增加override注解
import java
class Main extends Method {
Main() {
this.getName()="main"
}
}
from classGet cls
select cls
类类型
(1)实体类 正常定义的类
(2)抽象类 abstract修饰的类
(4)方法
Method(查找存在某个方法)
parameter 函数定义变量
arguement 函数传参变量
获取调用参数的数量
getNumberOfParameters()
获取调用的制定位置参数类型
method.getParameterType(0)
获取超类包含子类的类型包括本身
getAnAncestor ()
获取超类型不包括本身
getASupertype()
判断存在方法名
method.hasName("toObject")
获取方法的类名类型
method.getDeclaringType()
查询某个类的某个方法
method.hasQualifiedName(string package, string type)
获取是否存在某个危险方法
from Method method
where method.hasName("fromXML") and method.getDeclaringType().hasQualifiedName("com.thoughtworks.xstream", "XStream")
select method,method.getDeclaringType()
/*查询一个方法名为fromXML,并且方法的类为XStream*/
查询所有包括子类的xx方法
method.hasName("toObject") and method.getDeclaringType().getAnAncestor().hasQualifiedName("org.apache.struts2.rest.handler", "ContentTypeHandler")(包括他本身)
method.hasName("toObject") and method.getDeclaringType().getASupertype().hasQualifiedName("org.apache.struts2.rest.handler", "ContentTypeHandler")(不包括他本身)
MethodAccess(查找调用了某个方法)
查找调用了方法的方法
call.getMethod() = method(只能显示定义)
查找调用了ContentTypeHandler的toObject方法的方法(包含子类)
where method.hasName("toObject") and method.getDeclaringType().getAnAncestor().hasQualifiedName("org.apache.struts2.rest.handler", "ContentTypeHandler") and call.getMethod() = method
类型
原始数据类型 PrimitiveType
boolean, byte, char, double, float, int, long, short
非原始类型 RefType
基本类型 TypeString,TypeObject、TypeCloneable、TypeRuntime、TypeSerializable
类类型 Class
实例 from Variable v , TypeString ts
where ts=v.getType() and ts.hasName("String")
select v
适用于java的codeql
三大核心模块
DataFlow模块 数据流处理模块
Smtm模块 AST语法树模块
Expr模块 节点相关
导航调用图
callable 被调用的函数
call 调用callable
实例1
from Call ca ,Callable cb
where ca.getCallee() =cb and cb.getDeclaringType().hasQualifiedName("org.springframework.expression", "ExpressionParser")
and cb.hasName("parseExpression")
select ca
解释:寻找调用了org.springframework.expression.ExpressionParser#parseExpression的地方
from Call ca ,Callable cb
where ca.getCallee() =cb and cb.getDeclaringType().hasQualifiedName("com.alibaba.fastjson", "JSON")
and cb.hasName("parse")
select ca
解释:寻找调用了
com.alibaba.fastjson.JSON的地方
谓词
ca.getArgument(0) 调用的第一个参数
数据流分析
DataFlow ExprNode :表达式节点
ParameterNode:参数节点
全局数据流需要继承
DataFlow::Configuration,然后重写isSource isSink方法
本地数据流
DataFlow::localFlow(DataFlow::parameterNode(source), DataFlow::exprNode(sink))
全局污点追踪,需要继承 TaintTracking::Configuration
本地污点跟踪 TaintTracking::localTaint(DataFlow::parameterNode(source), DataFlow::exprNode(sink))
可以把他理解成一个污点传播的过程,parameterNode为污点,exprNode为汇聚点
区别
本地数据流是直接的进行带入,如果是底层方法实现则不会寻找 全局数据流可以全局带入,底层也能发现,详情可以见案例SSRF的实现
净化方法(就是依据条件打断当前的污点追踪),详情见案例3
isSanitizer
强制跟踪
isAdditionalTaintStep
就是当断点打断时候,利用此方法进行强制污点跟踪,可以解决未在codeql库里面导致跟踪失败的问题,详情见案例3
案例1 java-sec-code
实例代码来自java-sec-code
本地数据流(下图1)
from Call ca ,Callable cb,SpringRequestMappingMethod route//引入需要的模块
where ca.getCallee() =cb and cb.getDeclaringType().hasQualifiedName("com.alibaba.fastjson", "JSON")
and cb.hasName("parse") and TaintTracking::localTaint(DataFlow::parameterNode(route.getARequestParameter()),DataFlow::exprNode(ca.getArgument(0)))//寻找当调用的函数为com.alibaba.fastjson.JSON#parse的位置,并且对Spring传入的request的参数进行污点跟踪,跟踪的参数节点为传入参数,跟踪的表达式节点为我们传入的fastjson的恶意触发方法啊
select ca ,route.getARequestParameter() 查找结果,传入的参数
全局数据流查询ssrf
这里没有采用openConnection()因为它不带参数,所以使用NEW URL来替代
import java
import semmle.code.java.frameworks.spring.SpringController
import semmle.code.java.dataflow.TaintTracking
class Config extends DataFlow::Configuration{
Config(){
this = "Config"
}
override predicate isSource(DataFlow::Node source){
exists(SpringRequestMappingMethod SRMM | source.asParameter() =SRMM.getARequestParameter() )
}
override predicate isSink(DataFlow::Node sink) {
exists( Call ca,Callable cb|sink.asExpr() =ca.getArgument(0) and ca.getCallee()=cb and cb.getDeclaringType().hasQualifiedName("java.net", "URL")
and cb.hasName("URL"))
}
}
from DataFlow::Node sour ,DataFlow::Node sink,Config config
where config.hasFlow(sour, sink)
select sour,sink
案例2 数据流分析CVE-2017-9805
https://blog.sometimenaive.com/2020/05/21/find-fastjson-jndi-gadget-by-codeql-tainttracking/
https://github.com/githubsatelliteworkshops/codeql/blob/master/java.md
source:toObject(in)
sink:com.thoughtworks.xstream.fromXML
根据分析,我们需要寻找一个污点为toObject的第一个参数为Reader,汇聚点为xstream.fromXML的一条污点传播路径
代码如下
import java
import semmle.code.java.dataflow.DataFlow
class StrutsSafe extends DataFlow::Configuration {
StrutsSafe() { this = "StrutsSafe" }
override predicate isSource(DataFlow::Node source) {
exists(Method me | me.hasName("toObject") and me.getDeclaringType().getAnAncestor().hasQualifiedName("org.apache.struts2.rest.handler", "ContentTypeHandler") and me.getParameter(0) = source.asParameter())
}
override predicate isSink(DataFlow::Node sink) {
exists(Call call, Callable method | method.hasName("fromXML") and method.getDeclaringType().hasQualifiedName("com.thoughtworks.xstream", "XStream") and call.getCallee() = method and sink.asExpr() = call.getArgument(0))
}
}
from StrutsSafe stu, DataFlow::Node source, DataFlow::Node sink
where stu.hasFlow(source, sink)
select source,sink
案例三
https://www.freebuf.com/articles/web/283795.html
(1)分析sql注入,我们发现最终是利用jdbcTemplate.query()也就是query()方法触发,所以我们可以定义query()为汇聚点,污点为我们传入的参数。
import java
import semmle.code.java.dataflow.FlowSources
class SqlConfig extends TaintTracking::Configuration {
SqlConfig() { this = "SqlConfig" }
override predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource
}
override predicate isSink(DataFlow::Node sink) {
exists(Method me ,MethodAccess ma | me.hasName("query") and ma.getCallee() = me and sink.asExpr() = ma.getArgument(0) )
}
}
from SqlConfig scg ,DataFlow::Node source, DataFlow::Node sink
where scg.hasFlow(source, sink)
select source.toString(),source,sink
为了消除上面关于数值类型的注入误报,我们加入isSanitizer来进行污点误报解决,判断当污点为数字类型就断掉
import java
import semmle.code.java.dataflow.FlowSources
class SqlConfig extends TaintTracking::Configuration {
SqlConfig() { this = "SqlConfig" }
override predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource
}
override predicate isSink(DataFlow::Node sink) {
exists(Method me ,MethodAccess ma | me.hasName("query") and ma.getCallee() = me and sink.asExpr() = ma.getArgument(0) )
}
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or
node.getType() instanceof BoxedType or
node.getType() instanceof NumberType or
exists(ParameterizedType pt| node.getType() = pt and pt.getTypeArgument(0) instanceof NumberType )
}
}
from SqlConfig scg ,DataFlow::Node source, DataFlow::Node sink
where scg.hasFlow(source, sink)
select source.toString(),source,sink
为了消除不可识别类型对codeql分析的影响,我们加入isAdditionalTaintStep
import java
import semmle.code.java.dataflow.FlowSources
class SqlConfig extends TaintTracking::Configuration {
SqlConfig() { this = "SqlConfig" }
predicate isTaintedString(Expr expSrc, Expr expDest) {
exists(Method m ,MethodAccess ma1,MethodAccess ma2 | expSrc = ma1.getArgument(0) and expDest = ma2 and ma2.getMethod() =m and m.hasName("get"))
}
override predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource
}
override predicate isSink(DataFlow::Node sink) {
exists(Method me ,MethodAccess ma | me.hasName("query") and ma.getCallee() = me and sink.asExpr() = ma.getArgument(0) )
}
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or
node.getType() instanceof BoxedType or
node.getType() instanceof NumberType or
exists(ParameterizedType pt| node.getType() = pt and pt.getTypeArgument(0) instanceof NumberType )
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
isTaintedString(node1.asExpr(), node2.asExpr())
}
}
from SqlConfig scg ,DataFlow::Node source, DataFlow::Node sink
where scg.hasFlow(source, sink)
select source.toString(),source,sink
/**这个isTaintedString意思就说我们对get表达式节点进行了一个结合,但是由于没有做类型限制,所以会查出来比较多的数据,我们精确一下将参数和方法的类型做一个精确**/
import java
import semmle.code.java.dataflow.FlowSources
class SqlConfig extends TaintTracking::Configuration {
SqlConfig() { this = "SqlConfig" }
predicate isTaintedString(Expr expSrc, Expr expDest) {
exists(Method m ,MethodAccess ma1,MethodAccess ma2 | expSrc = ma1.getArgument(0) and expDest = ma2 and ma2.getMethod() =m and m.hasName("get") and m.getDeclaringType().toString()="Optional<String>" and ma1.getAnArgument().getType().toString()="Optional<String>")
}
override predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource
}
override predicate isSink(DataFlow::Node sink) {
exists(Method me ,MethodAccess ma | me.hasName("query") and ma.getCallee() = me and sink.asExpr() = ma.getArgument(0) )
}
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or
node.getType() instanceof BoxedType or
node.getType() instanceof NumberType or
exists(ParameterizedType pt| node.getType() = pt and pt.getTypeArgument(0) instanceof NumberType )
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
isTaintedString(node1.asExpr(), node2.asExpr())
}
}
from SqlConfig scg ,DataFlow::Node source, DataFlow::Node sink
where scg.hasFlow(source, sink)
select source.toString(),source,sink
原文始发于微信公众号(e0m安全屋):codeql 学习笔记1
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论