英文:
Is SetGID/SetUID on a Go[lang] binary safe?
问题
我已经写了一个简单的Go程序,使用YAML和MySQL驱动程序,旨在提供一个简单的实用程序来更新数据库,而不将用户名和密码凭据暴露给执行程序的用户。
(我很清楚我也可以使用Python或其他脚本语言来编写此程序,并使用sudo管理权限委派,但我想在这里尝试一种不同的方法,以增加自己的知识)。
构建程序后,我使用了chgrp sys dbcreds.yaml && chmod 0640 dbcreds.yaml
和chgrp sys ./myprog && chmod g+s ./myprog
(作为root用户)...一切似乎都正常工作。(在设置GID之前,我还测试了访问被拒绝的情况,正如应该的那样)。
我还测试了strace
,结果是权限被拒绝(正如应该的那样)。 (为了好玩,我还在上面运行了ltrace -S
;这是在Linux下。正如预期的那样,我没有看到很多正常的libc函数调用...尽管我对在列表中看到了一些*pthread_....()和一个malloc()*调用感到惊讶。我猜GO运行时确实链接到了一些系统库函数)。
我的问题是:这样安全吗?是否有任何已知的方法可以导致Go程序(如下所示)在读取这些私有凭据后核心转储或暴露其内存?在读取凭据后,是否有一种方法可以放弃我的SGID权限?是否有关于Go二进制文件的SUID/SGID漏洞的示例?是否有更好的方法来做到这一点?是否有一种方法可以主动防止核心转储或确保敏感数据(凭据)不会出现在核心转储中?
另一个注意事项:我发现gopkg.in/yaml.v2的语义有点令人不安。在我的YAML文件中,我有类似以下的内容:
---
user me
pw mypassword
但在我的代码中,我必须使用User和Pw(大写)而不是使用我预期的小写。我认为这是Goyaml的作者的实现决策。是这样吗?
package main
import (
"fmt"
"database/sql"
_ "github.com/go-sql-driver/mysql"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"strconv"
)
type Creds struct {
User string
Pw string
}
func main() {
filename := "./dbcreds.yaml"
var creds Creds
conf, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
err = yaml.Unmarshal(conf, &creds)
if err != nil {
panic(err)
}
var arg1 int
arg1, err = strconv.Atoi(os.Args[1])
if err != nil {
panic(err.Error()) // Just for example purpose. You should use proper error handling instead of panic
}
fmt.Println("arg1: ", arg1, "\n")
dsn := fmt.Sprintf("%s:%s@/mydatabase", creds.User, creds.Pw)
db, err := sql.Open("mysql", dsn)
if err != nil {
panic(err.Error())
}
defer db.Close()
err = db.Ping()
if err != nil {
panic(err.Error())
}
stmtOut, err := db.Prepare("SELECT quant FROM c WHERE id >= ?")
if err != nil {
panic(err.Error())
}
defer stmtOut.Close()
rows, err := stmtOut.Query(arg1)
if err != nil {
panic(err.Error())
}
defer rows.Close()
for rows.Next() {
var quant int
err = rows.Scan(&quant)
if err != nil {
panic(err.Error())
}
fmt.Println(quant)
}
}
英文:
I've written a simple [tag:go] program using YAML and the MySQL drivers with the intention of providing a simple utility to update a database without exposing the username and password credentials to the user executing the program.
(I'm well aware that I could also write this in Python or some other scripting language and manage the permissions delegations using [tag:sudo] but I'd like to try a different approach here, for my own edification).
After building the program I've used chgrp sys dbcreds.yaml && chmod 0640 dbcreds.yaml
and chgrp sys ./myprog && chmod g+s ./myprog
(as root) ... and everything seems to work. (I also tested that access was denied, as it should be, prior to the setGID step).
I also tested strace
and that results in permission denied (as it should be). (For fun I also ran ltrace -S
on it; this is under Linux. As expected I did not see many normal libc function calls ... through I am surprised to have seen a few pthread_....() and one malloc() calls in that listing. I guess the GO runtime does link to some system library functions after all).
My question: is this safe? Is there any known way to cause a Go program, such as this (below) to core dump or expose its memory after it has read these private credentials? Is there a way to drop my SGID privs after I've read my credentials? Are there any examples of SUID/SGID exploits on Go binaries? Is there a better way to do this? Is there a way to proactive prevent core dumps or ensure that sensitive data (credentials) would not be in core dumps?
One other note: I find the gopkg.in/yaml.v2 semantics to be a bit disconcerting. In my YAML file I have something like:
---
user me
pw mypassword
But in my code I have to use User and Pw (capitalized) rather than using lower case as I would have expected. I presume this is an implementation decision by the authors of Goyaml. Is that so?
#!go
package main
import (
"fmt"
"database/sql"
_ "github.com/go-sql-driver/mysql"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"strconv"
)
type Creds struct {
User string
Pw string
}
func main() {
filename := "./dbcreds.yaml"
var creds Creds
conf, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
err = yaml.Unmarshal(conf, &creds)
if err != nil {
panic(err)
}
var arg1 int
arg1, err = strconv.Atoi(os.Args[1])
if err != nil {
panic(err.Error()) // Just for example purpose. You should use proper error handling instead of panic
}
fmt.Println("arg1: ", arg1, "\n")
dsn := fmt.Sprintf("%s:%s@/mydatabase", creds.User, creds.Pw)
db, err := sql.Open("mysql", dsn)
if err != nil {
panic(err.Error())
}
defer db.Close()
err = db.Ping()
if err != nil {
panic(err.Error())
}
stmtOut, err := db.Prepare("SELECT quant FROM c WHERE id >= ?")
if err != nil {
panic(err.Error())
}
defer stmtOut.Close()
rows, err := stmtOut.Query(arg1)
if err != nil {
panic(err.Error())
}
defer rows.Close()
for rows.Next() {
var quant int
err = rows.Scan(&quant)
if err != nil {
panic(err.Error())
}
fmt.Println(quant)
}
}
答案1
得分: 8
一个setuid/setgid的Go程序在一定程度上是安全的,但有一个重要的限制。总体而言,Go的setuid/setgid程序与C/C++的setuid/setgid程序一样安全,没有更多也没有更少的安全性。
确实,你可以通过在运行时设置环境变量GOTRACEBACK=crash并发送信号来强制让Go程序转储核心。然而,对于你的目的来说,这是可以接受的,因为Go程序将(尝试)通过发送SIGABRT信号来创建核心转储。内核不会为被信号终止的setuid/setgid程序生成核心转储。
对于Go而言,重要的限制是在GNU/Linux系统上无法回退到原始用户ID。这是因为在GNU/Linux上为多线程程序实现setuid(以及setgid、setgroups、setreuid、setregid、setresuid和setresgid)的方式。详细信息请参阅http://golang.org/issue/1435。
最后需要注意的是,Uw和Pw需要大写,因为标准的reflect包不允许写入未导出的字段。
英文:
A setuid/setgid Go program is reasonably safe, with one major caveat. Go setuid/setgid programs are in general no more, and no less, secure than C/C++ setuid/setgid programs.
It's true that you can force a Go program to dump core by running it with the environment variable GOTRACEBACK=crash and then sending it a signal. However, this is OK for your purposes because the Go program will (try to) create the core dump by sending itself the SIGABRT signal. The kernel will not generate a core dump for a setuid/setgid program killed by a signal.
The major caveat for Go is that on GNU/Linux systems you can not drop back to the original user ID. This is because of how setuid (and setgid, setgroups, setreuid, setregid, setresuid, and setresgid) are implemented for multi-threaded programs on GNU/Linux. The details are at http://golang.org/issue/1435 .
On your final note Uw and Pw need to be capitalized because the standard reflect package does not permit writing to unexported fields.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论