qdPM v9.1 Authenticated RCE Exploit

This is a written guide that validates the PoC submitted for the qdPM 9.1 Authenticated RCE vulnerability (CVE-2020–7246) disclosed at the start of this year. This post features the following sections:

  1. An overview of the CVE
  2. A quick walk through of setting up the vulnerable lab and application
  3. A walk through of successfully exploiting the app using the documented CVE information
  4. An analysis of a custom Python script designed to launch an automated attack using a malicious web shell
  5. Final thoughts, reflections, and remediation strategies

Vulnerability Information

Official CVE Description:

A remote code execution (RCE) vulnerability exists in qdPM 9.1 and earlier. An attacker can upload a malicious PHP code file via the profile photo functionality, by leveraging a path traversal vulnerability in the users['photo_preview'] delete photo feature, allowing bypass of .htaccess protection. NOTE: this issue exists because of an incomplete fix for CVE-2015-3884.

What is qdPM?

“qdPM is a free web-based [LAMP] project management tool suitable for a small team working on multiple projects. It is fully configurable. You can easy manage Projects, Tasks and People. Customers interact using a Ticket System that is integrated into Task management.”

Installing qdPM in Ubuntu 19.10

I already had a Ubuntu 19.10 Desktop VM installed handy, so I used that to host the dqPM software. If I were actually deploying this I would likely be using a server distribution of some kind, but the GUI did make the installation and analysis go by a bit easier.

Installing LAMP on Ubuntu

qdPM is a classic LAMP application. Thankfully LAMP installation is fairly easy on Linux these days, but to refresh the commands I needed to get set up I followed this guide.

Installing qdPM and Setting Up the Low Priv. User

The official dqPM installation documentation was really lackluster, and only contained a few FAQs. Thankfully, the folks over at LinuxCloudVPS wrote up a great guide on how to install the platform step-by-step. I had to troubleshoot some MySQL commands to get the database working, but I ended up getting the platform installed and configured without too much headache. I set up the application with the following credentials:

Database Information

  • User: root
  • Pasword: ubuntu

qdPM Admin User

Once I was logged in as admin, I needed to make a low-privilege user for the attack to work:

Low Privilege User:

Note: Yes, those credentials are awful, but for the lab I figured I would keep it simple.

Initial Probing + Additional Config

I wanted to see if I could probe the application to discover the vulnerable behavior. I logged in as the newly created jsmith@example.com user and headed over to the “My Details” tab (which is the space where users can edit personal information and upload/modify their profile picture).

I went to upload an initial photo (thanks Unsplash for a generic businessman photo). But I got hit with a PHP error. Some quick troubleshooting revealed that I didn’t have the php-gd library installed. After I got that downloaded on Ubuntu, I was able to upload the photo no problem:

Attempting Manual File Uploads + Reverse Web Shell

I wanted to then see if the upload feature had any initial protection so I uploaded the following:

  • upload.txt (A simple .txt file) (SUCCESS) (Could view in browser)
  • upload.py (A simple Python file) (SUCCESS) (Prompted to download)
  • upload.php (A simple PHP file) (SUCCESS) (Viewed results from php echo statement)

At this point I was surprised that all three of these filetypes were allowed to uploaded and accessed. I wasn’t running anything malicious, but I figured that only image file extensions would be allowed to be uploaded (as this is common practice to secure file uploads). I opted to upload a PHP reverse shell and set up a nc listener locally to see if it would execute the script. Lo and behold it did:

Taking Another Look At The Vulnerability + .htaccess fix

This is where I became initially confused, as I downloaded the most recent version of the qdPM platform (9.1). What’s stranger is that CVE-2015–3884 (note the year) was the first time that this kind of arbitrary file upload vulnerability was reported on the qdPM platform. According to the older CVE, the platform allowed an “unrestricted file upload” to a range of places, including the myAccount profile photo.

However, if we look at the newly reported vulnerability (CVE-2020–7246), supposedly a .htaccess file was implemented to prevent malicious filetypes from being executed. However, as the new CVE reveals, this was considered an improper fix as it was possible to remove the .htaccess file via a directory traversal attack and then upload the malicious PHP script to invoke RCE.

I went looking at the root directory for a .htaccess file, but didn't find anything. I was then planning to make my own, and was reading that it was recommended that you place a .htaccess file into the folder that you want to prevent access to. After some digging I did find that qdPM came shipped with a .htaccess file restricting executable uploads:

I assumed that something about Apache was getting in the way from allowing the file from doing its job, so after doing some troubleshooting I discovered that you have to configure the Apache service to allow custom .htaccess files to be enabled. Once I switched these config settings in the /etc/apache.conf file to accept the .htaccess, I restarted Apache and attempted to upload a web shell. The file was successfully uploaded, but I was not allowed to view the resource (and thus execute the script):

Attempting To Launch The Attack (Manually)

Now that I had it configured to “current specs,” I wanted to see if I could manually recreate the attack based on the published PoC. It’s not clear who this is published this document, but it seems to be legit as it was included in the official CVE entry. According to the PoC, to exploit the app I would need to:

  1. Login as normal user & visit http://[site_domain]/qpdm/index.php/myAccount
  2. Check the “Remove photo” checkbox and save to intercept the request
  3. Intercept the request on submitting the form (save) by selecting the checkbox 'Remove Photo'
  4. Modify the request parameter users['photp_preview'] to ../.htaccess and forward the request.

Launching Burpsuite and Monitoring the Traffic

According to the PoC, the directory traversal attack occurs when a user attempts to remove their profile photo. I launched Burpsuite and configured Firefox to point all HTTP traffic to the Burp proxy. From there I went to the myAccount page and checked the “remove photo” box:

Once I hit “submit” I got two different requests to intercept. The first was simple and is part of the “checkUser” script. Nothing too exciting here:

However, the second request is where the vulnerable information is, which is being pulled from the myAccounts form data. I have highlighted the two values that we need in order to leverage this attack:

To break down what’s going on, the second highlighted portion is the value being pushed to the PHP script that deletes a the photo. The script looks like this in the /core/apps/qdPM/modules/myAccount/actions/actions.class.php file of the qdPM source code:

Notice the value is currently grabbing my old php-reverse-shell.php script that I attempted to upload (as it thinks that's my current profile photo). If I change the old value to .htaccess then the fully built URI developed by the script will look like unlink("/uploads/users/.htaccess");:

And as predicted (and also described in the PoC) when I forwarded this request the .htaccess was removed from the /uploads/user directory. Thus, we now have the ability to bypass now access our uploaded PHP scripts.

It is important to realize that this exploit is more than just a means to upload scripts. This exploit also allows attackers to effectively remove any file that they would like from the server (within the scope of Apache).

Reattempting the Web Shell (Getting RCE)

After removing the .htaccess file in the /uploads/user directory I reattempted to upload and access the PHP web shell (configuring it to now reach out to my Kali machine) but was hit with the same "forbidden" error as before. At first I thought that Apache needed to restart in order to notice that the .htaccess file was gone, but in doing research I found that .htaccess files are read in real time and don't require a restart.

I went back and more carefully read the PoC and found that a .htaccess file also existed in the parent /uploads/ directory. This means that I needed to push an additional Burp request targeting ../.htaccess as a means to move up from /uploads/users/.htaccess to /uploads/.htaccess

Once both .htaccess files were gone, I was able to successfully establish a nc connection using the PHP backdoor.

Building an Automated Payload Deployer (qdPM9.1_exploit.py)

I chose to build the automated deployer in Python as it’s my favorite scripting language, and there some good libraries for making web requests and parsing HTML. I wanted the script to run much like a Metasploit module, in that you go from exploit to a shell all in the same terminal.

To build this exploit, I knew that I would need to do the following:

  1. Create a unique requests session and log in
  2. Send the requests needed to remove both of the default .htaccess files
  3. Upload a PHP web shell (I used the one published by pentestmonkey)
  4. Create a listener and handle data transmission
  5. Force the uploaded payload to run and connect to the listener and spawn a shell

To see what I came up with, check out my Github Repo:

While I am not a professional programmer, I am fairly happy with the end result. “Version 1” of the exploit was strung together quickly and featured a lot of redundant code. “Version 2” (the one linked above) featured more modular functions to reduce redundancy as well as make the code much more readable. I also added ton of comments.

If I did consider building a “Version 3” it would likely include some error handling as well as possibly the ability to supply the custom values via command-line arguments.

I also included the PHP payload that I used to test when I was building the app in case it’s quirky and does not play well with others. In addition, I downloaded the currently vulnerable qdPM 9.1 .zip file to archive it in case anyone wanted to work with this exploit in the future (assuming that the devs patch this issue).

Comparing Against Other Automated Scripts

I was able to successfully avoid looking at the exploit published on the exploit-db while building my own. When I finally did review the PoC script I found that while it was significantly less complex than my script, it was missing the creation of a listener and featured some clunky code. Interestingly, they were using far less imports that I was, but they were most notably not using Beautiful Soup to help parse the requested html files. Instead they opted to manually parse the data using the html function from the lxml library. One bonus they had was they used argparse to enable arguments to be taken from the CLI as opposed to modifying the script like I currently have.

I feel like my script is stronger than the published PoC as it features a more seamless exploit, and it also features much less redundant code. However, I think that my addition of the listening socket added quite a bit of complexity to my own script and could have been omitted in favor of simply setting up my own nc listener in a separate terminal.

Recommended Remediation

It seems a little baffling to me that when the initial arbitrary file upload vulnerability was published (CVE-2015–3884) that the developers didn’t just revise the actual PHP code and do some work around validating uploaded files. Specifically, the block of code that seems to be the culprit can be found in the /core/apps/qdPM/modules/myAccount/actions/actions.class.php script:

Nowhere in this block of code does it actually check if the $userPhoto string is an image as opposed to PHP script. It blindly takes the user input and uploads it to the uploads/user/directory. While I am not an expert in secure PHP, there are a few things that could be added to prevent a malicious upload here:

  • Extension whitelisting (only allowing .png, .jpg, .gif, etc…)
  • Checking the content-type of the uploaded file
  • Use the PHP GD library to do even stronger image validation

I was quickly doing some research to look at PHP solutions to some of these, and I came across a seriously great Stack Overflow answer that took a deep-dive into a few really solid solutions into building a secure image upload.

Lessons Learned

While I already had some proficiency with parsing HTML from my OSINT web scraping project, I finally got around to learning some of the basics around sockets and building and maintaining network connections. In addition, I learned a lot about how requests and BeautifulSoup4 work with issuing POST requests and working with Sessions.

I was also really excited that I was able to find the vulnerable PHP scripts in the application and determine exactly why these exploits work, and also consider some of the ways that the script could be modified to remove the vulnerability.

Tobin Shields is a Information Security instructor in Portland, OR.