Use setuid() systemcall wherever possible to reduce privileges before accepting network connections. First step toward solving issue #16.
This commit is contained in:
parent
5258b29c6b
commit
4e6a8fcd05
6 changed files with 119 additions and 12 deletions
47
README.md
47
README.md
|
@ -299,17 +299,42 @@ a request URL includes components after the path to an executable
|
|||
executable (e.g. `/var/gemini/cgi-bin/scripty.py`) while the variable
|
||||
`PATH_INFO` will contain the remainder (e.g. `foo/bar/baz`).
|
||||
|
||||
It is very important to be aware that programs written in Go are
|
||||
*unable* to reliably change their UID once started, due to how
|
||||
goroutines are implemented on unix systems. As an unavoidable
|
||||
consequence of this, CGI processes started by Molly Brown are run as
|
||||
the same user as the server process. This means CGI processes
|
||||
necessarily have read and write access to the server logs and to the
|
||||
TLS private key. There is no way to work around this. As such you
|
||||
must be extremely careful about only running trustworthy CGI
|
||||
applications, ideally only applications you have carefully written
|
||||
yourself. Allowing untrusted users to upload arbitrary executable
|
||||
files into a CGI path is a serious security vulnerability.
|
||||
Molly Brown itself tries very hard to avoid being tricked into serving
|
||||
content that isn't supposed to be served, but it is completely unable
|
||||
to impose any control over what CGI processes can or can't go after
|
||||
they are started! Where possible, Molly Brown will use the operating
|
||||
system's security features to reduce risk, but it is your
|
||||
responsibility to understand what it can and cannot do and weigh the
|
||||
risks accordingly:
|
||||
|
||||
When compiled on GNU/Linux with Go version 1.16 or later, or on any
|
||||
other unix operating system with any version of Go, Molly Brown will
|
||||
use the setuid() system call as follows. When the compiled
|
||||
`molly-brown` executable has its SETUID bit set, so that it starts
|
||||
with the privileges of the user who owns the binary, it will change
|
||||
the effective UID back to the real UID before it begins accepting
|
||||
network connections. This way, config files, log files and TLS keys
|
||||
can be set readable by the user who owns the binary, but not readable
|
||||
by the user who runs the binary. CGI processes will then be unable to
|
||||
read any of those sensitive files. If the binary is not SETUID but is
|
||||
run by the superuser/root, then Molly will change its UID to that of
|
||||
the `nobody` user before accepting network connections, so CGI
|
||||
processes will again not be able to read sensitive files.
|
||||
|
||||
When compiled on GNU/Linux with Go versions 1.15 or earlier, Molly
|
||||
Brown is completley unable to reliably change its UID due to the way
|
||||
early implementations of goroutines interacted with the setuid()
|
||||
system call. In this situation, Molly Brown will refuse to run as
|
||||
superuser/root. It will run as any other user, but CGI processes will
|
||||
necessary run as the same user as the server and so unavoidably will
|
||||
have access to sensitive files. You should proceed with extreme
|
||||
caution and only use carefully vetted CGI programs (or upgrade Go).
|
||||
|
||||
Molly Brown will compile on non-unix operating systems and is known to
|
||||
run on Plan9, for example, but no special security measures are taken
|
||||
on these non-unix platforms. It is your responsibility to understand
|
||||
the risks. If you are aware of security measures for these systems
|
||||
which can be implemented in Go, patches are extremely welcome.
|
||||
|
||||
SCGI applications must be started separately (i.e. Molly Brown expects
|
||||
them to already be running and will not attempt to start them itself),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build !openbsd
|
||||
// +build js nacl plan9 windows
|
||||
|
||||
package main
|
||||
|
||||
|
|
45
security_dropprivs.go
Normal file
45
security_dropprivs.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
// +build linux,go1.16 aix darwin dragonfly freebsd illumos netbsd openbsd solaris
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func DropPrivs(config Config, errorLog *log.Logger) {
|
||||
|
||||
// Get our real and effective UIDs
|
||||
uid := os.Getuid()
|
||||
euid := os.Geteuid()
|
||||
|
||||
// If these are equal and non-zero, there's nothing to do
|
||||
if uid == euid && uid != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// If our real UID is root, we need to lookup the nobody UID
|
||||
if uid == 0 {
|
||||
user, err := user.Lookup("nobody")
|
||||
if err != nil {
|
||||
errorLog.Println("Could not lookup UID for user " + "nobody" + ": " + err.Error())
|
||||
log.Fatal(err)
|
||||
}
|
||||
uid, err = strconv.Atoi(user.Uid)
|
||||
if err != nil {
|
||||
errorLog.Println("Could not lookup UID fr user " + "nobody" + ": " + err.Error())
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Drop priveleges
|
||||
err := syscall.Setuid(uid)
|
||||
if err != nil {
|
||||
errorLog.Println("Could not setuid to " + strconv.Itoa(uid) + ": " + err.Error())
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
21
security_oldgolinux.go
Normal file
21
security_oldgolinux.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
// +build linux,!go1.16
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func enableSecurityRestrictions(config Config, errorLog *log.Logger) {
|
||||
|
||||
// Prior to Go 1.6, setuid did not work reliably on Linux
|
||||
// So, absolutely refuse to run as root
|
||||
uid := os.Getuid()
|
||||
euid := os.Geteuid()
|
||||
if uid == 0 || euid == 0 {
|
||||
setuid_err := "Refusing to run with root privileges when setuid() will not work!"
|
||||
errorLog.Println(setuid_err)
|
||||
log.Fatal(setuid_err)
|
||||
}
|
||||
}
|
|
@ -13,6 +13,9 @@ import (
|
|||
// and should pledge their own restrictions and unveil their own files.
|
||||
func enableSecurityRestrictions(config Config, errorLog *log.Logger) {
|
||||
|
||||
// Setuid to an unprivileged user
|
||||
DropPrivs(config, errorLog)
|
||||
|
||||
// Unveil the configured document base as readable.
|
||||
log.Println("Unveiling \"" + config.DocBase + "\" as readable.")
|
||||
err := unix.Unveil(config.DocBase, "r")
|
||||
|
|
13
security_other_unix.go
Normal file
13
security_other_unix.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
// +build linux,go1.16 aix darwin dragonfly freebsd illumos netbsd solaris
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
func enableSecurityRestrictions(config Config, errorLog *log.Logger) {
|
||||
|
||||
// Setuid to an unprivileged user
|
||||
DropPrivs(config, errorLog)
|
||||
}
|
Loading…
Add table
Reference in a new issue