前言
CVE-2021-43798
git clone https://github.com/grafana/grafana
git checkout v8.2.6
fmt.Println(filepath.Clean("./.../../../../../../../etc/passwd"))
输出:../../../../../etc/passwd 只清除了前面的./.../../
Codeql分析
codeql database create /Users/yhy/CodeQL/database/go/grafana/v8.2.6 -s ./ --language=go
sink
import go
import DataFlow::PathGraph
class Sink extends DataFlow::Node {
Sink() {
exists(
DataFlow::CallNode call |
call.getTarget().hasQualifiedName("os", "Open") |
call.getArgument(0) = this // 标记 sink 为 os.Open 第一个参数
)
}
}
source
private class UserControlledRequestField extends UntrustedFlowSource::Range, DataFlow::FieldReadNode {
UserControlledRequestField() {
exists(string fieldName | this.getField().hasQualifiedName("gopkg.in/macaron.v1", "Context", fieldName) |
fieldName = "Req"
)
}
}
isAdditionalTaintStep
1. 限制函数为Params。
2. 函数可被污染就说明参数可控,那么就让pred节点作为参数。
3. SimpleAssignStmt结构表示一个赋值表达式,如a+=b,Rhs表示等号右边,通过查看源码可知Params函数调用几乎都是在等号右边,因此可以通过该结构减少误报。
4. 最后将输出节点连接到赋值表达式。
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(
CallExpr call, SimpleAssignStmt sas |
// call.getTarget().getName() = "Params" and
// 限制为 Params 函数会产生局限性,去除
call.getAnArgument() = pred.asExpr() and
sas.getRhs().getAChild() = call.getParent*().getAChild() and
// 使用getParent*()是因为等号右边不止有光秃秃的Params方法调用,如漏洞点就存在Jion函数拼接操作,需要通过传递闭包getParent*()来获取完整表达式
// 使用getAChild()则是要获取Params的方法调用,不过测试发现用不用效果差不多,所以也不懂为什么还要加这个
sas.getRhs() = succ.asExpr()
)
}运行,成功发现该漏洞。
加入工具监控、扫描
/**
* @name read file
* @description read file
* @kind path-problem
* @problem.severity error
* @security-severity 6.1
* @sub-severity high
* @id yhy0/read-file
* @tags security
* @precision high
*/
import go
import DataFlow::PathGraph
class ReadFileSink extends DataFlow::Node {
ReadFileSink() {
exists(
DataFlow::CallNode call |
call.getTarget().hasQualifiedName("os", "Open") |
call.getArgument(0) = this // 标记 sink 为 os.Open 第一个参数
)
}
}
class ReadFileConfig extends TaintTracking::Configuration {
ReadFileConfig() { this = "read file" }
// 这里的 source 实现 UntrustedFlowSource ,方便其他框架通用, 对于Grafana ,我们已经修改了go/ql/lib/semmle/go/frameworks/Macaron.qll文件
override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof ReadFileSink }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(
CallExpr call, SimpleAssignStmt sas |
// call.getTarget().getName() = "Params" and
// 限制为 Params 函数会产生局限性,去除
call.getAnArgument() = pred.asExpr() and
sas.getRhs().getAChild() = call.getParent*().getAChild() and
// 使用getParent*()是因为等号右边不止有光秃秃的Params方法调用,如漏洞点就存在Jion函数拼接操作,需要通过传递闭包getParent*()来获取完整表达式
// 使用getAChild()则是要获取Params的方法调用,不过测试发现用不用效果差不多,所以也不懂为什么还要加这个
sas.getRhs() = succ.asExpr()
)
}
}
from ReadFileConfig rfc, DataFlow::PathNode sink, DataFlow::PathNode source
where rfc.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "read file find on $@.", source.getNode(), "user-provided value"
效果图
参考资料
https://tyskill.github.io/posts/codeql-grafana/#%E5%AE%9E%E8%B7%B5grafana%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E8%AF%BB%E5%8F%96
https://xz.aliyun.com/t/10648
https://codeql.github.com/
引用链接
[1] Codeql: https://github.com/github/codeql-cli-binaries
[2] lgtm: lgtm.com/
[3] 弈(Yi): https://github.com/ZhuriLab/Yi
[4] macaron: https://github.com/go-macaron/macaron
[5] macaron: https://github.com/go-macaron/macaron
[6] tyskill: https://tyskill.github.io/posts/codeql-grafana/#%E5%AE%9E%E8%B7%B5grafana%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E8%AF%BB%E5%8F%96
还没有评论,来说两句吧...