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
|
executable (e.g. `/var/gemini/cgi-bin/scripty.py`) while the variable
|
||||||
`PATH_INFO` will contain the remainder (e.g. `foo/bar/baz`).
|
`PATH_INFO` will contain the remainder (e.g. `foo/bar/baz`).
|
||||||
|
|
||||||
It is very important to be aware that programs written in Go are
|
Molly Brown itself tries very hard to avoid being tricked into serving
|
||||||
*unable* to reliably change their UID once started, due to how
|
content that isn't supposed to be served, but it is completely unable
|
||||||
goroutines are implemented on unix systems. As an unavoidable
|
to impose any control over what CGI processes can or can't go after
|
||||||
consequence of this, CGI processes started by Molly Brown are run as
|
they are started! Where possible, Molly Brown will use the operating
|
||||||
the same user as the server process. This means CGI processes
|
system's security features to reduce risk, but it is your
|
||||||
necessarily have read and write access to the server logs and to the
|
responsibility to understand what it can and cannot do and weigh the
|
||||||
TLS private key. There is no way to work around this. As such you
|
risks accordingly:
|
||||||
must be extremely careful about only running trustworthy CGI
|
|
||||||
applications, ideally only applications you have carefully written
|
When compiled on GNU/Linux with Go version 1.16 or later, or on any
|
||||||
yourself. Allowing untrusted users to upload arbitrary executable
|
other unix operating system with any version of Go, Molly Brown will
|
||||||
files into a CGI path is a serious security vulnerability.
|
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
|
SCGI applications must be started separately (i.e. Molly Brown expects
|
||||||
them to already be running and will not attempt to start them itself),
|
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
|
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.
|
// and should pledge their own restrictions and unveil their own files.
|
||||||
func enableSecurityRestrictions(config Config, errorLog *log.Logger) {
|
func enableSecurityRestrictions(config Config, errorLog *log.Logger) {
|
||||||
|
|
||||||
|
// Setuid to an unprivileged user
|
||||||
|
DropPrivs(config, errorLog)
|
||||||
|
|
||||||
// Unveil the configured document base as readable.
|
// Unveil the configured document base as readable.
|
||||||
log.Println("Unveiling \"" + config.DocBase + "\" as readable.")
|
log.Println("Unveiling \"" + config.DocBase + "\" as readable.")
|
||||||
err := unix.Unveil(config.DocBase, "r")
|
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…
Reference in a new issue