近来比赛中越来越多的go题目,所以最近在学习go方面的一些知识点,恰巧看到P神的讲到这个go语言的一些特性,这里简单复现一些并做一些记录。
go语言的安全性相较于其他语言来说要安全很多,这对安全人员来说也是更大的挑战,从语言设计或者生态的角度来看好像很难找到服务端上的独特问题,导致Go安全通常还是在看一些SQL注入、命令执行、越权等传统通用漏洞。
那么这里我们会发现一个go语言的特性,在一些地方会导致一些问题的产生。
在go语言中他对于“空值”有着他和其他语言不同的设计方法,我们来看一个案例。
python
php
这里我们看到在弱语言中我们删除变量后变量就不存在了,那么在一些强类型的语言中,我们声明一个变量如果不给他赋值,他会给一个null值,或者直接未初始化无法使用。
java
但是在go中,如果我们声明一个变量,那么他就会给这个变量一个0值。
那么发现这个特性后我们思考会引发什么问题,这里我们看到Gorm这个地方的例子。
package web
import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
var DB *gorm.DB
type UserTab struct {
gorm.Model
Email string `json:"email" gorm:"column:email;not null;type:varchar(128);unique;size:128;"`
Token string `json:"token" gorm:"column:token;index;type:varchar(16);default:null;"`
}
func UserHandler(c *gin.Context) {
token := c.Query("token")
user, err := UserFirst(&UserTab{Token: token})
if err != nil {
c.Status(404)
return
}
c.JSON(200, gin.H{
"user": user,
})
}
func UserFirst(query *UserTab) (*UserTab, error) {
var user UserTab
err := DB.Where(query).First(&user).Error
return &user, err
}
模拟了一个根据Token查询用户的功能。其中每个用户都会有一些随机生成的字符串Token,预访问后面的功能需要先校验请求包里的Token对应的用户身份。
这是一个常见的功能,我们可以把这个Token理解为session_id或其他任何什么用户身份标识。但是,我在使用下面这个查询语句查找用户之前,一定要先判断token的值是否为空:
DB.Where(&User{Token: token}).First(&user).Error
Gorm支持使用一个数据库Model指针的形式生成查询语句,比如&User{Token: token},在token的值不为空时,生成的SQL语句将会是:
SELECT * FROM `user` WHERE `token` = 'your-token' LIMIT 1
但是,由于Go这门语言本身对于“零值”的设计,他是无法区分某个结构体中某个字段是否有被赋值过。
如果User结构体的token字段本身是一个字符串类型的属性,在初始化User对象的时候Token属性将会拥有一个默认的零值,空字符串;如果此时用户传入的token也是一个空字符串,其赋值给Token属性的时候,对于这个User对象的值将不会有任何改变。
那么此时Gorm的Where函数接收到这样一个User对象的时候,它内部是无法分辨这个对象是根本没有给Token设置任何值,还是给它设置的是空字符串。所以它在生成SQL语句的时候将不会为Token生成条件语句:
SELECT * FROM `user` LIMIT 1
这样就会导致上面这条ORM语句查询到数据库里第一个用户,而通常第一个用户都是管理员,这也可能导致一次比较高危的越权。 Go无法区分一个结构中属性是没有被初始化过,还是被初始化但赋了零值,这个问题也是Go语言设计中一个比较常见的问题。抛开这次的问题,我们如果作为开发者,要区分这两者通常只能将结构体中的字段设置成一个指针,并通过判断字符是不是nil来判断是否被初始化过。
refer
原文始发于微信公众号(山石网科安全技术研究院):Go语言特性引发的安全问题的思考