蚂蚁集团的YASA Enigne v0.2.1开源了。其提供了一种静态审计代码I/O流安全性的的新方法。
核心概念解释
首先,需要理解静态应用程序安全测试(SAST) 工具(如 YASA)的基本工作流程:它们通过预定义的规则来识别源代码中的安全漏洞模式。这些规则的核心就是准确地告诉工具:
-
Source(源):数据从不可信的地方进入程序的地方(如用户输入、网络请求、文件读取)。这是污染的起点。
-
Sink(汇聚点):数据被用于危险操作的地方(如执行SQL查询、生成HTML、执行系统命令)。如果不受信任的数据到达这里,就可能产生漏洞。
-
Sanitizer(净化器):对数据进行安全处理的函数(如转义HTML字符、验证输入格式)。如果数据经过了净化器,它就不再被视为污染数据。
-
EntryPoint(入口点):程序开始接受外部输入的地方(如HTTP处理函数、main函数)。工具会从这里开始分析数据流。
下面介绍YASA的ruleConfig的配置。
一 ·Source
1.强类型对象函数调用的返回值
代码示例
func main() {
router := gin.Default()
router.POST("/post", func(c *gin.Context) {
id := c.Query("id") //定义c.Query的返回值为source
page := c.DefaultQuery("page", "0")
name := c.PostForm("name")
message := c.PostForm("message")
fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message)
})
router.Run(":8080")
}
在这段代码中,c.Query("id")
从一个HTTP请求的URL参数中获取值。因为HTTP请求完全由用户控制,所以这个返回值是不可信的、危险的。如果它被直接用于数据库查询(SQL注入)或输出到网页(XSS),就会造成安全风险。
source指定
"FuncCallReturnValueTaintSource":[
{
"fsig": "Query", // 函数名
"calleeType": "*gin.Context", // 调用者的类型
"values": [ // 指定哪个返回值是Source
"0" // 第一个返回值(索引为0)
],
"scopeFile": "all", // 规则生效的范围:所有文件
"scopeFunc": "all" // 规则生效的范围:所有函数
},
...
]
详解JSON
-
"FuncCallReturnValueTaintSource"
:-
这是什么:这是一个规则数组,专门用于配置哪些函数调用的返回值应该被标记为污染源(Source)。
-
为什么用这个:因为
c.Query("id")
正是一个函数调用,并且它的返回值是我们关心的源。
-
-
"fsig": "Query"
:-
含义:
fsig
是 “function signature” 的缩写。这里你指定了函数名为Query
。 -
作用:YASA 会寻找所有名为
Query
的函数调用。
-
-
"calleeType": "*gin.Context"
:-
含义:
callee
指“被调用者”。calleeType
限制了函数调用发生的位置。这里指定了调用Query
方法的对象类型必须是*gin.Context
(Gin框架的上下文指针类型)。 -
为什么重要:这是非常关键的精确匹配条件。很多不同的库或结构体可能都有
Query
方法。通过这个字段,你确保了这条规则只匹配gin.Context
的Query
方法,而不会误伤其他无关的Query
方法(例如,数据库查询相关的Query
)。这大大减少了误报。
-
-
"values": [ "0" ]
:-
含义:指定函数返回的多个值中,哪一个应该被标记为源。Go 语言支持函数返回多个值。
-
"0"
表示第一个返回值。对于c.Query("id")
,它只返回一个值(string),所以这个"0"
指的就是这个字符串值。 -
举例:如果一个函数返回
(value, error)
,那么"values": [ "0" ]
表示只将value
标记为源,而error
不是。
-
-
"scopeFile": "all"
和"scopeFunc": "all"
:-
含义:这条规则的应用范围。
"all"
表示在分析所有文件中的所有函数时,只要遇到*gin.Context.Query()
的调用,就将其返回值标记为 Source。 -
替代方案:你也可以将其范围缩小,例如只针对某个特定的文件 (
"scopeFile": "api/user.go"
) 或某个特定的函数 ("scopeFunc": "getUserById"
),以提高精确度或避免一些误报。
-
source指定
"FuncCallArgTaintSource":[
{
"fsig": "ShouldBindUri", // 函数名
"calleeType": "*gin.Context", // 调用者的类型
"args": [ // 指定哪个参数是Source的“入口”
"0" // 第一个参数(索引为0)
],
"scopeFile": "all", // 规则生效的范围:所有文件
"scopeFunc": "all" // 规则生效的范围:所有函数
},
...
]
详解JSON
-
"FuncCallArgTaintSource"
:-
这是什么:这是一个规则数组,专门用于配置哪些函数会将其参数标记为污染源。
-
为什么用这个:因为
ShouldBindUri
这个函数的行为是通过参数向外传递污染数据,而不是通过返回值。
-
-
"fsig": "ShouldBindUri"
:-
含义:指定函数名为
ShouldBindUri
。
-
-
"calleeType": "*gin.Context"
:-
含义:同样是为了精确性,指定只有
*gin.Context
类型的方法ShouldBindUri
才适用此规则。
-
-
"args": [ "0" ]
(这是与返回值规则最核心的区别):-
含义:
args
字段指定了哪个参数是污染的“入口”。这里的"0"
指的是函数的第一个参数。 -
如何工作:这条规则告诉YASA:“当你看到
*gin.Context.ShouldBindUri
被调用时,就去检查它的第一个参数(索引0)。这个参数所指向的变量(例如&person
)在函数调用之后,其内容将变为污染数据。” -
对于
c.ShouldBindUri(&person)
:-
参数0:
&person
(类型是*Person
)
-
-
-
"scopeFile": "all"
和"scopeFunc": "all"
:-
含义:与之前相同,表示此规则全局有效。
-
3.强类型对象的属性
代码示例
func main() {
router := gin.Default()
router.GET("/user/:id", func(c *gin.Context) {
// 1. 从 c.Params 这个属性中读取数据
id := c.Params.ByName("id") // C.Params是source
// 2. 从 c.Accepted 这个属性(切片)中读取数据
accepted := c.Accepted[0]
// 其他逻辑...
c.JSON(200, gin.H{
"id": id, // <- 来自 c.Params
"accepted": accepted, // <- 来自 c.Accepted
})
})
router.Run()
}
-
c.Params
:这是*gin.Context
类型的一个字段(属性)。它的作用是存储从请求URL路径中解析出来的参数(例如/user/123
中的123
)。这些数据直接来自用户请求,因此是不可信的。 -
c.Accepted
:这也是一个字段,它存储了客户端在Accept
头中声明的、其能够接受的内容类型列表。这个信息同样来自HTTP请求头,由客户端控制,因此也是不可信的。
在这段代码中,污染源不是某个函数的返回值或参数,而是对象 c
的 Params
和 Accepted
这两个属性本身。无论是用 c.Params.ByName("id")
还是直接遍历 c.Params
,其数据都源自用户输入。
source指定
"TaintSource": [
{
"path": "Params", // 属性(字段)的名称
"className": "*gin.Context", // 该属性所属的结构体类型
"scopeFile": "all", // 规则生效的范围:所有文件
"scopeFunc": "all" // 规则生效的范围:所有函数
},
...
]
详解JSON
-
"TaintSource"
:-
这是什么:这是一个最直接的规则数组,用于声明某个类型(类)的特定属性(字段)本身就是一个污染源。
-
为什么用这个:因为
c.Params
这个字段存储的就是原始的用户输入数据。
-
-
"path": "Params"
:-
含义:
path
字段指定了哪个属性名是污染源。这里指定了名为Params
的字段。 -
作用:YASA 会监控所有对
Params
这个字段的访问。
-
-
"className": "*gin.Context"
:-
含义:
className
指定了该属性所属的结构体类型。这是至关重要的精确匹配条件。 - 为什么重要:很多不同的结构体可能都有
Params
字段。通过这个字段,你确保了这条规则只匹配*gin.Context
类型的Params
字段,而不会误伤其他无关的结构体。例如,你可能有一个数据库模型也叫Params
,但它不是源。
-
四·三种Source规则的对比与应用场景
规则类型 | 适用场景 | 例子 | 本质 |
---|---|---|---|
FuncCallReturnValueTaintSource |
方法调用后返回污染数据 | id := c.Query("id") |
方法是源 |
FuncCallArgTaintSource |
方法调用通过参数向外输出污染数据 | c.ShouldBindUri(&person) |
方法是源 |
TaintSource |
对象属性本身存储着污染数据 | id := c.Params.ByName("id") |
字段是源 |
暂无评论内容