SetGID/SetUID 在 Go[lang] 二进制文件上安全吗?
Is SetGID/SetUID on a Go[lang] binary safe?
我使用 YAML 和 MySQL 驱动程序编写了一个简单的 go 程序,目的是提供一个简单的实用程序来更新数据库,而不会将用户名和密码凭证暴露给执行的用户程序。
(我很清楚我也可以用 Python 或其他一些脚本语言编写它并使用 sudo 管理权限委托,但我想在这里尝试不同的方法,为了我自己的启迪)。
构建程序后,我使用了 chgrp sys dbcreds.yaml && chmod 0640 dbcreds.yaml
和 chgrp sys ./myprog && chmod g+s ./myprog
(作为 root)……一切似乎都正常。 (我还测试了在 setGID 步骤之前访问被拒绝,因为它应该是这样)。
我还测试了 strace
,这导致权限被拒绝(应该如此)。 (为了好玩,我还在上面 运行 ltrace -S
;这是在 Linux 下。不出所料,我没有看到很多正常的 libc 函数调用......通过我很惊讶地看到了一些pthread_....() 和一个 malloc() 调用在该列表中。我猜 GO 运行时确实 link 一些毕竟是系统库函数)。
我的问题:这样安全吗?是否有任何已知的方法可以使 Go 程序(如下所示)在读取这些私有凭证后进行核心转储或暴露其内存?有没有办法在我阅读我的凭据后删除我的 SGID 权限?是否有任何 SUID/SGID 利用 Go 二进制文件的示例?有一个更好的方法吗?有没有办法主动防止核心转储或确保敏感数据(凭据)不会在核心转储中?
另一个注意事项:我发现 gopkg.in/yaml.v2 语义有点令人不安。在我的 YAML 文件中,我有类似的内容:
---
user me
pw mypassword
但是在我的代码中我必须使用 User 和 Pw(大写)而不是像我预期的那样使用小写。我认为这是 Goyaml 作者的实施决定。是吗?
#!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)
}
}
A setuid/setgid Go 程序相当安全,但有一个主要警告。 Go setuid/setgid 程序通常不比 C/C++ setuid/setgid 程序更安全。
确实可以通过 运行 使用环境变量 GOTRACEBACK=crash 强制 Go 程序转储核心,然后向其发送信号。但是,这对您的目的来说是可以的,因为 Go 程序将(尝试)通过向自己发送 SIGABRT 信号来创建核心转储。内核不会为被信号杀死的 setuid/setgid 程序生成核心转储。
Go 的主要警告是在 GNU/Linux 系统上您不能退回到原始用户 ID。这是因为 setuid(以及 setgid、setgroups、setreuid、setregid、setresuid 和 setresgid)是如何在 GNU/Linux 上为多线程程序实现的。详情在 http://golang.org/issue/1435 .
关于你最后的说明,Uw 和 Pw 需要大写,因为标准反射包不允许写入未导出的字段。
我使用 YAML 和 MySQL 驱动程序编写了一个简单的 go 程序,目的是提供一个简单的实用程序来更新数据库,而不会将用户名和密码凭证暴露给执行的用户程序。
(我很清楚我也可以用 Python 或其他一些脚本语言编写它并使用 sudo 管理权限委托,但我想在这里尝试不同的方法,为了我自己的启迪)。
构建程序后,我使用了 chgrp sys dbcreds.yaml && chmod 0640 dbcreds.yaml
和 chgrp sys ./myprog && chmod g+s ./myprog
(作为 root)……一切似乎都正常。 (我还测试了在 setGID 步骤之前访问被拒绝,因为它应该是这样)。
我还测试了 strace
,这导致权限被拒绝(应该如此)。 (为了好玩,我还在上面 运行 ltrace -S
;这是在 Linux 下。不出所料,我没有看到很多正常的 libc 函数调用......通过我很惊讶地看到了一些pthread_....() 和一个 malloc() 调用在该列表中。我猜 GO 运行时确实 link 一些毕竟是系统库函数)。
我的问题:这样安全吗?是否有任何已知的方法可以使 Go 程序(如下所示)在读取这些私有凭证后进行核心转储或暴露其内存?有没有办法在我阅读我的凭据后删除我的 SGID 权限?是否有任何 SUID/SGID 利用 Go 二进制文件的示例?有一个更好的方法吗?有没有办法主动防止核心转储或确保敏感数据(凭据)不会在核心转储中?
另一个注意事项:我发现 gopkg.in/yaml.v2 语义有点令人不安。在我的 YAML 文件中,我有类似的内容:
---
user me
pw mypassword
但是在我的代码中我必须使用 User 和 Pw(大写)而不是像我预期的那样使用小写。我认为这是 Goyaml 作者的实施决定。是吗?
#!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)
}
}
A setuid/setgid Go 程序相当安全,但有一个主要警告。 Go setuid/setgid 程序通常不比 C/C++ setuid/setgid 程序更安全。
确实可以通过 运行 使用环境变量 GOTRACEBACK=crash 强制 Go 程序转储核心,然后向其发送信号。但是,这对您的目的来说是可以的,因为 Go 程序将(尝试)通过向自己发送 SIGABRT 信号来创建核心转储。内核不会为被信号杀死的 setuid/setgid 程序生成核心转储。
Go 的主要警告是在 GNU/Linux 系统上您不能退回到原始用户 ID。这是因为 setuid(以及 setgid、setgroups、setreuid、setregid、setresuid 和 setresgid)是如何在 GNU/Linux 上为多线程程序实现的。详情在 http://golang.org/issue/1435 .
关于你最后的说明,Uw 和 Pw 需要大写,因为标准反射包不允许写入未导出的字段。