弈 - Codeql 自动运行和项目监控工具

发布时间:2024年01月11日

前言

代码审计总是离不开一些神器,笔者常用?Codeql[1]?这款工具辅助挖洞。当我每写一个规则都需要对其它项目手动运行检查一遍,效率很低,再加上?lgtm[2]?的关闭,此项目诞生了 ---?弈(Yi)[3]?。

CVE-2021-43798

这里以 Graana 的任意文件读取漏洞举例说明使用方法(初学 Codeql,如有错误之处,轻点喷)。

该漏洞版本为 8.0.0 - 8.3.0 , 修复版本为 8.3.1, 8.2.7, 8.1.8, 8.0.7。

git?clone?https://github.com/grafana/grafana
git?checkout?v8.2.6

这个漏洞发生在?/public/plugins/:pluginId/*?api 中,当输入的?pluginId存在时,会匹配*内容,使用filepath.Clean清理路径中的多余字符后,直接拼接到pluginFilePath,然后使用os.open(pluginFilePath)打开该文件,最终回显到页面。而且plugins api权限为public,是未授权的,任何人都可以查看。

图片

fmt.Println(filepath.Clean("./.../../../../../../../etc/passwd"))

输出:../../../../../etc/passwd 只清除了前面的./.../../

Codeql分析

生成 codeql 数据库:

codeql?database?create?/Users/yhy/CodeQL/database/go/grafana/v8.2.6?-s?./?--language=go

sink

os.open(),这个显而易见。

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

我将?Source?点定在了macaron.Params()函数。

网上的文章都是以github.com/grafana/grafana/pkg/api/routing.RouteRegister作为起始点,这就导致一个问题,写完的规则只对grafana项目起作用,不通用。

仔细研究会发现,RouteRegister的实现是以gopkg.in/macaron.v1框架为基础的,但是官方的go/ql/lib/semmle/go/frameworks/Macaron.qll,只是实现了一个重定向相关的检测规则,emmm,只能自己动手写了。

写着写着忽然发现,怎么也获取不到macaron.Params, 去看?macaron[4]?源码才发现,这个函数就根本没有,是Grafana自己实现的。我们来分析一下这个?Params?函数。

图片

其实就是获取net/httpRequest.Context的值,而参数r,又是通过pkg/macaron/context.go

图片

也就是?macaron[5]?框架中的Context结构体中的Req成员,这个Req就是我们要找的Source点。

修改go/ql/lib/semmle/go/frameworks/Macaron.qll文件,加入如下代码:

??private?class?UserControlledRequestField?extends?UntrustedFlowSource::Range,?DataFlow::FieldReadNode?{
??UserControlledRequestField()?{
????exists(string?fieldName?|?this.getField().hasQualifiedName("gopkg.in/macaron.v1",?"Context",?fieldName)?|?
????fieldName?=?"Req"
????????)
????}
??}

单独执行,可以找到污染点。

图片

直接将 sink、source 拼接跑,并没有出结果,因此需要一些处理来连接数据流。

isAdditionalTaintStep

这里?tyskill[6]?师傅说的很详细,引用一下:

  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()
    ????)
    }
    

    运行,成功发现该漏洞。

图片

加入工具监控、扫描

因为项目中调用 Codeql 将扫描结果保存为文件,这里需在文件头添加一些描述,完整代码如下:

/**
?*?@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"

将此 ql 文件路径加入配置文件config.yaml中,?- go/ql/src/myRules/ReadFile.ql ,之后程序会自动对监控的项目运行此条规则,等待捡洞即可(笔者已经捡到了^_^)。

效果图

图片

?

图片

文章来源:https://blog.csdn.net/moresec/article/details/135528368
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。