implement CGICommand as a CGI program override

This commit is contained in:
Travis J Parker 2022-06-13 22:57:51 +02:00
parent e42c366565
commit c9838f3f29
3 changed files with 32 additions and 13 deletions

View File

@ -285,17 +285,14 @@ 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.
Be aware that CGI processes started by Molly Brown are run as the same
user as the server process. This means that by default, CGI processes
have read and write access to the server logs and to the TLS private
key. Set a `CGICommand` to something that drop privileges before
running `$SCRIPT_PATH` to add guard rails to CGI processes provided by
other system users. Otherwise, allowing untrusted users to upload
arbitrary executable files into a CGI path is a serious security
vulnerability.
SCGI applications must be started separately (i.e. Molly Brown expects
them to already be running and will not attempt to start them itself),
@ -314,6 +311,11 @@ startup, database connection etc. on each request).
if wildcards are used, the path should *not* end in a trailing slash
- this appears to be a peculiarity of the Go standard library's
`filepath.Glob` function.
* `CGICommand`: An executable command which will be run instead of
cgi programs. It can still run the cgi program by executing
`$SCRIPT_PATH`, but before doing so it should change the effective
user, chroot into a limited file system, and take any other steps
appropriate to sandbox user-provided programs.
* `SCGIPaths`: In this section of the config file, keys are URL path
prefixes and values are filesystem paths to unix domain sockets.
Any request for a URL whose path begins with one of the specified

View File

@ -2,10 +2,10 @@ package main
import (
"errors"
"github.com/BurntSushi/toml"
"log"
"os"
"path/filepath"
"github.com/BurntSushi/toml"
)
type Config struct {
@ -24,6 +24,7 @@ type Config struct {
PermRedirects map[string]string
MimeOverrides map[string]string
CGIPaths []string
CGICommand string
SCGIPaths map[string]string
CertificateZones map[string][]string
DirectorySort string
@ -61,6 +62,7 @@ func getConfig(filename string) (Config, error) {
config.TempRedirects = make(map[string]string)
config.PermRedirects = make(map[string]string)
config.CGIPaths = make([]string, 0)
config.CGICommand = ""
config.SCGIPaths = make(map[string]string)
config.DirectorySort = "Name"

View File

@ -13,6 +13,8 @@ import (
"strconv"
"strings"
"time"
"github.com/google/shlex"
)
func handleCGI(config Config, path string, cgiPath string, URL *url.URL, log *LogEntry, errorLog *log.Logger, conn net.Conn) {
@ -47,10 +49,23 @@ func handleCGI(config Config, path string, cgiPath string, URL *url.URL, log *Lo
// Prepare environment variables
vars := prepareCGIVariables(config, URL, conn, scriptPath, pathInfo)
// Use the CGICommand if provided
var script []string
if config.CGICommand != "" {
spl, err := shlex.Split(config.CGICommand)
if err != nil {
errorLog.Printf("Failed to split CGICommand: %s\n", err.Error())
return
}
script = spl
} else {
script = []string{scriptPath}
}
// Spawn process
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, scriptPath)
cmd := exec.CommandContext(ctx, script[0], script[1:]...)
cmd.Env = []string{}
for key, value := range vars {
cmd.Env = append(cmd.Env, key+"="+value)