GHSA-GXMJ-R5RF-GGWQ
Vulnerability from github – Published: 2026-09-02 14:37 – Updated: 2026-09-02 20:21Summary
elFinder provides uploadDeny and uploadAllow options in its connector configuration to restrict which MIME types may be uploaded. When uploadDeny includes text/x-php, direct upload of .php, .phtml, and .phar files is correctly blocked. However, the extract command (ZIP decompression) internally calls checkExtractItems(), which invokes mimetypeInternalDetect() directly without passing the result through mimeTypeNormalize(). Because phtml, phar, and similar PHP-executable extensions are absent from mime.types, they are not resolved to text/x-php at the detection stage, causing the MIME filter to be silently bypassed. An attacker who is permitted to upload ZIP archives can therefore extract PHP-executable files into the web-accessible files/ directory. If the server is configured to execute the affected extension (e.g., .phtml, .phar) as PHP — which is the case in common Apache and Nginx deployments — this results in Remote Code Execution.
Details
elFinder's MIME validation pipeline for direct uploads (upload command) is:
mimetype()
└─ mimetypeInternalDetect() // stage 1: extension → MIME via mime.types
└─ mimeTypeNormalize() // stage 2: apply staticMimeMap
phtml:* → text/x-php
phar:* → text/x-php
php5:* → text/x-php
└─ allowPutMime() // blocked: text/x-php ∈ uploadDeny
The extract command (checkExtractItems() in elFinderVolumeDriver.class.php, line 7110) uses a shortened pipeline:
// line 7110 — stage 2 (mimeTypeNormalize) is never called
if ($chkMime
&& ($mimeByName = elFinderVolumeDriver::mimetypeInternalDetect($name))
&& !$this->allowPutMime($mimeByName)) {
Because phtml and phar are not present in mime.types, mimetypeInternalDetect() returns a generic type (e.g., application/octet-stream) for these extensions. Without mimeTypeNormalize(), the staticMimeMap entries that would map phtml:* → text/x-php are never applied, so allowPutMime() sees a non-blocked MIME and permits extraction.
Affected extensions confirmed: .phtml, .phar, .php5, .php3
Not bypassed: .php (present in mime.types, detected as text/x-php in stage 1)
PoC
Requirements:
- elFinder 2.1.69 deployed under Apache/Nginx (PHP-FPM or mod_php)
- Connector configured with uploadDeny = ['text/x-php'] and uploadAllow including application/zip
- files/ directory served under a public web path
Step 1 — Confirm direct upload is blocked
Open elFinder in a browser and click the Upload button.
Select hello.phtml (content: <?php phpinfo(); ?>).
→ Upload is rejected with: "Upload file hello.phtml: File type not allowed (text/x-php)"
Step 2 — Upload a ZIP containing the payload
Create bypass.zip containing hello.phtml.
Upload bypass.zip via the Upload button.
→ ZIP is accepted (MIME: application/zip ∈ uploadAllow).
Step 3 — Extract the ZIP
Right-click bypass.zip in the file list → Extract files.
→ hello.phtml appears in the file list without any error.
→ File is now present at {files_dir}/hello.phtml on the server.
Step 4 — Execute the extracted PHP file
Navigate to:
http://<target>/elFinder/files/hello.phtml
→ Apache processes the file as PHP and renders the full phpinfo() output, confirming Remote Code Execution.
Impact
Any user with ZIP upload permission can bypass the uploadDeny MIME restriction, place PHP-executable files in a web-accessible directory, and achieve Remote Code Execution on the server.
Concrete impact:
- Arbitrary PHP code execution on the web server
- Full server environment disclosure via phpinfo() (paths, PHP version, loaded modules, environment variables)
- Potential access to server filesystem, database credentials, and internal network services
- Complete compromise of the web application if an attacker substitutes phpinfo() with a web shell (e.g., <?php system($_GET['cmd']); ?>)
Extensions confirmed executable on Apache (default config): phtml, phar, php5, php3
Recommended fix:
Apply mimeTypeNormalize() inside checkExtractItems() so that the full MIME pipeline is used consistently:
// elFinderVolumeDriver.class.php, line 7110
// Before (vulnerable):
$mimeByName = elFinderVolumeDriver::mimetypeInternalDetect($name)
// After (fixed):
$mimeByName = $this->mimeTypeNormalize(
elFinderVolumeDriver::mimetypeInternalDetect($name),
$name,
pathinfo($name, PATHINFO_EXTENSION)
)
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "Studio-42/elFinder"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.1.70"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-81891"
],
"database_specific": {
"cwe_ids": [
"CWE-434"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-02T14:37:54Z",
"nvd_published_at": "2026-08-31T21:17:52Z",
"severity": "HIGH"
},
"details": "### Summary\n\nelFinder provides `uploadDeny` and `uploadAllow` options in its connector configuration to restrict which MIME types may be uploaded. When `uploadDeny` includes `text/x-php`, direct upload of `.php`, `.phtml`, and `.phar` files is correctly blocked. However, the `extract` command (ZIP decompression) internally calls `checkExtractItems()`, which invokes `mimetypeInternalDetect()` directly without passing the result through `mimeTypeNormalize()`. Because `phtml`, `phar`, and similar PHP-executable extensions are absent from `mime.types`, they are not resolved to `text/x-php` at the detection stage, causing the MIME filter to be silently bypassed. An attacker who is permitted to upload ZIP archives can therefore extract PHP-executable files into the web-accessible `files/` directory. If the server is configured to execute the affected extension (e.g., `.phtml`, `.phar`) as PHP \u2014 which is the case in common Apache and Nginx deployments \u2014 this results in Remote Code Execution.\n\n---\n\n### Details\n\nelFinder\u0027s MIME validation pipeline for **direct uploads** (`upload` command) is:\n\n```\nmimetype()\n \u2514\u2500 mimetypeInternalDetect() // stage 1: extension \u2192 MIME via mime.types\n \u2514\u2500 mimeTypeNormalize() // stage 2: apply staticMimeMap\n phtml:* \u2192 text/x-php\n phar:* \u2192 text/x-php\n php5:* \u2192 text/x-php\n \u2514\u2500 allowPutMime() // blocked: text/x-php \u2208 uploadDeny\n```\n\nThe `extract` command (`checkExtractItems()` in `elFinderVolumeDriver.class.php`, line 7110) uses a **shortened** pipeline:\n\n```php\n// line 7110 \u2014 stage 2 (mimeTypeNormalize) is never called\nif ($chkMime\n \u0026\u0026 ($mimeByName = elFinderVolumeDriver::mimetypeInternalDetect($name))\n \u0026\u0026 !$this-\u003eallowPutMime($mimeByName)) {\n```\n\nBecause `phtml` and `phar` are not present in `mime.types`, `mimetypeInternalDetect()` returns a generic type (e.g., `application/octet-stream`) for these extensions. Without `mimeTypeNormalize()`, the `staticMimeMap` entries that would map `phtml:*` \u2192 `text/x-php` are never applied, so `allowPutMime()` sees a non-blocked MIME and permits extraction.\n\n**Affected extensions confirmed:** `.phtml`, `.phar`, `.php5`, `.php3` \n**Not bypassed:** `.php` (present in `mime.types`, detected as `text/x-php` in stage 1)\n\n---\n\n### PoC\n\n**Requirements:**\n- elFinder 2.1.69 deployed under Apache/Nginx (PHP-FPM or mod_php)\n- Connector configured with `uploadDeny = [\u0027text/x-php\u0027]` and `uploadAllow` including `application/zip`\n- `files/` directory served under a public web path\n\n**Step 1 \u2014 Confirm direct upload is blocked**\nOpen elFinder in a browser and click the **Upload** button. \nSelect `hello.phtml` (content: `\u003c?php phpinfo(); ?\u003e`). \n\u2192 Upload is rejected with: *\"Upload file hello.phtml: File type not allowed (text/x-php)\"*\n\u003cimg width=\"1061\" height=\"408\" alt=\"1\" src=\"https://github.com/user-attachments/assets/22f833d9-7d2a-4197-85c2-3b0eb19ecfbc\" /\u003e\n\n\n**Step 2 \u2014 Upload a ZIP containing the payload**\nCreate `bypass.zip` containing `hello.phtml`. \nUpload `bypass.zip` via the **Upload** button. \n\u2192 ZIP is accepted (MIME: `application/zip` \u2208 `uploadAllow`).\n\u003cimg width=\"886\" height=\"320\" alt=\"2\" src=\"https://github.com/user-attachments/assets/30c3498f-91f4-45cf-8c2b-cc806716e3a4\" /\u003e\n\n\n**Step 3 \u2014 Extract the ZIP**\nRight-click `bypass.zip` in the file list \u2192 **Extract files**. \n\u2192 `hello.phtml` appears in the file list without any error. \n\u2192 File is now present at `{files_dir}/hello.phtml` on the server.\n\u003cimg width=\"1123\" height=\"792\" alt=\"3\" src=\"https://github.com/user-attachments/assets/49e7db8e-566c-4653-a57f-e80fb31ea61d\" /\u003e\n\n\n**Step 4 \u2014 Execute the extracted PHP file**\n\nNavigate to:\n```\nhttp://\u003ctarget\u003e/elFinder/files/hello.phtml\n```\n\u2192 Apache processes the file as PHP and renders the full `phpinfo()` output, confirming Remote Code Execution.\n\n---\n\u003cimg width=\"1059\" height=\"739\" alt=\"4\" src=\"https://github.com/user-attachments/assets/ad85fdf7-bdc7-4514-bdec-e45cfb2e2bd3\" /\u003e\n\n\n\n### Impact\n\nAny user with ZIP upload permission can bypass the `uploadDeny` MIME restriction, place PHP-executable files in a web-accessible directory, and achieve Remote Code Execution on the server.\n\n**Concrete impact:**\n- Arbitrary PHP code execution on the web server\n- Full server environment disclosure via `phpinfo()` (paths, PHP version, loaded modules, environment variables)\n- Potential access to server filesystem, database credentials, and internal network services\n- Complete compromise of the web application if an attacker substitutes `phpinfo()` with a web shell (e.g., `\u003c?php system($_GET[\u0027cmd\u0027]); ?\u003e`)\n\n**Extensions confirmed executable on Apache (default config):**\nphtml, phar, php5, php3\n\n**Recommended fix:**\n\nApply `mimeTypeNormalize()` inside `checkExtractItems()` so that the full MIME pipeline is used consistently:\n\n```php\n// elFinderVolumeDriver.class.php, line 7110\n// Before (vulnerable):\n$mimeByName = elFinderVolumeDriver::mimetypeInternalDetect($name)\n\n// After (fixed):\n$mimeByName = $this-\u003emimeTypeNormalize(\n elFinderVolumeDriver::mimetypeInternalDetect($name),\n $name,\n pathinfo($name, PATHINFO_EXTENSION)\n)\n```\n\n---\n\n---",
"id": "GHSA-gxmj-r5rf-ggwq",
"modified": "2026-09-02T20:21:31Z",
"published": "2026-09-02T14:37:54Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Studio-42/elFinder/security/advisories/GHSA-gxmj-r5rf-ggwq"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-81891"
},
{
"type": "WEB",
"url": "https://github.com/Studio-42/elFinder/commit/191372c1bbebbd36fb55af79a84b9984861390ff"
},
{
"type": "WEB",
"url": "https://github.com/Studio-42/elFinder/commit/dd73e702820c146a192969800ee674ecdb208365"
},
{
"type": "PACKAGE",
"url": "https://github.com/Studio-42/elFinder"
},
{
"type": "WEB",
"url": "https://github.com/Studio-42/elFinder/releases/tag/2.1.70"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "elFinder: ZIP extraction bypasses uploadDeny MIME filter allowing PHP file upload (RCE)"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
How durations are calculated
Following the VTEM framework (Black Hat USA 2015), time to patch starts when the vendor or a coordinator was informed, total time to patch at discovery, and disclosure to patch at public disclosure; vendor response time runs from the report to its acknowledgement. Time to exploit and time unpatched start at the earliest discovery or report, falling back to public disclosure. Unpatched time ends at the first recorded patch, or the calculation time when no patch is recorded. Exploitation to patch shows the interval between those two events; a published proof of concept is a public exploit, not exploitation.
Each result shows its source dates. A negative interval means the end event came first; when date-only inputs leave the order open, the result says so. Date-only inputs are approximate. The earliest evidence wins: KEV report dates and sightings are labelled as such. Records describing another vulnerability are listed but never calculated from, and a PoC alone does not establish exploitation. Missing patch evidence does not prove that no patch exists. Filters below change the event list only.
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.