Intro
Ажил дээр CTF-н тэмцээн саяхан болсон ба тэрхүү тэмцээний зохион байгуулах багт орон хэдэн бодлого дэвшүүлж үзлээ. CTF-н тухай ойлголт бага байсан ч сонирхолтой байлаа. Миний хувьд 4 төрлийн бодлого дэвшүүлэх гэж оролдсон ба тэр тухайгаа бичлээ. Гэснээс тухайн бодлогын нэртэй кинонуудыг үзээд үзээрэй
Cryptography
Cryptography-д надад сонирхолтой санагддаг ба энэхүү сэдвээр бодлого дэвшүүлэх хамгийн сонирхолтой нь байлаа. Ерөнхийдөө 2 бодлогын санаа байсан ба
- SSH Public Key Authentication
Бүгд л ssh public key authentication ашигладаг эсвэл ашиглаж үзсэн байх. Тэгвэл боломжийг нь шалгаад тайлах боломжтой юу? гэдэг санаан дээр тулгуурласан. Бодит байдал дээр бол 2048bit-н хэмжээтэйгээр түлхүүр нь үүсдэг ба энэ нь тайлах боломжгүй гэж хэлж болно. Харин бага хэмжээтэй тохиолдолд үүнийг тайлах боломжтой. Гэхдээ үүнийг зохиоход нэг асуудал гарсан нь OpenSSH-нь хамгийн багадаа 1024b хэмжээтэй байх ёстой гэсэн ба энэ нь хэтэрхий том. Хуучин хувилбарыг ашиглах гэж үзсэн боловч тийм ч их хэмжээ нь багасаагүй. Тиймээс энэ бодлогыг хувиргаад publicKey.pem
өгөгдөхөд хэрхэн нууц захиаг тайлах вэ гэсэн бодлого болсон.
- Group message encryption
Групп чат хэрхэн encryption хийдэг вэ? Ихэнх нь дундаа нэг түлхүүр ашигладаг ба тэр түлхүүрийг хаана хэн яаж үүсгэх нь их сонин. Тэгвэл үүнээс сэдэвлэн Merkle мод ашиглан түлхүүрийг байгуулна. Шинэ гишүүн орох, хуучин гишүүн гарах үед тухайн модыг шинэчлэх нь энэ тохиолдолд хурдан. Ер нь энэ санаа их харагддаг. Хэрэглэгч бүр өөрийн нууцыг тухайн модонд оруулснаар бүх гишүүдээс хамааралтай нууц түлхүүр гарснаар үүнийгээ encrypt, decrypt хийхэд ашиглаж болно. Тэгвэл энгийн алгоритм байгаа тохиолдолд хэрхэн чатыг унших боломжтой вэ гэсэн бодлого болсон.
Reverse
Reverse-н тухайд надад туршлага байхгүй байсан ч манай гаргийн хэкэр найз byamb4-тай ярилцсаны дараа 2тын кодын тухай оруулахаар болсон
C, Go гэх мэт хэл нь compile хийсний дараа binary гаргаж өгдөг ба энэ нь шууд ажиллах боломжтой гэсэн үг билээ. Тэгвэл энэхүү машин кодоос анхны code-г гаргах боломжтой юу? гэсэн ерөнхий сэдвийн хүрээнд ба энэ нь нэлээн төвөгтэй билээ.
Эхний бодлогын хувьд salt, hash дээр суурилан гарсан ба нууц үгийг давс нэмээд hash-алдаг 2тын код өгөгдсөн бол тухайн hash-с анхны нууц үгийг олох юм. Salt нь static тэмдэгт мөр тул энэ нь харьцангуй олоход амархан байхаар хийсэн.
Дараагийн бодлогын хувьд арай хүнд ба энэ нь багадаа үзэж байсан нэг киноноос сэдэвлэсэн. Тухайн кино дээр нууц үг нь 15мин тутамд шинэчлэгддэг ба аль болох random байх шаардлагатай гэж гардаг. Тэгвэл энэ удаа 2 тал нэгэн программ солилцсон ба энэ нь нэг удаагийн нууц үгийг үүсгэдэг. Энэхүү программын гол хэсэг нь байгаа бол одоо байгаа нууц үгийг олох ёстой.
Forensic
Эндээр ярилцаад 2 бодлого дэвшүүлсэн ба
- Disk
Файлыг диск дээр хэрхэн хадгалдаг вэ? Бас файл устгалаа гэхэд тэрийг дискээс арчдаг уу гэвэл ихэнх үед үгүй ба учир нь залхуу байснаар энерги хэмнэнэ. Тэгвэл disk image өгөгдсөн ба тухайн диск дээр олон файл устгасан ба тухайн файлуудыг сэргээж тугийг олох ёстой.
- File таних
Яаж бид ер нь бичлэг, дуу эсвэл зураг гэдгийг ялгадаг вэ? Энэ нь мэдээж файлын сүүлийн хэсэг буюу extension-с шууд харж болдог? гэхдээ энэ нь худлаа эсвэл төр төрөл нь байхгүй бол яах вэ? Энэхүү даалгавар дээр base64 текст байгаа ба үүнийг 2тын файл болговол zip file байгаа. үүнийг задалбал zip, tar, gzip, 7z гэх мэт боломжуудаас гарч ирэх ба үүнийг нь таних гэсэн санаатай.
Web
Веб нь надад хамгийн ойрхон сэдэв байсан ч бодлого зохиоход хамгийн хэцүү нь байсан. Учир нь ерөнхийдөө ийм алдаа хүмүүс хийхгүйдээ гэж бодоод байгаа болохоор амьдралд ойр жишээ оруулмаар байсан болохоор байхаадаа. Тиймээс URL injection болон automation шаардсан 2 энгийн даалгавар оруулсан.
Дүгнэлт
CTF-н бодлого зохиох нь үнэхээр сонирхолтой байлаа. Санал зөвлөгөө өгсөн byamb4-даа баярлалаа. Инженер хүний хувьд хаана ямар асуудал байж болох вэ гэдгийг мэддэг байх нь хамгийн чухал чадваруудын нэг билээ. Тиймээс CTF-н даалгаврууд хийснээр эдгээр зүйлийг таних, олох чадвал илүү нэмэгдэх нь тодорхой биз ээ. Доор бодлогууд болон түүнийг хэрхэн гаргасан кодыг хуваалцлаа.
Problems
Crypto - 1. Finding nemo
Marlin is trying to find Nemo, but he encounters an octopus who knows cryptography. She asks him to find the hidden message in the given text. We need to help Marlin. The only clue is a public RSA key and the octopus’s name, Otto.
Input:
public_key.pem
- message(base64):
tjzcxU2SBzcDJl76ZTWBSBL+o4ijbkRXT7VZKOzXquI=
Solution:
RSA security relies on the fact that factoring large numbers (like n
, which is the product of two large prime numbers p
and q
) is hard. In this case, we need to factor n
to find p
and q
.
Tools like Msieve or YAFU can help factor large numbers, or you can use factordb.com, an online tool for factoring integers.
- Get RSA parameters from public key
with open('public_key.pem', 'rb') as key_file:
public_key = serialization.load_pem_public_key(key_file.read())
if isinstance(public_key, rsa.RSAPublicKey):
public_numbers = public_key.public_numbers()
modulus = public_numbers.n # Modulus N
exponent = public_numbers.e # Public exponent e
print(f"Modulus (N): {modulus}")
print(f"Public Exponent (e): {exponent}")
else:
print("The loaded key is not an RSA public key.")
- Find the p,q using
yafu
orfactordb
- build private_key.pem
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_public_key
from sympy import mod_inverse
import sympy # For factorization and modular inverse
# Given modulus N
N = 88241730478298554455297400401663645747594464022843286938863438781971498192411
p = 129766800833396739358807081789224332651
q = 680002357394856057528752849081299295761
print("p, q:", p, q)
assert p*q == N
phi_N = (p - 1) * (q - 1)
e = 65537
assert sympy.gcd(e, phi_N) == 1
d = int(sympy.mod_inverse(e, phi_N))
print("d:", d)
private_key = rsa.RSAPrivateNumbers(
p=p,
q=q,
d=d,
dmp1=(d % (p - 1)),
dmq1=(d % (q - 1)),
iqmp=int(mod_inverse(q, p)),
public_numbers=rsa.RSAPublicNumbers(e=e, n=N)
).private_key()
# Serialize private key to PEM format
private_key_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
# Create public key object
public_key = private_key.public_key()
# Serialize public key to PEM format
public_key_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
# Save to files
with open('private_key.pem', 'wb') as f:
f.write(private_key_pem)
with open('public_key.pem', 'wb') as f:
f.write(public_key_pem)
print("Keys have been saved to private_key.pem and public_key.pem")
- decrypt message (or we can use online tools)
base64 -d encrypted_flag_base64.txt > decrypted_flag.bin
openssl rsautl -decrypt -inkey private_key.pem -in decrypted_flag.bin
Generate problem
#!/bin/bash
echo "flag{IM_A_FISH}" | openssl rsautl -encrypt -inkey public_key.pem -pubin -out encrypted_flag.bin
base64 encrypted_flag.bin > encrypted_flag_base64.txt
cat encrypted_flag_base64.txt
base64 -d encrypted_flag_base64.txt > decrypted_flag.bin
openssl rsautl -decrypt -inkey private_key.pem -in decrypted_flag.bin
ssh-keygen -t rsa -b 256 -f private_key.pem
openssl rsa -in private_key.pem -pubout -out public_key.pem
openssl rsa -pubin -in public_key.pem -modulus -noout
Crypto - 2. Stalker
The chat room uses a key rotation mechanism based on a Merkle tree. Each room member contributes a secret, which is hashed into a tree. The root of the Merkle tree is used as the shared secret key for encrypting messages. When a member leaves or joins, the tree is recomputed, and a new shared key is generated.
Alexander were once part of this room, and his secret was his name. However, after you left, the remaining members added a few new people, and a new key was generated. Unfortunately, the key rotation algorithm is flawed. If you know the names of the current members, you can predict the new key and decrypt the message.
You have intercepted an encrypted message. Your task is to regenerate the current shared secret key using the Merkle tree key rotation algorithm and decrypt the message.
Inputs:
- Member’s Secret: Each member’s secret is their name (case-sensitive).
- Hash Function: The hash function used is SHA-256.
- When a Member Leaves or Joins: The tree is rebalanced, and a new root is calculated as the new shared secret key.
- Room Members:
Faime Jurno,Alisa Freindlich,Raimo Rendi,Nikolai Grinko,Anatoly Solonitsyn,Natasha Abramova,Andrei Tarkovsky
- Ecnrypted message:
2f36c0e23e4e4921454521fd98b364400a03dacf2e0a171cfade9903b15db958
Generate Problem and Solution
import hashlib
import binascii
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad, pad
from typing import List
import itertools
import random
def sha256_hexdigest(data: str) -> str:
return hashlib.sha256(data.encode()).hexdigest()
def build_merkle_tree(leaves: List[str]) -> str:
def hash_pair(left: str, right: str) -> str:
return sha256_hexdigest(left + right)
nodes = [sha256_hexdigest(leaf) for leaf in leaves]
while len(nodes) > 1:
new_level = []
for i in range(0, len(nodes) - 1, 2):
new_level.append(hash_pair(nodes[i], nodes[i+1]))
if len(nodes) % 2 == 1:
new_level.append(nodes[-1])
nodes = new_level
return nodes[0]
def encrypt_message(message: str, key: str) -> str:
cipher = AES.new(binascii.unhexlify(key), AES.MODE_ECB)
padded_message = pad(message.encode(), AES.block_size)
encrypted_padded = cipher.encrypt(padded_message)
return binascii.hexlify(encrypted_padded).decode()
def decrypt_message(encrypted_hex: str, key_hex: str) -> str:
encrypted_bytes = binascii.unhexlify(encrypted_hex)
key_bytes = binascii.unhexlify(key_hex)
cipher = AES.new(key_bytes, AES.MODE_ECB)
decrypted_padded = cipher.decrypt(encrypted_bytes)
decrypted = unpad(decrypted_padded, AES.block_size)
return decrypted.decode()
def generate_all_permutations(keys: List[str], encrypted_message_hex: str) -> str:
permutations = itertools.permutations(keys)
for perm in permutations:
perm_str = ','.join(perm)
shared_key_hex = build_merkle_tree(perm)
try:
return decrypt_message(encrypted_message_hex, shared_key_hex)
except Exception as e:
continue
return None
members = ["Andrei Tarkovsky", "Anatoly Solonitsyn", "Alisa Freindlich", "Nikolai Grinko", "Faime Jurno", "Natasha Abramova", "Raimo Rendi"]
shared_key_hex = build_merkle_tree(members)
encrypted_message_hex = encrypt_message("flag{WE_DONT_TALK_ANYMORE}", shared_key_hex)
print("encrypted hex:", encrypted_message_hex)
random.shuffle(members)
print(",".join(map(str,members)))
decrypted_message = generate_all_permutations(members, encrypted_message_hex)
print("Shared key (hex):", shared_key_hex)
print("Decrypted message:", decrypted_message)
Forensic: 1. Lost in Translation
An admin accidentally deleted an important file containing a flag. The file was deleted from a system, and the admin believes it’s fine because the file was removed from the filesystem. However, you know that deleted files can often be recovered, and your task is to find and recover the deleted file to obtain the flag.
You are given access to a disk image of the system where the file was deleted. Your challenge is to analyze the disk image, recover the deleted file, and extract the flag.
Files Provided:
- Disk Image:
disk_image.img
- A raw disk image file from which the file needs to be recovered.
Generate Problem
#!/bin/bash
# Create disk image
dd if=/dev/zero of=disk.img bs=1M count=50
mkfs.ext4 disk.img
# Mount disk image
mkdir -p /mnt/disk
mount -o loop disk.img /mnt/disk
# Create and add files
for i in $(seq 1 1000); do
echo "ene_bol_${i}" > "/mnt/disk/ene_a_${i}.txt"
done
echo "flag{Scarlett_Ingrid_Johansson}" > "/mnt/disk/ene_a_392.txt"
umount /mnt/disk
sleep 1
mount -o loop disk.img /mnt/disk
# Delete some files
for i in $(seq 3 800); do
rm "/mnt/disk/ene_a_${i}.txt"
done
# Unmount disk image
sudo umount /mnt/disk
echo "Disk image created with 400 files, some deleted for challenge."
Solution
hexdump -C disk.img | grep -A 2 flag
Forensic: 2. The Invisible Man
Find the flag from the given file
Generate Problem
import zipfile
import tarfile
import os
import bz2
import gzip
import shutil
import random
import uuid
def create_initial_file(file_name, content="This is the secret flag: FLAG{WHAT_U_CANT_SEE_CAN_HURT_U}"):
with open(file_name, 'w') as f:
f.write(content)
def compress_nested(file_name, iterations=2000):
current_file = file_name
formats = ['zip', 'tar', 'bz2', 'gz'] # List of compression formats to alternate between
for i in range(1, iterations + 1):
ind = random.randint(0, len(formats)-1)
format_type = formats[ind]
g = uuid.uuid4()
next_file = f'hidden_{g}'
if i == iterations+1:
next_file = 'hidden'
print(next_file)
if format_type == 'zip':
with zipfile.ZipFile(next_file, 'w') as zipf:
zipf.write(current_file)
elif format_type == 'tar':
with tarfile.open(next_file, 'w') as tarf:
tarf.add(current_file)
elif format_type == 'bz2':
with open(current_file, 'rb') as input_file:
with bz2.open(next_file, 'wb') as output_file:
shutil.copyfileobj(input_file, output_file)
elif format_type == 'gz':
with open(current_file, 'rb') as input_file:
with gzip.open(next_file, 'wb') as output_file:
shutil.copyfileobj(input_file, output_file)
os.remove(current_file)
current_file = next_file
return current_file
# Step 3: Generate the problem
def generate_problem():
create_initial_file('flag.txt') # Create the initial file with the flag
final_file = compress_nested('flag.txt')
print(f"Final nested file is: {final_file}")
if __name__ == '__main__':
generate_problem()
Solution
import zipfile
import tarfile
import bz2
import gzip
import shutil
import uuid
import os
def extract_file(file_name):
with open(file_name, 'rb') as f:
file_header = f.read(1024)
if file_header[:4] == b'PK\x03\x04':
with zipfile.ZipFile(file_name, 'r') as zipf:
zipf.extractall()
return zipf.namelist()[0] # Return the name of the extracted file
elif b'ustar' in file_header:
with tarfile.open(file_name, 'r') as tarf:
tarf.extractall()
return tarf.getnames()[0] # Return the name of the extracted file
elif file_header[:3] == b'BZh':
extracted_name = f'hidden_{uuid.uuid4()}'
with bz2.open(file_name, 'rb') as bz2f, open(extracted_name, 'wb') as output_file:
shutil.copyfileobj(bz2f, output_file)
return extracted_name
elif file_header[:2] == b'\x1f\x8b':
extracted_name = f'hidden_{uuid.uuid4()}'
with gzip.open(file_name, 'rb') as gzfile, open(extracted_name, 'wb') as output_file:
shutil.copyfileobj(gzfile, output_file)
return extracted_name
else:
raise ValueError("Unknown file format or unsupported file type.")
def decompress_nested(file_name, iterations=10000):
current_file = file_name
for i in range(1, iterations + 1):
next_file = extract_file(current_file)
os.remove(current_file) # Clean up the current file
current_file = next_file
print(f"Final file content: {open(current_file, 'r').read()}")
if __name__ == '__main__':
final_file = 'example'
decompress_nested(final_file)
Reverse - 1. Saltburn
You have been provided with a binary executable compiled from C code that hashes numbers using a known hash function. The program is given a number 1 <= x <= 10^6
, appends a static salt string to the number, and then hashes the result using a hash function.
Your task is to figure out the value of x
that was hashed, given the hash and the salt.
Provided Information:
- hash_program
- hash:
3d7c3cef2491f23ca9a0b76fcc9afc93ad6bdd67d2812267a570eb492fe62247
Solution
- use tool like
string
to inspect the binary and extract the statis salt
strings hash_program | grep -i salt
- Brute force the value of x
import hashlib
def compute_sha256(data: str) -> str:
return hashlib.sha256(data.encode()).hexdigest()
def find_number(target_hash: str, salt: str, max_number: int) -> int:
for number in range(1, max_number + 1):
combined = f"{number}{salt}"
hash_value = compute_sha256(combined)
if hash_value == target_hash:
return number
return None
target_hash = "3d7c3cef2491f23ca9a0b76fcc9afc93ad6bdd67d2812267a570eb492fe62247"
salt = "static_salt_oliver_quick"
max_number = 10**6
number = find_number(target_hash, salt, max_number)
if number is not None:
print(f"The number is: {number}")
else:
print("Number not found.")
Generate Problem
gcc -o hash_program hash_program.c -lssl -lcrypto
#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>
#define SALT "static_salt_oliver_quick"
void compute_sha256(const char *str, unsigned char output[SHA256_DIGEST_LENGTH]) {
SHA256_CTX sha256;
SHA256_Init(&sha256);
SHA256_Update(&sha256, str, strlen(str));
SHA256_Final(output, &sha256);
}
void hash_number(int number, unsigned char output[SHA256_DIGEST_LENGTH]) {
char buffer[256];
snprintf(buffer, sizeof(buffer), "%d%s", number, SALT);
compute_sha256(buffer, output);
}
void to_hex_string(const unsigned char *hash, char *hex_string) {
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(hex_string + (i * 2), "%02x", hash[i]);
}
}
int main() {
int number = 123456;
unsigned char hash[SHA256_DIGEST_LENGTH];
char hash_hex[2 * SHA256_DIGEST_LENGTH + 1];
hash_number(number, hash);
to_hex_string(hash, hash_hex);
printf("Hash: %s\n", hash_hex);
return 0;
}
Reverse - 2. Catch me if you can
Two parties, Alice and Bob, communicate using an RNG algorithm to generate a shared sequence of random numbers. Both parties use the same shared seed to initialize their RNG, but the exact algorithm used is unknown. Your task is to reverse-engineer the random number generator algorithm from the provided binary and find the seed that was used to generate a specific random number.
The first few random numbers have already been intercepted. Can you figure out the seed and predict the next random number in the sequence?
Given Information:
- You are provided with a binary file
rng_program
. - The first few random numbers generated are:
- Random number 1:
48271
- Random number 2:
65535
- Random number 3:
123456
- Random number 1:
- The random number generator is initialized with an unknown seed (32-bit integer).
- The RNG algorithm is built into the program, and you need to reverse-engineer the binary to find it.
Solution
- Reverse engineer the Binary
- Use tools like
Ghidra
,Radare2
, orIDA Pro
to reverse-engineer the binary and understand how the random numbers are generated. - Analyze how the seed is initialized, how the RNG works, and how random numbers are generated.
- Use tools like
- Brute-Force the Seed
- Once the RNG algorithm is identified, participants will need to brute-force the seed by simulating the RNG and comparing the first few numbers to the ones provided.
- Predict the Next Random Number
- After finding the correct seed, participants will use it to predict the next random number in the sequence.
class LCG:
def __init__(self, seed, a, c, m):
self.state = seed
self.a = a
self.c = c
self.m = m
def next(self):
self.state = (self.a * self.state + self.c) % self.m + 13
return self.state
def find_seed(a, c, m, intercepted_numbers):
for seed in range(m):
lcg = LCG(seed, a, c, m)
if all(lcg.next() == num for num in intercepted_numbers):
return seed
return None
def test_rng():
intercepted_numbers = [7424, 2729, 2104]
a = 17
c = 3212
m = 7919
seed = find_seed(a, c, m, intercepted_numbers)
if seed is not None:
lcg = LCG(seed, a, c, m)
# Advance the generator to the fourth number
for i in range(12345):
print(i, lcg.next())
next_number = lcg.next()
print(f"Next predicted number: {next_number}")
else:
print("Seed not found")
test_rng()
Generate Problem
#include <stdio.h>
#include <stdint.h>
#define A 17
#define C 3212
#define M 7919
uint32_t custom_rng(uint32_t *state) {
*state = (*state * A + C) % M + 13;
return *state;
}
int main() {
uint32_t seed = 247;
uint32_t state = seed;
printf("Random number 1: %u\n", custom_rng(&state));
printf("Random number 2: %u\n", custom_rng(&state));
printf("Random number 3: %u\n", custom_rng(&state));
return 0;
}
Web: 1. Taxi driver
Find the flag from the server
Server
package main
import (
"fmt"
"math/rand"
"strconv"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/limiter"
"github.com/google/uuid"
)
var problems = make(map[string]string)
var answers = make(map[string]int)
var adj = make(map[string]string)
func generateProblem() (string, int) {
rand.Seed(time.Now().UnixNano())
num1 := rand.Intn(100000)
num2 := rand.Intn(100000)
answer := num1 + num2
problem := fmt.Sprintf("%d + %d", num1, num2)
return problem, answer
}
func handleAnswer(c *fiber.Ctx) error {
id := c.Params("id")
answer, exists := answers[id]
if !exists {
return c.SendStatus(fiber.StatusNotFound)
}
if answer == -1 {
return c.SendString("flag{U_TALKING_TO_ME!!!}")
}
userAnswer := c.FormValue("answer")
userAnswerInt, err := strconv.Atoi(userAnswer)
if err != nil || userAnswerInt != answer {
return c.SendStatus(fiber.StatusForbidden)
}
return c.SendString(fmt.Sprintf("/path/%s", adj[id]))
}
func handleDynamicEndpoint(c *fiber.Ctx) error {
id := c.Params("id")
problem, exists := problems[id]
if !exists {
return c.SendStatus(fiber.StatusNotFound)
}
if problem == "found" {
return c.SendString("flag{U_TALKING_TO_ME!!!}")
}
return c.SendString(fmt.Sprintf("Problem: %s\nSubmit answer to /path/%s", problem, id))
}
func main() {
app := fiber.New()
app.Use(limiter.New(limiter.Config{
Max: 300,
Expiration: 1 * time.Second,
KeyGenerator: func(c *fiber.Ctx) string {
return c.IP() // Limit based on the client's IP
},
LimitReached: func(c *fiber.Ctx) error {
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
"message": "Too many requests, please try again later.",
})
},
}))
current := ""
for i := 0; i < 300; i++ {
problem, answer := generateProblem()
problems[current] = problem
answers[current] = answer
next := uuid.New().String()
adj[current] = next
current = next
}
fmt.Println(current)
problems[current] = "found"
app.Post("/path", handleAnswer)
app.Get("/path", handleDynamicEndpoint)
app.Post("/path/:id", handleAnswer)
app.Get("/path/:id", handleDynamicEndpoint)
app.Listen(":3000")
}
Solution
import requests
import re
import time
def extract_problem_and_answer(response_text):
problem = re.search(r'Problem: (\d+) \+ (\d+)', response_text)
if problem:
num1 = int(problem.group(1))
num2 = int(problem.group(2))
answer = num1 + num2
return answer
return None
def main():
base_url = "https://<url>"
current_endpoint = "/path"
s = 1/300
i = 0
while True:
i += 1
response = requests.get(base_url + current_endpoint)
if response.status_code != 200:
print(f"Error: {response.status_code}")
break
response_text = response.text
print(f"Received: {response_text}")
answer = extract_problem_and_answer(response_text)
if answer is None:
print("Failed to extract problem or calculate answer.")
break
time.sleep(s)
post_response = requests.post(base_url + current_endpoint, data={"answer": answer})
if post_response.status_code != 200:
print(f"Error: {post_response.status_code}")
break
post_response_text = post_response.text
print(f"Next response: {post_response_text} {i}")
current_endpoint = post_response_text
if "flag" in post_response_text.lower():
print(f"Flag found: {post_response_text}")
break
time.sleep(s)
if __name__ == "__main__":
main()
Web 2: Inception
Find the flag from the server
Server
# flag{minio_doesnt_pay_me}
from flask import Flask, request, send_from_directory, render_template
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
import os
app = Flask(__name__)
upload_folder = './files'
app.config['UPLOAD_FOLDER'] = upload_folder
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 5MB
limiter = Limiter(
get_remote_address,
app=app,
default_limits=["100 per minute"]
)
@app.route('/')
def index():
return render_template('./index.html')
@app.route('/upload', methods=['POST'])
@limiter.limit("2 per minute")
def upload_file():
if 'file' not in request.files:
return 'No file part', 400
file = request.files['file']
if file.filename == '':
return 'No selected file', 400
if request.content_length > 1024 * 1024: # 1MB
return f'File too large. Max size allowed is 1 MB', 413
file_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
file.save(file_path)
return f'File uploaded successfully! file path: {file_path}', 200
@app.route('/files', methods=['GET'])
@limiter.limit("2 per second")
def include_file():
filename = request.args.get('file')
if filename:
file_path = os.path.join("./", filename)
print(file_path)
try:
return send_from_directory("./", filename)
except Exception as e:
print(e)
return 'File not found', 404
if __name__ == '__main__':
app.run(host="0.0.0.0", debug=True)
Solution
curl https://<url>/files?file=files/../server.py