diff --git a/cmd/singularity-server/main.go b/cmd/singularity-server/main.go index 5a79b42..8609f8d 100644 --- a/cmd/singularity-server/main.go +++ b/cmd/singularity-server/main.go @@ -5,7 +5,10 @@ import ( "fmt" "log" "net/http" + "os/user" + "runtime" "strconv" + "syscall" "time" "github.com/nccgroup/singularity" @@ -32,6 +35,8 @@ func (a *arrayPortFlags) Set(value string) error { func initFromCmdLine() *singularity.AppConfig { var appConfig = singularity.AppConfig{} var myArrayPortFlags arrayPortFlags + var f = false + var enableLinuxTProxySupport = &f var responseIPAddr = flag.String("ResponseIPAddr", "192.168.0.1", "Specify the attacker host IP address that will be rebound to the victim host address using strategy specified by flag \"-DNSRebingStrategy\"") @@ -42,7 +47,11 @@ func initFromCmdLine() *singularity.AppConfig { var dangerouslyAllowDynamicHTTPServers = flag.Bool("dangerouslyAllowDynamicHTTPServers", false, "DANGEROUS if the flag is set (to anything). Specify if any target can dynamically request Singularity to allocate an HTTP Server on a new port.") var WsHttpProxyServerPort = flag.Int("WsHttpProxyServerPort", 3129, "Specify the attacker HTTP Proxy Server and Websockets port that permits to browse hijacked client services.") - var enableLinuxTProxySupport = flag.Bool("enableLinuxTProxySupport", false, "Specify whether to enable Linux TProxy support or not. Useful to listen on many ports with an appropriate iptables configuration.") + if runtime.GOOS != "darwin" { + enableLinuxTProxySupport = flag.Bool("enableLinuxTProxySupport", false, "Specify whether to enable Linux TProxy support or not. Useful to listen on many ports with an appropriate iptables configuration.") + } + var dontDropPrivileges = flag.Bool("dontDropPrivileges", false, "Don't drop privileges if running as the root user. By default privileges will be dropped to the 'nobody' user and group") + var dropToUserName = flag.String("dropToUserName", "nobody", "Specify the username of the account you would like to drop privileges to, defaults to 'nobody'") flag.Var(&myArrayPortFlags, "HTTPServerPort", "Specify the attacker HTTP Server port that will serve HTML/JavaScript files. Repeat this flag to listen on more than one HTTP port.") var dnsServerBindAddr = flag.String("DNSServerBindAddr", "0.0.0.0", "Specify the IP address the DNS server will bind to, defaults to 0.0.0.0") @@ -65,6 +74,8 @@ func initFromCmdLine() *singularity.AppConfig { appConfig.DNSServerBindAddr = *dnsServerBindAddr appConfig.WsHTTPProxyServerPort = *WsHttpProxyServerPort appConfig.EnableLinuxTProxySupport = *enableLinuxTProxySupport + appConfig.DontDropPrivileges = *dontDropPrivileges + appConfig.DropToUserName = *dropToUserName return &appConfig } @@ -114,7 +125,6 @@ func main() { if httpServerErr != nil { log.Fatalf("Main: Could not start main HTTP Server instance: %v", httpServerErr) } - } wsHTTPProxyServer := singularity.NewHTTPProxyServer(hss.WsHTTPProxyServerPort, dcss, wscss, hss) @@ -124,6 +134,33 @@ func main() { log.Fatalf("Main: Could not start proxy Webssockets/HTTP Server instance: %v", wsHTTPProxyServerErr) } + if syscall.Getuid() == 0 && appConfig.DontDropPrivileges == false { + log.Printf("Main: Running as root, dropping privileges to user %s\n", appConfig.DropToUserName) + dropToUser, err := user.Lookup(appConfig.DropToUserName) + if err != nil { + log.Fatalf("User not found: %s\n", err) + } + + uid, err := strconv.ParseInt(dropToUser.Uid, 10, 64) + if err != nil { + log.Fatalf("Main: Could not parse UID for user %s, error: %s\n", appConfig.DropToUserName, err) + } + + gid, err := strconv.ParseInt(dropToUser.Gid, 10, 64) + if err != nil { + log.Fatalf("Main: Could not parse GID for user %s\n", appConfig.DropToUserName) + } + if err := syscall.Setgid(int(gid)); err != nil { + log.Fatalf("Main: Error setting GID %d: %s\n", gid, err) + } + + if err := syscall.Setuid(int(uid)); err != nil { + log.Fatalf("Main: Error setting UID %d: %s\n", uid, err) + } + + log.Printf("Main: Running as %s, %d:%d\n", appConfig.DropToUserName, uid, gid) + } + expiryDuration := time.Duration(appConfig.ResponseReboundIPAddrtimeOut) * time.Second expireClientStateTicker := time.NewTicker(expiryDuration) diff --git a/singularity.go b/singularity.go index 7681e5b..0a11f0e 100644 --- a/singularity.go +++ b/singularity.go @@ -3,6 +3,7 @@ package singularity import ( "context" crand "crypto/rand" + "encoding/binary" "encoding/hex" "encoding/json" "errors" @@ -15,10 +16,10 @@ import ( "net/http" "os" "path/filepath" + "runtime" "strconv" "strings" "sync" - "syscall" "time" "github.com/miekg/dns" @@ -56,6 +57,20 @@ type AppConfig struct { DNSServerBindAddr string WsHTTPProxyServerPort int EnableLinuxTProxySupport bool + DontDropPrivileges bool + DropPrivilegesUserName bool + DropToUserName string +} + +// Parse IP address from a string or 32-bit integer +func parseIP(s string) net.IP { + // parse ip and if decimal, convert to IP + if ipInt, err := strconv.ParseUint(s, 10, 32); err == nil { + ip := make(net.IP, 4) + binary.BigEndian.PutUint32(ip, uint32(ipInt)) + return ip + } + return net.ParseIP(s) } // GenerateRandomString returns a secure random hexstring, 20 chars long @@ -145,22 +160,24 @@ func NewDNSQuery(qname string) (*DNSQuery, error) { return name, errors.New("cannot parse DNS query") } - if net.ParseIP(elements[0]) == nil { + if parseIP(elements[0]) == nil { return name, errors.New("cannot parse IP address of first host in DNS query") - } - name.ResponseIPAddr = elements[0] + name.ResponseIPAddr = parseIP(elements[0]).String() if elements[1] != "localhost" { - elements[1] = strings.Replace(elements[1], "_", "-", -1) - if net.ParseIP(elements[1]) == nil && golang.IsDomainName(elements[1]) == false { + if parseIP(elements[1]) != nil { + name.ResponseReboundIPAddr = parseIP(elements[1]).String() + } else if golang.IsDomainName(elements[1]) != false { + name.ResponseReboundIPAddr = elements[1] + } else { return name, errors.New("cannot parse IP address or CNAME of second host in DNS query") } + } else { + name.ResponseReboundIPAddr = elements[1] } - name.ResponseReboundIPAddr = elements[1] - name.Session = elements[2] if len(name.Session) == 0 { @@ -775,17 +792,6 @@ type HTTPServerError struct { Port string } -// Linux Transparent Proxy Support -// https://www.kernel.org/doc/Documentation/networking/tproxy.txt -// e.g. `sudo iptables -t mangle -I PREROUTING -d ext_ip_address -// -p tcp --dport 8080 -j TPROXY --on-port=80 --on-ip=ext_ip_address -// will redirect external port 8080 on port 80 of Singularity -func useIPTransparent(network, address string, conn syscall.RawConn) error { - return conn.Control(func(descriptor uintptr) { - syscall.SetsockoptInt(int(descriptor), syscall.IPPROTO_IP, syscall.IP_TRANSPARENT, 1) - }) -} - // StartHTTPServer starts an HTTP server // and adds it to dynamic (if dynamic is true) or static HTTP Store func StartHTTPServer(s *http.Server, hss *HTTPServerStoreHandler, dynamic bool, tproxy bool) error { @@ -793,6 +799,13 @@ func StartHTTPServer(s *http.Server, hss *HTTPServerStoreHandler, dynamic bool, var err error var l net.Listener + if runtime.GOOS == "darwin" { + if tproxy == true { + log.Printf("HTTP: Transparent proxy support is not availible on macOS\n") + } + tproxy = false + } + if tproxy == true { listenConfig := &net.ListenConfig{Control: useIPTransparent} l, err = listenConfig.Listen(context.Background(), "tcp", s.Addr) diff --git a/singularity_darwin.go b/singularity_darwin.go new file mode 100644 index 0000000..b5ade0e --- /dev/null +++ b/singularity_darwin.go @@ -0,0 +1,8 @@ +package singularity + +import "syscall" + +// Currently not supported on macOS, this is a blank function to help compilation +func useIPTransparent(_, _ string, _ syscall.RawConn) error { + return nil +} diff --git a/singularity_linux.go b/singularity_linux.go new file mode 100644 index 0000000..b7ab75e --- /dev/null +++ b/singularity_linux.go @@ -0,0 +1,13 @@ +package singularity + +// Linux Transparent Proxy Support +// https://www.kernel.org/doc/Documentation/networking/tproxy.txt +// e.g. `sudo iptables -t mangle -I PREROUTING -d ext_ip_address +// -p tcp --dport 8080 -j TPROXY --on-port=80 --on-ip=ext_ip_address +// will redirect external port 8080 on port 80 of Singularity + +func useIPTransparent(network, address string, conn syscall.RawConn) error { + return conn.Control(func(descriptor uintptr) { + syscall.SetsockoptInt(int(descriptor), syscall.IPPROTO_IP, syscall.IP_TRANSPARENT, 1) + }) +}