├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── http-swagger.nse ├── kube-recon.sh └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | kube-recon 3 | kubectl 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.7.3 2 | WORKDIR /go/src/github.com/octarinesec/kube-recon 3 | RUN apt update && apt install -y libpcap-dev 4 | COPY main.go . 5 | RUN go get -v 6 | RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' main.go 7 | 8 | FROM alpine 9 | RUN apk --no-cache add ca-certificates curl nmap libpcap-dev nmap-scripts bash 10 | WORKDIR / 11 | RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.11.0/bin/linux/amd64/kubectl && chmod +x kubectl 12 | COPY --from=0 /go/src/github.com/octarinesec/kube-recon/main /kube-recon 13 | COPY http-swagger.nse . 14 | ENTRYPOINT ["/bin/sh"] 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 octarinesec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reconnaissance Test for Kubernetes 2 | 3 | The purpose of this tool is to gather maximum information from a pod inside kubernetes cluster. 4 | 5 | The output report shows pods/services that are visible and accessible to help better understand where security is 6 | not tight enough 7 | 8 | ## Running 9 | 10 | You can run the tool via already build docker which you deploy inside the cluster or install 11 | the prerequisite and run the tool directly on an already running pod. The recommended way is to run the docker 12 | 13 | ## Kubernetes 14 | 15 | ```bash 16 | kubectl run kuberecon1 --tty -i --image octarinesec/kube-recon:v11 17 | ./kube_recon # (This is inside the docker) 18 | ./kube_recon -help 19 | ./kube_recon -skip-nmap (full nmap might take alot of time) 20 | ``` 21 | 22 | Example Output: 23 | ```bash 24 | 2018/10/19 07:22:02 Testing K8S API permissions 25 | 2018/10/19 07:22:03 Your K8S API Server is configured properlly 26 | 2018/10/19 07:22:03 Trying to download EICAR file 27 | 2018/10/19 07:22:03 Downloaded EICAR successfully. No malware protection is in place 28 | ``` 29 | 30 | ## Development 31 | 32 | ### Requiremnts: 33 | * Go version > 1.10 34 | 35 | ### Running 36 | ```bash 37 | go get 38 | go run main.go 39 | ``` 40 | ### Building 41 | ```bash 42 | go build 43 | sudo ./kube_recon 44 | ``` 45 | 46 | ### Building Docker Image 47 | ```bash 48 | docker build -t . 49 | ``` 50 | -------------------------------------------------------------------------------- /http-swagger.nse: -------------------------------------------------------------------------------- 1 | local http = require "http" 2 | local nmap = require "nmap" 3 | local shortport = require "shortport" 4 | local strbuf = require "strbuf" 5 | local table = require "table" 6 | 7 | description = [[ 8 | Checks for swagger documentations entries in /robots.txt on a web server. 9 | 10 | The higher the verbosity or debug level, the more disallowed entries are shown. 11 | ]] 12 | 13 | --- 14 | --@output 15 | -- 80/tcp open http syn-ack 16 | -- | Path /swagger.json exist 17 | 18 | 19 | 20 | author = "Yevgeny Pats" 21 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 22 | categories = {"default", "discovery", "safe"} 23 | 24 | portrule = shortport.http 25 | local last_len = 0 26 | 27 | -- parse all disallowed entries in body and add them to a strbuf 28 | action = function(host, port) 29 | local dis_count, noun 30 | local paths = {"/swagger.json", 31 | "/swagger", 32 | "/v1/swagger.json", 33 | "/v1/swagger"} 34 | for i,v in ipairs(paths) do 35 | local answer = http.get(host, port, v) 36 | if answer.status == 200 then 37 | return "\n Path " .. v .. " exist" 38 | end 39 | end 40 | 41 | return nil 42 | end 43 | -------------------------------------------------------------------------------- /kube-recon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.11.0/bin/linux/amd64/kubectl 3 | chmod +x ./kubectl 4 | ./kubectl get pods 5 | 6 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net" 7 | "net/http" 8 | "os/exec" 9 | "strings" 10 | ) 11 | 12 | 13 | func exec_command_wrapper(name string, args ...string) string { 14 | log.Printf("Running %s %s", name, strings.Join(args, " ")) 15 | cmd := exec.Command(name, args...) 16 | stdouterrr, err := cmd.CombinedOutput() 17 | if err != nil { 18 | print(string(stdouterrr)) 19 | log.Fatal(err) 20 | } 21 | return string(stdouterrr) 22 | } 23 | 24 | type KubeRecon struct { 25 | ip_addresses map[string]bool 26 | } 27 | 28 | func newKubeRecon() *KubeRecon { 29 | ips := map[string]bool{} 30 | return &KubeRecon{ips} 31 | } 32 | 33 | func (k* KubeRecon) get_ip_addr() { 34 | log.Printf("Getting local ip address and subnet") 35 | ifaces, err := net.Interfaces() 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | // handle err 40 | for _, i := range ifaces { 41 | addrs, err := i.Addrs() 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | if (i.Flags & net.FlagLoopback) == net.FlagLoopback { 46 | continue 47 | } 48 | // handle err 49 | for _, addr := range addrs { 50 | var ip net.IP 51 | switch v := addr.(type) { 52 | case *net.IPNet: 53 | ip = v.IP 54 | case *net.IPAddr: 55 | ip = v.IP 56 | } 57 | if ip.To4() != nil && !(strings.HasPrefix(ip.String(), "172")) { 58 | k.ip_addresses[ip.String() + "/24"] = true 59 | } 60 | // process IP address 61 | } 62 | } 63 | } 64 | 65 | func (k *KubeRecon) test_rbac() { 66 | log.Printf("Testing K8S API permissions") 67 | // exec_command_wrapper("mv", "./kubectl", "/usr/local/bin/kubectl") 68 | stdouterr, err := exec.Command("./kubectl", "get", "pods").CombinedOutput() 69 | if err != nil { 70 | log.Print("Your K8S API Server is configured properlly") 71 | } else { 72 | log.Print("Your K8S API Server permissions are wide open. Please consider using RBAC") 73 | log.Print("Accessible Pods:") 74 | lines := strings.Split(string(stdouterr), "\n") 75 | for _, row := range lines[1 : len(lines)-1] { 76 | ip := strings.Split(row, " ")[0] 77 | log.Printf("%s", ip) 78 | k.ip_addresses[ip] = true 79 | } 80 | } 81 | } 82 | 83 | func (k *KubeRecon) nmap() { 84 | log.Print("Running Nmap on the discovered IPs") 85 | k.get_ip_addr() 86 | for ip := range k.ip_addresses { 87 | output := exec_command_wrapper("nmap", 88 | "-p", "[1-65535]", 89 | "--script", "http-swagger.nse", 90 | "--script", "cassandra-brute", 91 | "--script", "http-brute", 92 | "--script", "http-proxy-brute", 93 | "--script", "ms-sql-brute", 94 | "--script", "mysql-brute", 95 | "--script", "pgsql-brute", 96 | "--script", "mongodb-brute", 97 | ip) 98 | log.Printf(output) 99 | } 100 | } 101 | 102 | func (k * KubeRecon) test_eicar_file() { 103 | log.Print("Trying to download EICAR file") 104 | resp, err := http.Get("https://www.eicar.org/download/eicar.com.txt") 105 | if err != nil { 106 | log.Fatalln(err) 107 | } 108 | if resp.StatusCode == 200 { 109 | log.Print("Downloaded EICAR successfully. No malware protection is in place") 110 | } else { 111 | log.Print("EICAR download was blocked. Malware protection is in place") 112 | } 113 | } 114 | 115 | func (k *KubeRecon) run() { 116 | 117 | skip_rbac := flag.Bool("skip-rbac", false, "Skip RBAC test") 118 | skip_nmap := flag.Bool("skip-nmap", false, "Skip NMAP scan") 119 | skip_eicar := flag.Bool("skip-eicar", false, "Skip EICAR test") 120 | 121 | flag.Parse() 122 | // check_root() 123 | 124 | if !*skip_rbac { 125 | k.test_rbac() 126 | } 127 | 128 | if !*skip_nmap { 129 | k.nmap() 130 | } 131 | 132 | if !*skip_eicar { 133 | k.test_eicar_file() 134 | } 135 | 136 | } 137 | 138 | func main() { 139 | 140 | k := newKubeRecon() 141 | k.run() 142 | 143 | } 144 | --------------------------------------------------------------------------------