概述
介绍及搭建教程:https://www.freebuf.com/sectool/269924.html
Golang依赖库:https://github.com/github/codeql-go
注意:该依赖库应该独立为一个workspace而不是放进CLI项目中
内置资源:
ql/examples/snippets
目录提供了一些基础的ql文件,通过阅读与仿写可以了解一些基本的使用,具体有哪些就自己去看吧,反正也不多lib/security
目录下定义了一些常见漏洞的检测规则,通过import semmle.go.security.[module]
导入ql文件配合污点分析使用
基础使用
寻找指定方法和调用点
import go
// 方法一
from Function func
where func.hasQualifiedName("os", "Open")
select func, func.getAReference()
// 方法二
// call.getTarget()相当于Function
// call.getExpr()、call.asExpr()和call.getTarget().getAReference()同理
from DataFlow::CallNode call
where call.getTarget().hasQualifiedName("os", "Open")
select call.getTarget(), call.getExpr(), call.getTarget().getAReference()
// 方法三
from Function func, DataFlow::CallNode call
where
func.hasQualifiedName("os", "Open") and
call = func.getACall()
select func, call
获得方法参数
// 方法一 -- 获得函数定义时的参数(receiver和param)
from Function func
where func.getName() = "getPluginAssets"
select func.getAParameter()
// 方法二 -- 获得函数调用时的参数
from DataFlow::CallNode call
// from CallExpr call也可以
where call.getTarget().hasQualifiedName("os", "Open")
select call.getAnArgument()
查询指定参数的方法
注:查询结果排序区分大小写,按ASCII排序
|
|
其他
getExpr和asExpr区别:
从注释上看:
getExpr:Gets the underlying expression this node corresponds to.(获取此节点对应的基础表达式) asExpr:Gets the expression corresponding to this node, if any.(获取与此节点对应的表达式(如果有))
两个方法分别是获得基础表达式和节点对应表达式,应该是有区别的,但是在函数体中两者却是相同的:
class ExprNode extends InstructionNode {
override IR::EvalInstruction insn;
Expr expr;
ExprNode() { expr = insn.getExpr() }
override Expr asExpr() { result = expr }
/** Gets the underlying expression this node corresponds to. */
Expr getExpr() { result = expr }
}
唯一的区别就是asExpr是通过覆写基类Node::asExpr
方法来实现的,所以到底有什么区别我也不知道(有营销号那味儿了2333
不管了,目前看到的文章中都是用asExpr,那就暂且用这个吧。等等党终会获得胜利(bushi
其他的使用方法见http://f4bb1t.com/post/2020/12/15/codeql-for-golang-practise2/(or直接读examples)
污点分析
概述
文章https://www.modb.pro/db/139793直接借助了官方提供的SqlInjection模块,代码在lib/semmle/go/security/SqlInjection
目录下,其内部定义了Configuration类提供source、sink检测以及节点是否可被污染的状态变化,并通过定义isAdditionalTaintStep方法扩展了NoSQL的污染方式
基于此我们可以了解一个污点分析模块基本的写法:
- 定义一个继承
TaintTracking::Configuration
的类 - source、sink判断/isSource、isSink
- 可选:节点状态判断/isSanitizer、isSanitizerGuard
- 可选:是否存在其他的污染方式/isAdditionalTaintStep,即中断的数据流能否有方式重新连接
实践/Grafana任意文件读取
数据库懒得编译,就直接用现成的:https://github.com/safe6Sec/codeql-grafana
漏洞成因:
- plugins api权限为public,任何人都可以查看
- 传入参数被直接拼接进了插件路径
- 未经处理的路径被Open函数处理,导致任意文件读取
规则编写: sink很明显,就是Open函数:
class Sink extends DataFlow::Node {
Sink() {
exists(
DataFlow::CallNode call |
call.getTarget().hasQualifiedName("os", "Open") |
call.getArgument(0) = this // 标记 sink 为 os.Open 第一个参数
)
}
}
source可以锁定为 router 参数,那么应该与Context类型有关,也就是*models.ReqContext
类,但是写不出来emmm...那就照着文章中查询 router 注册的思路走。
定义source如下:
|
|
如果单纯的这样写会查询出大量的source,因此需要添加限制条件来减少误报,通过观察源码发现router注册参数大部分都是string, selectorExpr
和string, CallExpr(selectorExpr)
格式,因此可以将参数格式限制为SelectorExpr,可以看到文中添加了大量的SelectorExpr过滤条件:
selectorExpr为符合
f.x
格式的表达式
|
|
因此完整的source为:
|
|
Query发现没有结果,因为没有Context传递参数导致在获取路径阶段就截断了,因此需要一些处理来连接数据流:
我一开始的思路是仿照SqlInjection模块的isAdditionalTaintStep方法编写规则,但是那样查询出来的结果太多了,稍微加点限制就查询无果,只好放弃挣扎了
分析一下:
- 限制函数为Params
- 函数可被污染就说明参数可控,那么就让pred节点作为参数
- SimpleAssignStmt结构表示一个赋值表达式,如
a+=b
,Rhs表示等号右边,通过查看源码可知Params函数调用几乎都是在等号右边,因此可以通过该结构减少误报 - 最后将输出节点连接到赋值表达式
|
|
最后ql文件:
|
|
Reference
https://www.modb.pro/db/139793
https://cloud.tencent.com/developer/article/1750796