This is an old revision of the document!


Tema

Deadline: TODO

Task 1 - Break authentication (50p)

Capture-the-flag: doriti ca sa capturati flag-ul unui server care este protejat sub un cont administrativ. Va trebui sa obinem un tag valid de administrator. Se observa ca serverul ne da un cont de guest care insa nu are access la flag. Din fericire am reusit sa extragem codul sursa al serverului dar se pare ca secretul nu este in codul sursa.

Flag format: “CTF{(secret)}”

Acest exercitiu va testa capacitatea de a intelege constructii criptografice analizand cod sursa si folosirea de criptanaliza pentru a demonstra ca un cipher este nesigur. In final va fi nevoie sa construiti un proof-of-concept care demonstreaza un atac eficient, executand atacul impotriva unui server.

Recomandare de rezolvare:

  1. Intelegerea codului sursa si a parametriilor care lipsesc din el.
  2. Notarea parametriilor constanti cunoscuti si necunoscuti (eg: siruri de caractere, lungimi, etc.)
  3. Extragerea parametriilor de pe server
  4. Atac impotriva algoritmului
    1. In prima faza puteti descarca serverul
    2. Creati un fisier cu secretz, adaugati parametrii extrasi din pasul 2, si inlocuiti flag-ul cu o valoare dummy
    3. Rulati serverul local. Puteti sa modificati codul sursa al serverului offline. Recomandarea este sa folositi metode de debugging si sa atacati algoritmul pe bucati
    4. Replicati atacul pe serverul online de la adresa mentionata
  5. ???
  6. Capture the flag $

Source-code server:

server.py
#!/usr/bin/python3
from Crypto import Random
from Crypto.Cipher import AES
import base64 as b64
from secretz import *
 
options_txt = """Options:
1. Get guest token
2. Login
3. Exit
Input:"""
 
welcome_txt = """Welcome to SRY, most confidential platform with top-notch integrity.
To get public data login as guest."""
 
GUEST_NAME = b"Anonymous"
AES_KEY_SIZE = 16
 
def ctr_xor(txt, rnd):
    return bytes(a ^ b for a, b in zip(txt, rnd))
 
 
class Crypt:
    def __init__(self):
        KEY = Random.get_random_bytes(AES_KEY_SIZE)
        self.IV = Random.get_random_bytes(AES_KEY_SIZE)
        self.C = AES.new(KEY,AES.MODE_ECB)
 
        KEY2 = Random.get_random_bytes(AES_KEY_SIZE)
        self.INTEGRITY = AES.new(KEY,AES.MODE_ECB)
 
    def getINTEGRITY(self,plain):
        return self.INTEGRITY.encrypt(b'\x00'*(AES_KEY_SIZE-len(plain))+plain)[0:INTEGRITY_LEN]
 
    def encrypt(self,plain):
        rnd = self.C.encrypt(self.IV)
        cipher = ctr_xor(plain,rnd) + SERVER_PUBLIC_BANNER + self.getINTEGRITY(plain)
        return cipher
 
    def decrypt(self,input):
        rnd = self.C.encrypt(self.IV)
        secret_len = (INTEGRITY_LEN+len(SERVER_PUBLIC_BANNER))
        cipher,secret,crc = input[:-secret_len],input[-secret_len:-INTEGRITY_LEN], input[-INTEGRITY_LEN:]
        plain = ctr_xor(cipher,rnd)
        if secret != SERVER_PUBLIC_BANNER:
            return -1
        if self.getINTEGRITY(plain) != crc:
            return None
 
        return plain
 
def get_guest_token():
    global C
    token = C.encrypt(GUEST_NAME)
    print(b64.b64encode(token).decode('raw_unicode_escape'))
 
def login():
    global C
    try:
        s = input("Token:") # Python3. Don't get funny RCE ideas
        cipher = b64.b64decode(s)
 
        if(len(cipher)>16):
            print("Tokens must be smaller than 16 bytes!")
        plain = C.decrypt(cipher)
 
        if plain == -1:
            print("Wrong server secret!")
        elif plain == None:
            print("Failed integrity check!")
        elif plain == GUEST_NAME:
            print("Secret:","No secrets for anonymous")
        elif plain == b"Ephvuln":
            print("Secret:",FLAG)
        else:
            print("I don't have an answer for",plain.decode('utf-8'))
    except:
        print("No h3k1ng allowed")
        exit()
 
 
def invalid():
    print("! Invalid option.")
 
def menu():
    global C
    C = Crypt()
 
    print(welcome_txt)
    while True:
        print(options_txt, end='')
        switch={
            "1":get_guest_token,
            "2":login,
            "3":exit
        }
        func = switch.get(input(),invalid)
        func()
        print()
 
if __name__=="__main__":
    menu()

Serverul se găsește la adresa: TODO

Pentru testare conexiune incercati sa folositi netcat:

nc 141.85.224.117 1337

Pentru interactiune automata cu serverul aveti la dispozitie urmatorul schelet de cod care necesita pachetul pwntools. Pentru instalare:

 pip3 install pwntools 
skel.py
from pwn import *
import base64 as b64
from time import sleep
 
def byte_xor(ba1, ba2):
    return bytes([_a ^ _b for _a, _b in zip(ba1, ba2)])
 
LOCAL = True # Local means that you run binary directly 
 
if LOCAL:
    r = process("<PATH_TO_PYTHON_CHALLENGE>") # Complete this if you want to test locally
else:
    r = remote("141.85.224.117",1337) # Complete this if changed
 
def read_options():
    """Reads server options menu."""
    r.readuntil(b"Input:")
 
def get_token():
    """Gets anonymous token as bytearray."""
    read_options()
    r.sendline(b"1")
    token = r.readline()[:-1]
    return b64.b64decode(token)
 
def login(tag):
    """Expects bytearray. Sends base64 tag."""
    r.readline()
    read_options()
    r.sendline(b"2")
    #sleep(0.01) # Uncoment this if server rate-limits you too hard
    r.sendline(b64.b64encode(tag))
    r.readuntil(b"Token:")
    response= r.readline().strip()
    return response
 
 
# TODO: Solve challenge
# ..
# ..
# ..
# ..
# response = login(b64.b64encode(payload)).decode('utf-8')
# if "CTF" in response:
#        print("[*] Found flag:",response)
#
 
 
r.close()

Atentie! In cazul in care se trimit prea multe request-uri serverul va actiona un rate-limiting. Recomandarea este ca sa puneti un *sleep* atunci cand trimiteti pachete in cadrul unei structuri repetitive.

Task 2 - TODO (50p)

TODO

ic/teme/tema2022.1667849232.txt.gz · Last modified: 2022/11/07 21:27 by mihai.bogatu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0