设置SetGID/SetUID在Go语言二进制文件上是否安全?

huangapple go评论83阅读模式
英文:

Is SetGID/SetUID on a Go[lang] binary safe?

问题

我已经写了一个简单的Go程序,使用YAML和MySQL驱动程序,旨在提供一个简单的实用程序来更新数据库,而不将用户名和密码凭据暴露给执行程序的用户。

(我很清楚我也可以使用Python或其他脚本语言来编写此程序,并使用sudo管理权限委派,但我想在这里尝试一种不同的方法,以增加自己的知识)。

构建程序后,我使用了chgrp sys dbcreds.yaml && chmod 0640 dbcreds.yamlchgrp 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

但在我的代码中,我必须使用UserPw(大写)而不是使用我预期的小写。我认为这是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.

huangapple
  • 本文由 发表于 2015年5月31日 08:50:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/30552760.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定