Intro
I was part of the CTF challenges admin team, where we worked on creating a CTF competition for our company. I didn’t have much experience with CTF before, so it was interesting to get involved in creating the challenges. There were four main topics I focused on when designing the problems. Btw, the problem titles are movies I recommend.
Cryptography
I like cryptography, and creating challenges for this CTF was really fun. I came up with two problem ideas:
- SSH Public Key Authentication
We all use SSH public key authentication frequently. Can it be brute-forced? I wanted to give this idea by using the RSA encryption algorithm with a smaller key size. In real life, RSA keys are typically 2048 bits, which are nearly impossible to break. However, for this challenge, I used a 256-bit key, making it relatively easy to crack.
The issue I ran into was that OpenSSH requires keys to be at least 1024 bits, which prevented me from using a 256-bit key directly. I even tried older versions of OpenSSH, but they also required key sizes greater than 256 bits. In the end, I modified the challenge: instead of using SSH, I provide a publickey.pem
file and an encrypted message. The participants will need to decrypt the message using the public key.
- Group message encryption
How does group chat encryption work in real life? Typically, group chats use symmetric key encryption to secure messages. But how is the shared key calculated, and how do participants share it securely?
I want to propose an idea: using a Merkle tree to represent the shared group key. Each user would contribute their secret to the Merkle tree after agreeing on the encryption scheme. The root of the Merkle tree would then serve as the shared key for the group.
But what happens when someone leaves or is added to the group? We need to update the shared key. Fortunately, due to the structure of the Merkle tree, it is relatively easy to update the tree and recalculate the group key. Only the affected parts of the tree need to be recalculated, making it efficient to manage changes in group membership.
Reverse
I don’t have much experience with reverse engineering, but one of my friends, byamb4, is really is hecker. We discussed some ideas:
- About Binary
In languages like C or Go, after a program is compiled, it becomes a binary, and all the program logic is embedded within it. But can we reverse-engineer the binary to recover the original logic? It’s a much more complex topic, so we created a simpler problem.
The idea is based on salt and hashing. When saving passwords, we often use salt to make the hashes more secure. For this challenge, we used a binary that runs the password_hash
function. If a user provides a password string, the binary returns a hashed version of the password with a salt. The challenge is that we’ve lost the original password, and participants need to reverse-engineer the binary to find it. The salt is stored as a static string in the binary, making it relatively easy to recover.
The second problem is relatively more difficult. Participants will need to reverse-engineer both the logic and the parameters. In this challenge, there’s a random password generator algorithm that two parties use to generate one-time passwords (OTPs). I got the idea from a movie, though I can’t remember which one. In the movie, the password was recalculated every 15 minutes. For this problem, participants have access to the binary and need to figure out the algorithm, find the seed, and determine the current password.
Forensic
In this challenge, we explored two problems:
- Disk Storage
How does a computer store data on a disk? Do files get completely erased after being deleted? The answer is often no, because we need to by lazy sometimes to keep energy little as possible. In this task, there is a disk image containing many deleted files. Can you recover the flag from one of them?
- File Identification
How do we know if a file is music, video, or text? We typically look at the file extension, but what happens if the extension is removed? In this task, you’ll start with a base64-encoded string. Once you decode it to binary data, you’ll discover it’s a zip file. Unzipping it will reveal another binary file, which could be a zip, tar, gzip, 7z, or another format. Can you correctly identify and extract the contents to find the flag?
Web
Web is the topic I’m most familiar with, but ironically, it was the hardest one to create challenges for. I wanted to keep the problems straightforward, so one focuses on URL injection and the other on automation. By the way, it’s challenging to come up with real-life, yet interesting, problems for CTFs.
Conclusion
Creating CTF challenges has been an interesting experience. Thanks to Byamb4 for sharing valuable ideas that helped shape some of the problems. As engineers, it’s crucial for us to be more aware of vulnerabilities and understand how they can be exploited. Working on these challenges highlighted the importance of not only writing secure code but also recognizing potential weaknesses in real-world applications. Through exercises like these, we can improve our ability to protect systems from various types of attacks. Below this, there’s a code and solution for the all problems that i recommended.
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