KOSENセキュリティ・コンテスト2016に参加した話

概要

 ちょうど一週間前の11月27日(日)に「KOSENセキュリティ・コンテスト2016」というコンテストが開催され、私は4人からなる最南端の高専生チームEAnbaiの一員として参加した。コンテスト概要はサイバーな雰囲気漂うデザインの公式サイトを参照されたい。運営・参加者共に初めての試みだった本コンテスト。記憶に新しいうちに、参加記とは言えないだろう参加記をここに書き残したい。ちなみに私達のチームは31チーム参加中7位タイで競技を終えた。タイというのも、コンテストのランキング表は最終得点日時に基づく時系列が考慮されていたのだ。時系列も考慮した場合の私達の最終順位は11位であることも、ランキング警察訪問に備えて付しておこう。

(追記)しばらくして大会に使用された環境が異なっていたことが運営の方の調査によって明らかになり、その違いによってランキング時に2つの部門に分けられました。その結果とあるファイルがあった部門において3位となり、表彰状をいただきました。

競技の一週間程度前~競技前日

 この頃から運営からのメールでの連絡が増え始め、一歩一歩確実に戻ることなく近づいてくる競技の足音とプロ参加者の自己紹介に震えながら、更新されていく情報に耳を傾けていた。競技数日前、SSHでの競技サーバー接続テスト用の情報が公開された。各々好みのターミナルを起ち上げ、指定されたサーバーアドレスを打ち込み、研究室の回線を介して正常にサーバーへ接続できることを確認した。北は北海道、南は我らが沖縄県からの参加がある本コンテストだが、このようにインターネット経由で参加できるオンライン形式のコンテストなので参加は容易い。勉強会や現地会場でのみ執り行われる大会には地理的ハードルが高い私達でも、オンラインの大会ならば亜光速で現地入りできるのだから、通信技術には頭が上がらない。
 この数日後、予告されていた事前課題が解放された。初めての亜光速現地入りの時の入口から少し時間を進めたところから入れば、それにたどり着けるようだ。既に学生寮の自室へ戻っていた私は、研究室から亜光速現地入りして現場検証を行うチームメンバーからの報告を見ていた。翌日、チームメンバーの報告どおり「iotcar.py」、「iotcar」の存在をそれぞれ自分の目で確かめた。どうやらiotcarの方はpython iotcar.pyとした時と同じ挙動をするものらしい。運営からSlackを通じて提供されている事前課題の情報と照らし合わせつつ、それを実行し、コマンドをUDPで送信し挙動を見ることを行った――こう書くと何だか大層なことをやってのけているように思えるが、実際は運営から渡されたサンプル通信プログラムをサーバー上に新たなファイルとして作成して実行したにすぎない。
 少なくともここまでの作業は全国の参加チームで行われたことだろう。私達のチームではこのリモートブラックボックスiotcar.pyが本番、どのように作用してくるのかを考えていた。パラメータを指定してコマンドを送信すること、コマンドの仕様がそれぞれわかっていたが、事前課題として示されていたプログラムに基づいたコマンドの送信方法では、本番数多のコマンドを送信することを予想するとあまりにも効率が悪いと判断した。そこで以下のようなプログラムを作成した。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# -*- coding: utf-8 -*-
import array
import socket
from datetime import datetime
HOST = "localhost"
PORT = "PORT_NUM"
def genCmd():
cmd = []
kind = {
"u": {
1: u"1: スポーツモード",
2: u"2: ハンドル舵角",
3: u"3: ブレーキ量",
(中略)
8: u"8: エンジン"
},
"a": {
1: u"1: ウィンドウ動作",
2: u"2: ウィンドウロック",
3: u"3: エアコン設定温度",
(中略)
18: u"18: 4WD",
}
}
# append
for val in [0xf0, 0x40, 0x01]:
cmd.append(val)
print "[step1]カテゴリ選択"
opt1 = raw_input("運転動作orアクセサリ(u | a):")
cmd.append((lambda x: 0x10 if x == 'u' else 0x20)(opt1))
print "[step2]コマンド選択"
for i in range(1, len(kind[opt1]) + 1):
print kind[opt1][i]
opt2 = raw_input("コマンド番号(数字)")
cmd.append(int(opt2))
print "[step3]データ1"
opt3 = raw_input("データ#1:")
cmd.append(int(opt3, 16))
print "[step4]データ2"
opt4 = raw_input("データ#2:")
cmd.append(int(opt4, 16))
# append2
for val in [0x01, 0x02, 0xf7]:
cmd.append(val)
print "[genCmd]cmd generated: %s" % cmd
return cmd
def pushCmd(cmd):
serv = (HOST, PORT)
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto(array.array("B", cmd), serv)
print "[%s]cmd sent." % datetime.now().strftime("%H:%M:%S")
client.close()
if __name__ == "__main__":
cmd = genCmd()
pushCmd(cmd)

コマンド送信を簡略化するためのこのプログラム利用者は天才であることを前提に作成したので、使い勝手はあまり考慮されていないが本番でどの程度このプログラムが力を発揮できるのかわからない中だったので、まあひとまずといったところだ。ここがEAnbaiによるiotcarピークだった。

競技当日

 結論から言うと、EAnbaiはiotcarを得点につなげることができなかった(問題の内訳が公開されていない今、本当に獲得できていないのかは定かではないが恐らくそうだろう)。コンテストの加点は5分に一度運営側で脆弱性の修正が行われたことが確認された段階で行われる仕組みになっており、自分たちのどの作業が自分たちの得点になったのかは即時に判断できない場合がある。ひとまずサーバーにアクセスして一体どんな脆弱性やおかしな設定が与えられているのか、確認するところから始めるしかない。本番環境のサーバーにアクセスしてまずはじめに、事前課題の洞窟と同じ風景が広がっているのが一目見えた。しかし今回は事前環境とは違う点がある。iotcar.pyを編集できるのだ。その変化に満足しながらひとまずpython iotcar.pyとする。しかし期待に反してプログラムはSyntax Errorを話すのだった。ここからもう一人のチームメンバーと共同でのiotcar.pyデバッグが始まる。これに没頭しすぎたのは今考えると良くなかった。もう少し編集可能になった宝について、その使いみちを検討すべきだったが、満足に実行できるようにすることを優先して時間をかけすぎた感が否めない。しかしもちろん無駄な作業だったわけではない。構文エラー、変数名の誤り等いくつかのエラーを乗り越えてiotcar.pyは正常に動作した。
 この作業と並行して他のチームメンバー2人によるサーバー内探索が進んでいた。不審なユーザーがJoeアカウントで登録されていたのを修正したり、設定不備なファイルの修正を行ったりと、いくつかの得点を重ねていた。root権限に飢えながら行われたこれらの幾つかの作業が最終得点700点の全てだと思われる。一方、前日に用意したコマンド送信用のプログラムも正常に動作し、iotcar.pyにコマンドが送られ、ファイルに書き込まれ、Webページの表示からWarningが消えていくのを見て私はある程度の満足感を覚えていた。しかし、くどいようだがこれらの作業は得点には繋がらなかった。アプローチの仕方が間違っていて、本質的な解決に至っていなかったからだ。ここからは苦しい時間が続いた。/var/www内のPHPファイルの内容が明らかに怪しいのはわかっていて、ソースコードを読むことでどのような脆弱性があって、Webインターフェースでの実際の作用まである程度想定できていた。足りなかったのはroot権限だった。編集権限までは与えられていなかったこれらの問題児に手を付けたくてもつけられない、どうにかして糸口を見つけ出したい、そんな状況が続いた――直ぐ側にある宝に気づけないまま。
 「灯台下暗し」とは、まさにあのことだったのだろう。この件に関しては後日運営から追加情報が出次第、追記することにするがいやはや、今になってみれば何故あの大きな大きな宝に気づくことができなかったのだろうか、と悔やまれるばかりだ。宝があったとかなかったとか言う議論とは別の次元に、私達がその宝に気づけなかったという事実はある。この悔しさをバネに、次の機会では大きい魚を逃さぬように目を凝らさねばならないと誓ったのだった。

終わりに

 悔しさが残る競技となった一方、楽しかったという感情も大きかったです。きっと、セキュリティミニキャンプに参加して、セキュリティを勉強し始めた約1年前の自分では知識不足で何からどう手を付けていいのかすらわからなかったと思います(当時はLinuxすらまともに触ったことがなかった)。朝10時から始まり15時半まで5時間半続いた競技時間、数字だけ見ると長いように思えますが実際にはあっという間でした。12時頃に寮食を食べに食堂まで行く時間も惜しくて、普段の数倍のスピードでご飯を食べながら/etc/shadowをどう開くかGoogle検索片手に考えていたので何を食べていたのかもよく思い出せないですが、これも良い思い出です。
 今回のコンテストはCTFというよりはHardening色が強い競技形態だったと思います。といっても僕はHardeningへの参加経験がなく、今回をきっかけに参加してみたい気持ちが今まで以上に強くなりました。CTFなどのコンテスト自体の参加経験も浅いので、今後も研鑽を積みつつ顔を出していきたいです。
 最後になりますが、運営の皆様方、一緒に参加したチームメンバー、当日学校に来てくださった担当の先生、本当にありがとうございました。雑多な記事になってしまいましたが、今回は以上です。