Golang安全问题分享
一些Golang的安全问题。
前言
Golang 是一门强类型的语言,在代码中不使用 cgo 的情况下默认使用静态编译,在编译过程中就能杜绝许多安全问题,因此 Golang 会出现的一些安全问题经常是因为依赖库中的issues和开发者自身操作不当。本文将分享几个 Golang 开发中容易忽视的安全问题。
整数溢出
无符号整型的溢出:
|
|
如果开发者定义时直接赋值一个大小已经溢出的数,编译器会编译不通过
有符号数的溢出也是一样:
|
|
可以看到对127加1超出了int8的范围,翻转为了-128
在$GOROOT/src/math/const.go
文件可以看到对范围的定义
在类型转换中,也会出现较大整型向较小整型转换的截断问题,Golang 是一种强类型语言,但是 Golang 提供了类型强制转换的功能,来跳过该限制
如:
|
|
旧版本的kubectl
命令行中出现了一个strconv.Atoi
导致的截断问题。当我们传入port参数的对应字符串后,容器启动的端口这一参数会将经Atoi处理后的字符串进行int32的类型转换。由于64位系统的int是int64类型。转int32的话会出现明显截断:
|
|
这样就有可能导致81端口的服务启动,或者被停止。
小结:
溢出问题一般会出现在开发者对数进行加减乘除操作等其他转换操作,如果开发者没有注意该整数定义的类型,对其操作超过了范围,就会出现无法预料的bug或者是安全漏洞
伪随机数
伪随机数,是使用一个确定性的算法计算出来的似乎是随机的数序,因此伪随机数实际上并不随机,只要用于计算的种子值相同,计算出来的伪随机数就相同,所以正常使用中开发者一般会使用当前时间作为种子值,但是仍然存在被猜测爆破的风险
|
|
我们在https://golang.google.cn/pkg/math/rand/#Seed可以看到,Golang 官方库math/rand
生成种子值默认为1,如果用户不指定种子值,则默认使用1作为种子
如果开发者使用的种子值信息泄漏了,也会被恶意攻击者利用导致一些安全问题,例如gin框架中开发者使用伪随机数作为cookie的key值,如果种子值被泄漏,则会导致攻击者本地生成cookie,越权攻击
小结:
在开发中使用伪随机数时应注意种子值的修改,如果我们的应用对安全性要求比较高,需要使用真随机数的话,可以使用crypto/rand
包中的方法
Windows 非可信目录路径查找
CVE-2021-3115
该漏洞是由日本安全研究人员RyotaK发现的,属于命令注入漏洞。漏洞产生的根本原因是用户运行go get命令来提取库时编译过程的工作原理造成的。
在Windows 命令窗口输入netstat,Windows就会在当前目录中搜索netstat.exe、netstat.bat或其他netstat.*可执行文件,这些搜索到的文件会优先执行,如果当前目录中不存在 netstat 相关的可执行文件,那么Windows shell就会查找 netstat 系统工具,该系统工具位于 Windows %PATH%
变量中。
为了保持一致性,Golang 二进制文件在Unix 系统中效仿了Unix 规则,在Windows 系统中效仿了Windows 规则。也就是说,运行下面的命令在Unix系统和Windows 系统中会产生不同的行为:
|
|
在Windows 系统中,本地的Go二进制文件优先级高,而Unix 系统中会首先搜索$PATH变量来确认其可信位置中是否存在 Go 二进制文件。
这种优先本地、非可信目录的方法也在Go helper和编译器中实现了,比如为调用C语言代码生成Go包的工具:cgo。
Cgo在Windows系统中编译C代码时,Golang可执行文件首先会在非可信的本地目录中搜索GCC编译器。Cgo运行的go命令会包含包资源。因此,攻击者可以利用cgo来启动恶意的gcc.exe程序。
SSTI
Golang的html/template
包提供模板渲染的功能,当源码中出现用户可控且直接拼接渲染时会出现模板注入的漏洞,轻则造成信息泄漏,严重的可能会出现rce等
一个demo:
|
|
这里我实现了一个函数func ShowImg(img string)
用于动态展示图片,并且模板渲染的参数直接拼接且用户可控,所以这里会造成任意文件读取漏洞,所以开发时要注意自定义的渲染函数可能存在被利用的情况
我们还可以利用{{.}}
来泄漏信息:
除了这些,当存在SSTI时,我们还可以进行XSS:
参考:https://blog.takemyhand.xyz/2020/05/ssti-breaking-gos-template-engine-to.html
|
|
net
在官方issues中,net
相关库的安全问题比较多,这里找几个典型的在开发中容易忽视的安全问题介绍
CRLF注入
CVE-2019-9741
issues: https://github.com/golang/go/issues/30794
测试代码:
|
|
监听接收结果:
|
|
可以发现我们利用\r\n
换行成功控制注入到header字段,我们可以利用此攻击内网中的redis、memcached等应用
这个漏洞只在特定版本存在,开发时注意版本即可
header CRLF注入
issues: https://github.com/golang/go/issues/47711
碰巧注意到最近有人在issues提交了header中允许换行符的安全问题,golang官方给其添加上了security
标签,但是实际上我觉得在实战中几乎不会出现header头用户可控的情况,来看看demo:
|
|
提交者给了一个示例,使用两个换行导致其余的header变成body部分,再加上构造的xss payload,可以逃逸header到body触发xss
|
|
官方的修复方案是给header加了过滤:
ip前导0解析问题
CVE-2021-29923
issues: https://github.com/golang/go/issues/30999
影响版本:Golang < 1.17
近日,研究人员在DEF CON大会介绍了Go中的net模块安全漏洞。漏洞CVE编号为CVE-2021-29923,漏洞产生的原因是net处理混合格式的IP地址方式上存在问题,即当数字IPv4地址中以0开头时会触发漏洞。
在net库中,所有IP地址开头的0都会被移除和丢弃。根据IETF的原始说明,如果IPv4地址的前缀有0,那么可以理解为是八进制。但是Golang的net模块忽略了这一点,并将其作为十进制数来处理。
因此,如果开发者使用net库来验证IP地址是否属于某个特定的范围,比如访问控制列表ACL中的IP列表,结果可能就会出现错误。
我们先来看看ping程序对含有前导0的ip解析:
|
|
可以发现,ping程序将0177作为八进制,转换为十进制后就是127,所以最后解析为127.0.0.1
再来看 Golang 对含有前导0的处理demo:
|
|
Goalng net/http库中对于ip解析或请求的函数都受其影响
可以看到,Golang对前导0直接采用忽略的方式,而ping将其当作八进制处理,这样的差异可能会导致一些安全问题
FileServer对Range头错误解析
issues: https://github.com/golang/go/issues/40940
影响版本:Golang < 1.16
net/http
包中提供了展示静态资源的文件服务器FileServer
,Range
头经常用于断点续传技术中,但是Range
是有一定范围和规范的,如果未对传入的Range
进行验证,可能会导致不可预料的安全问题
在 Golang 中启动一个静态文件服务器的demo:
|
|
加上不符合规则的Range头后请求:
|
|
可以看到服务端返回了正常响应,错误的解析了Range头Content-Range: bytes 475-472/473
,并没有报任何错误,而正常来说对于错误的Range头返回应该是这样的:
|
|
解析Range头的实现在$GOROOT/src/net/http/fs.go的parseRange
函数
该parseRange
函数获取Range
标题,从中删除bytes=
前缀并将其用","
字符拆分。稍后,对于每个拆分字符串或更确切地说是“范围”,它会将其进一步对"-"
进行拆分,然后检索start
和end
值。
当只有一个值时,start
是一个空字符串。然后程序转到第一个分支并end
通过ParseInt(end, 10, 64)
调用解析值。
举个例子,如果我们传递bytes=--2
Range 标头,我们最终会得到start="", end="-2"
字符串,然后end
通过ParseInt(end, 10, 64)
调用-2
int64 值来解析。
官方的修复是在调用ParseInt
函数前加上判断是否为负数,不符合规则就报错
由此issue还衍生出了一个题目:https://ctftime.org/task/14632
题目实现了一个静态资源服务,利用go-FileServer对Range头的解析差异进行攻击。
HTTP/2拒绝服务漏洞
CVE-2019-9512 && CVE-2019-9514
issues: https://github.com/golang/go/issues/33606
net/http
并golang.org/x/net/http2
接受来自不可信的客户端直接连接的服务器可以远程作出分配的内存无限量,直到程序崩溃。
这是一个HTTP/2的自身漏洞,攻击者可能会导致服务器排队无限数量的 PING、ACK 或 RST_STREAM 帧通过请求它们而不读取它们,直到程序内存不足导致拒绝服务,在Golang的某些特定版本中会出现。
HTTP_PROXY安全问题
CVE-2016-5386
httpoxy是一组影响在 CGI 或类 CGI 环境中运行的应用程序代码的漏洞。
详细参考:https://httpoxy.org/
漏洞成因:
- CGI 规范定义传入请求标头
Foo: Bar
映射到环境变量HTTP_FOO == "Bar"
。(见 RFC 3875 4.1.18) - HTTP_PROXY 环境变量通常用于为 HTTP 客户端配置 HTTP 代理(Go 的 net/http.Client 和 Transport 默认遵守)
这意味着在 CGI 环境中运行的 Go 程序(作为 CGI 主机下的子进程)容易受到包含Proxy:attacker.com:1234
的传入请求的攻击,设置 HTTP_PROXY,并更改默认情况下 Go 代理所有出站 HTTP 请求的代理。
官方修复是直接弃用了CGI模式下的HTTP_PROXY标头的设置:
官方的poc:https://github.com/httpoxy/go-httpoxy-poc
SMTP注入
issues: https://github.com/golang/go/issues/21159
影响版本:Golang < 1.10
在受影响的版本中,net/smtp 库中的一些函数对接收者邮件地址未做过滤处理,导致可以利用CRLF注入进行修改邮件正文,一个简单的demo如下:
|
|
官方的修复是对SendMail
、Rcpt
和Mail
等受影响的函数增加了换行符过滤:
ReadRequest栈溢出
CVE-2021-31525
issues: https://github.com/golang/go/issues/45710
ReadRequest 和 ReadResponse 在读取非常大的标头(在 64 位架构上超过 7MB,或在 32 位架构上超过 4MB)时会遇到不可恢复的panic。
服务默认情况下不易受到攻击,但如果通过设置Server.MaxHeaderBytes
为更高的值来覆盖默认的最大标头 1MB,则会受到堆栈溢出导致崩溃或拒绝服务。
CGI/FCGI XSS漏洞
CVE-2020-24553
issues: https://github.com/golang/go/issues/40928
当处理程序没有明确设置 Content-Type 标头时,CGI 和 FCGI 实现都默认为text/html
。
如果攻击者可以让服务器在他们的控制下生成内容(例如包含用户数据的 JSON 或上传的图像文件),这可能会被服务器错误地返回为text/html
。如果受害者访问这样的页面,他们可能会在服务器源的上下文中执行攻击者的恶意JavaScript
代码。
利用参考:https://www.redteam-pentesting.de/en/advisories/rt-sa-2020-004/-inconsistent-behavior-of-gos-cgi-and-fastcgi-transport-may-lead-to-cross-site-scripting
Reference
https://github.com/golang/go/issues/34540
https://github.com/golang/go/issues/40928
https://github.com/golang/go/issues/43783
https://github.com/golang/go/issues/12741
https://github.com/golang/go/issues/47711
https://github.com/golang/go/issues/30794
https://github.com/golang/go/issues/30999
https://github.com/golang/go/issues/40940
https://github.com/golang/go/issues/33606
https://github.com/golang/go/issues/16405
https://github.com/golang/go/issues/21159
https://github.com/golang/go/issues/45710
https://www.bleepingcomputer.com/news/security/google-fixes-severe-golang-windows-rce-vulnerability/