├── .github └── workflows │ └── semgrep.yml ├── .gitignore ├── LICENSE ├── README.md ├── kubestalk.py ├── plugins ├── cadvisor.json ├── etcd-exposure.json ├── etcd-viewer.json ├── kubernetes-dashboard.json ├── kubernetes-operational-view.json ├── kubernetes-resource-report.json ├── kubernetes-web-view.json ├── paths-exposure.json ├── pods-exposure.json └── version-disclosure.json └── requirements.txt /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: {} 3 | pull_request: {} 4 | push: 5 | branches: 6 | - main 7 | - master 8 | paths: 9 | - .github/workflows/semgrep.yml 10 | schedule: 11 | # random HH:MM to avoid a load spike on GitHub Actions at 00:00 12 | - cron: 18 22 * * * 13 | name: Semgrep 14 | jobs: 15 | semgrep: 16 | name: semgrep/ci 17 | runs-on: ubuntu-20.04 18 | env: 19 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 20 | container: 21 | image: returntocorp/semgrep 22 | steps: 23 | - uses: actions/checkout@v4 24 | - run: semgrep ci 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | *.csv -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 0xInfection 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or other 11 | materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors may 14 | be used to endorse or promote products derived from this software without specific 15 | prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 23 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 25 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 26 | OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KubeStalk 2 | KubeStalk is a tool to discover Kubernetes and related infrastructure based attack surface from a black-box perspective. This tool is a community version of the tool used to probe for unsecured Kubernetes clusters around the internet during [Project Resonance - Wave 9](https://redhuntlabs.com/blog/unsecured-kubernetes-clusters-exposed/). 3 | 4 | ## Usage 5 | The GIF below demonstrates usage of the tool: 6 | 7 | ![tooldemo](https://user-images.githubusercontent.com/39941993/195374856-eb13c002-a619-425c-a819-cb90fff9af70.gif) 8 | 9 | ### Installation 10 | KubeStalk is written in Python and requires the `requests` library. 11 | 12 | To install the tool, you can clone the repository to any directory: 13 | ``` 14 | git clone https://github.com/redhuntlabs/kubestalk 15 | ``` 16 | Once cloned, you need to install the `requests` library using `python3 -m pip install requests` or: 17 | ``` 18 | python3 -m pip install -r requirements.txt 19 | ``` 20 | Everything is setup and you can use the tool directly. 21 | 22 | ### Command-line Arguments 23 | A list of command line arguments supported by the tool can be displayed using the `-h` flag. 24 | ```s 25 | $ python3 kubestalk.py -h 26 | 27 | +---------------------+ 28 | | K U B E S T A L K | 29 | +---------------------+ v0.1 30 | 31 | [!] KubeStalk by RedHunt Labs - A Modern Attack Surface (ASM) Management Company 32 | [!] Author: 0xInfection (RHL Research Team) 33 | [!] Continuously Track Your Attack Surface using https://redhuntlabs.com/nvadr. 34 | 35 | usage: ./kubestalk.py / 36 | 37 | Required Arguments: 38 | urls List of hosts to scan 39 | 40 | Optional Arguments: 41 | -o OUTPUT, --output OUTPUT 42 | Output path to write the CSV file to 43 | -f SIG_FILE, --sig-dir SIG_FILE 44 | Signature directory path to load 45 | -t TIMEOUT, --timeout TIMEOUT 46 | HTTP timeout value in seconds 47 | -ua USER_AGENT, --user-agent USER_AGENT 48 | User agent header to set in HTTP requests 49 | --concurrency CONCURRENCY 50 | No. of hosts to process simultaneously 51 | --verify-ssl Verify SSL certificates 52 | --version Display the version of KubeStalk and exit. 53 | ``` 54 | 55 | #### Basic Usage 56 | To use the tool, you can pass one or more hosts to the script. All targets passed to the tool must be [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986) complaint, i.e. must contain a scheme and hostname (and port if required). 57 | 58 | A basic usage is as below: 59 | ``` 60 | $ python3 kubestalk.py https://███.██.██.███:10250 61 | 62 | +---------------------+ 63 | | K U B E S T A L K | 64 | +---------------------+ v0.1 65 | 66 | [!] KubeStalk by RedHunt Labs - A Modern Attack Surface (ASM) Management Company 67 | [!] Author: 0xInfection (RHL Research Team) 68 | [!] Continuously Track Your Attack Surface using https://redhuntlabs.com/nvadr. 69 | 70 | [+] Loaded 10 signatures to scan. 71 | [*] Processing host: https://███.██.██.██:10250 72 | [!] Found potential issue on https://███.██.██.██:10250: Kubernetes Pod List Exposure 73 | [*] Writing results to output file. 74 | [+] Done. 75 | ``` 76 | 77 | #### HTTP Tuning 78 | HTTP requests can be fine-tuned using the `-t` (to mention HTTP timeouts), `-ua` (to specify custom user agents) and the `--verify-ssl` (to validate SSL certificates while making requests). 79 | 80 | #### Concurrency 81 | You can control the number of hosts to scan simultanously using the `--concurrency` flag. The default value is set to 5. 82 | 83 | #### Output 84 | The output is written to a CSV filea and can be controlled by the `--output` flag. 85 | 86 | A sample of the CSV output rendered in markdown is as belows: 87 | 88 | |host |path |issue |type |severity | 89 | |---------------------------|-----|----------------------------|--------------|------------------------------| 90 | |`https://█.█.█.█:10250`|/pods|Kubernetes Pod List Exposure|core-component|vulnerability/misconfiguration| 91 | |`https://█.█.█.█:443` |/api/v1/pods|Kubernetes Pod List Exposure|core-component|vulnerability/misconfiguration| 92 | |`http://█.█.██.█:80`|/|etcd Viewer Dashboard Exposure|add-on|vulnerability/exposure| 93 | |`http://██.██.█.█:80`|/|cAdvisor Metrics Web UI Dashboard Exposure|add-on|vulnerability/exposure| 94 | 95 | ## Version & License 96 | The tool is licensed under the [BSD 3 Clause License](LICENSE) and is currently at v0.1. 97 | 98 | *[`To know more about our Attack Surface Management platform, check out NVADR.`](https://redhuntlabs.com/nvadr)* 99 | -------------------------------------------------------------------------------- /kubestalk.py: -------------------------------------------------------------------------------- 1 | import csv, glob 2 | import sys, warnings 3 | import requests, re, json 4 | from urllib.parse import urljoin 5 | from argparse import ArgumentParser 6 | from concurrent.futures import ThreadPoolExecutor 7 | 8 | version = 'v0.1' 9 | writer = None 10 | fingerprints = list() 11 | warnings.filterwarnings('ignore') 12 | 13 | def_headers = { 14 | 'User-Agent' : 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36', 15 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 16 | 'Accept-Language' : 'en-US,en;q=0.9', 17 | 'Accept-Encoding' : 'identity', 18 | 'Sec-Fetch-Mode' : 'navigate', 19 | 'DNT' : '1', 20 | 'Connection' : 'close' 21 | } 22 | lackofart = ''' 23 | +---------------------+ 24 | | K U B E S T A L K | 25 | +---------------------+ %s 26 | 27 | [!] KubeStalk by RedHunt Labs - A Modern Attack Surface (ASM) Management Company 28 | [!] Author: 0xInfection (RHL Research Team) 29 | [!] Continuously Track Your Attack Surface using https://redhuntlabs.com/nvadr. 30 | ''' 31 | 32 | def loader(fname: str) -> None: 33 | ''' 34 | Loads the signatures from JSON 35 | ''' 36 | global fingerprints 37 | files = glob.glob(fname+'/*.json') 38 | for xfile in files: 39 | with open(xfile, 'r') as rf: 40 | fingerprints.append(json.load(rf)) 41 | print('[+] Loaded %d signatures to scan.' % len(fingerprints)) 42 | 43 | def make_request(host: str, path: str, timeout: int, ssl=False) -> str: 44 | ''' 45 | Makes a HTTP request to the URL specified 46 | ''' 47 | xurl = urljoin(host, path) 48 | req = requests.get(xurl, headers=def_headers, timeout=timeout, verify=ssl) 49 | if req is not None: 50 | return req.text 51 | 52 | def match_fps(path: str, resp: str) -> tuple: 53 | ''' 54 | Matches the fingerprints per path 55 | ''' 56 | for x in fingerprints: 57 | if isinstance(x['path'], list): 58 | for xpath in x['path']: 59 | if xpath == path: 60 | for rex in x['detector']: 61 | if re.search(rex, resp, re.I): 62 | return x['name'], x['type'], x['severity'] 63 | else: 64 | if x['path'] == path: 65 | for rex in x['detector']: 66 | if re.search(rex, resp, re.I): 67 | return x['name'], x['type'], x['severity'] 68 | 69 | def proc_host(host: str, timeout: int, ssl: bool) -> None: 70 | ''' 71 | Processes a single host 72 | ''' 73 | print('[*] Processing host:', host) 74 | global writer 75 | allpaths = list() 76 | issuecounter = 0 77 | 78 | for fp in fingerprints: 79 | if isinstance(fp['path'], list): 80 | allpaths.extend(fp['path']) 81 | else: 82 | allpaths.append(fp['path']) 83 | 84 | if '://' not in host: 85 | print('[-] Scheme not specified, fixing protocol with http (might not work correctly)...') 86 | host = 'http://' + host 87 | 88 | for path in set(allpaths): 89 | resp = make_request(host, path, timeout, ssl) 90 | xfresp = match_fps(path, resp) 91 | 92 | if xfresp: 93 | print('[!] Found potential issue on %s: %s' % (host, xfresp[0])) 94 | issuecounter += 1 95 | writer.writerow([host, path, xfresp[0], xfresp[1], xfresp[2]]) 96 | 97 | print('[*] %s issues found on %s' % (issuecounter, host)) 98 | 99 | def process_hosts(hosts: list, concurrency: int, timeout: int, ssl: bool) -> None: 100 | ''' 101 | Main wrapper around the engine 102 | ''' 103 | with ThreadPoolExecutor(max_workers=concurrency) as exec: 104 | for host in hosts: 105 | exec.submit(proc_host, str(host), timeout, ssl) 106 | 107 | def main(): 108 | ''' 109 | Main function to wrap all of them 110 | ''' 111 | print(lackofart % version) 112 | parser = ArgumentParser(usage='./kubestalk.py ') 113 | parser._action_groups.pop() 114 | 115 | required = parser.add_argument_group('Required Arguments') 116 | optional = parser.add_argument_group('Optional Arguments') 117 | 118 | required.add_argument('urls', nargs='*', help='List of hosts to scan') 119 | 120 | optional.add_argument('-o', '--output', help='Output path to write the CSV file to', dest='output', default='kubestalk-results.csv') 121 | optional.add_argument('-f', '--sig-dir', help='Signature directory path to load', dest='sig_dir', default='plugins') 122 | optional.add_argument('-t', '--timeout', help='HTTP timeout value in seconds', dest='timeout', type=int, default=10) 123 | optional.add_argument('-ua', '--user-agent', help='User agent header to set in HTTP requests', dest='user_agent') 124 | optional.add_argument('--concurrency', help='No. of hosts to process simultaneously', dest='concurrency', type=int, default=5) 125 | optional.add_argument('--verify-ssl', help='Verify SSL certificates', dest='ssl', action='store_true', default=False) 126 | optional.add_argument('--version', help='Display the version of KubeStalk and exit.', dest='version', action='store_true') 127 | 128 | args = parser.parse_args() 129 | 130 | if not args.urls and not args.version: 131 | print('[-] You must supply at least a single host to scan.') 132 | sys.exit(1) 133 | 134 | if args.version: 135 | print('[+] KubeStalk Version:', version) 136 | sys.exit() 137 | 138 | if args.sig_dir: 139 | loader(args.sig_dir) 140 | 141 | global writer, def_headers 142 | xfwriter = open(args.output, 'w+') 143 | writer = csv.writer(xfwriter) 144 | writer.writerow(['host', 'path', 'issue', 'type', 'severity']) 145 | 146 | if args.user_agent: 147 | def_headers['User-Agent'] = args.user_agent 148 | 149 | process_hosts(args.urls, args.concurrency, args.timeout, args.ssl) 150 | 151 | print('[*] Writing results to output file.') 152 | xfwriter.flush() 153 | xfwriter.close() 154 | print('[+] Done.') 155 | 156 | if __name__ == '__main__': 157 | main() 158 | -------------------------------------------------------------------------------- /plugins/cadvisor.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cAdvisor Metrics Web UI Dashboard Exposure", 3 | "protocol": "http", 4 | "path": "/", 5 | "detector": [ 6 | "cAdvisor" 7 | ], 8 | "platform": "on-premise/cloud", 9 | "type": "add-on", 10 | "severity": "vulnerability/exposure" 11 | } -------------------------------------------------------------------------------- /plugins/etcd-exposure.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Unauthenticated etcd Exposure", 3 | "protocol": "http", 4 | "path": "/version", 5 | "detector": [ 6 | "\"etcdserver\":\"([\\d\\.]+?)\",[\\s\\n]*?\"etcdcluster\":" 7 | ], 8 | "platform": "cloud", 9 | "type": "database", 10 | "severity": "vulnerability/misconfiguration" 11 | } -------------------------------------------------------------------------------- /plugins/etcd-viewer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "etcd Viewer Dashboard Exposure", 3 | "protocol": "http", 4 | "path": "/", 5 | "detector": [ 6 | "etcd viewer", 7 | "Kubernetes Dashboard" 7 | ], 8 | "platform": "on-premise/cloud", 9 | "type": "core-component", 10 | "severity": "vulnerability/misconfiguration" 11 | } -------------------------------------------------------------------------------- /plugins/kubernetes-operational-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kubernetes Operational View Dashboard Exposure", 3 | "protocol": "http", 4 | "path": "/", 5 | "detector": [ 6 | "Kubernetes Operational View[\\s\\d\\.]+?" 7 | ], 8 | "platform": "on-premise/cloud", 9 | "type": "add-on", 10 | "severity": "vulnerability/exposure" 11 | } -------------------------------------------------------------------------------- /plugins/kubernetes-resource-report.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kubernetes Resource Report Dashboard Exposure", 3 | "protocol": "http", 4 | "path": "/", 5 | "detector": [ 6 | "Overview - Kubernetes Resource Report" 7 | ], 8 | "platform": "on-premise/cloud", 9 | "type": "add-on", 10 | "severity": "vulnerability/exposure" 11 | } -------------------------------------------------------------------------------- /plugins/kubernetes-web-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kubernetes Web View Dashboard Exposure", 3 | "protocol": "http", 4 | "path": "/", 5 | "detector": [ 6 | "Kubernetes Web View", 7 | "