基于YASA程序分析研究(前置知识学习 1.0)

基于YASA程序分析研究(前置知识学习 1.0)_秃兔安全
基于YASA程序分析研究(前置知识学习 1.0)
此内容为付费阅读,请付费后查看
20
暂时无法购买,请与站长联系
您当前未登录!建议登陆后购买,可保存购买订单
付费阅读

蚂蚁集团的YASA Enigne  v0.2.1开源了。其提供了一种静态审计代码I/O流安全性的的新方法。

核心概念解释

首先,需要理解静态应用程序安全测试(SAST) 工具(如 YASA)的基本工作流程:它们通过预定义的规则来识别源代码中的安全漏洞模式。这些规则的核心就是准确地告诉工具:

  1. Source(源):数据从不可信的地方进入程序的地方(如用户输入、网络请求、文件读取)。这是污染的起点。

  2. Sink(汇聚点):数据被用于危险操作的地方(如执行SQL查询、生成HTML、执行系统命令)。如果不受信任的数据到达这里,就可能产生漏洞。

  3. Sanitizer(净化器):对数据进行安全处理的函数(如转义HTML字符、验证输入格式)。如果数据经过了净化器,它就不再被视为污染数据。

  4. 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"),以提高精确度或避免一些误报。

2.强类型对象方法调用的参数

代码示例

func main() {
    route := gin.Default()
    route.GET("/:name/:id", func(c *gin.Context) {
        var person Person // 1. 声明一个干净的 Person 结构体变量

        // 2. 调用 ShouldBindUri,并传入 person 变量的地址 (&person)
        if err := c.ShouldBindUri(&person); err != nil {
            c.JSON(400, gin.H{"msg": err.Error()})
            return
        }
        // 3. 此时,person 结构体的字段(如 Name, ID)已被填充为HTTP请求中的用户数据
        c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID}) // <- 如果person.Name/ID未经验证直接使用,可能危险
    })
    route.Run(":8088")
}
  • c.ShouldBindUri(&person) 是关键。

  • 它的作用是:将HTTP请求URI中的参数(如 :name 和 :id绑定到传入的 person 结构体实例的对应字段上。

  • 这个函数本身的返回值是一个 error,用于指示绑定是否成功,它通常不是我们关心的数据。

  • 我们真正关心的、来自用户不可信输入的数据,被写入到了参数 &person 所指向的内存地址中。

  • 因此,person 变量(特别是它的字段 person.Name 和 person.ID)在 ShouldBindUri 调用之后变成了污染源

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") 字段是源
© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容