2018年を振り返る

今年もいろいろあったので振り返る。2017年の振り返りはこちら

icchy.hatenablog.jp

昨年に劣らずいろいろなことがあったと思う。昨年同様、メインはCTFでの活動に関する話である。

1月

幸いにも一年前のように病院のベッドの上で年を越してはいなかった。失ってから初めてその重大さに気づくものっていろいろあると思うが、健康なんかはその良い例だと思う。
2017年の12月にYan先生 (a.k.a zardus) がHITCONの帰りがてら日本に遊びに来ていて、そこで留学の誘いを受けていたがまだ決心がついていなかった。1月半ばあたりにメールを出して、親や研究室のボスに相談した後に行く決心を固めたのはここから1週間くらいだったと思う。4月から行く予定だったので時間的にはかなり余裕に見えたが、実際にはApplicationを書いたり、ビザの取得のためのDS2019を用意してもらったりで最終的にすべての用意が整ったときは出発まで1週間を切っていて、かなりギリギリだった。

2月

一応Research Specialistという肩書で大学に雇われるという扱いなので、大学の求人に応募する形でApplicationを書く。どうやら専用の求人を掲載したらしいが、Web上に掲載されるまでかなり時間がかかった。また、Applicationを提出してからAcceptされるまでさらに時間がかかった。実はRejectされるんじゃないか?と内心ヒヤヒヤしていた。
とにかくこの応募手続きでかなり時間がかかって、多分2月の半分くらいこれで溶けた気がする。

3月

正式にOffer letterを受け取ったのでビザを取得するためのDS2019という書類の発行手続きに入る。この書類の発行が結構時間がかかって、向こうから発送したよというメールをもらったのが3/16で受け取ったのが3/19だった。これを待ってからビザを取りに行く準備を始めると明らかに間に合わないので、あらかじめ予約だけ取っておいてDS2019待ち、という状況にしておいた。確かDS2019を受け取った2日後にビザの面接を受けて、その翌日くらいに発送されていたと思うが、受け取るまで少し時間がかかった。
めでたくビザを手に入れてあとは出国だけという状態になったのが確か3/25だったと思う。
留学とはいえ持ち運べる荷物はせいぜいスーツケースとリュックサックが一つずつなので、とにかく必要最低限の物しか入れなかった。

4月

出発直前に0CTF Qualsに出た。オンサイトで集まったこともあって途中までかなり良い感じだったが、予選突破にはあとちょっと足りないという状態で残り30分を迎えて頭を捻っていたところ、奇跡的に解法を思いついて残り5分のところでsubmitした。これが決め手となって決勝の出場権を獲得した。

初めてアメリカを訪れた。インターネットがないと確実に詰むのであらかじめSIMを買っておいたのが良かった。現地の空港について、Airbnbのホストに連絡を入れたところで日付を一日間違えて予約していたことが発覚して危うく初日を野宿で過ごすところだったが、幸いにも前日が空いていたので免れた。
時差ボケが本当にひどくて、一週間くらい夜中の3時に目が覚めて昼間クソ眠いみたいな状態を繰り返していた。 到着後に必要な書類手続き (主に税金関係) が結構面倒で、4月末くらいまでは広い大学構内を練り歩いたりPhoenix社会保障事務所に行ったりしてた。

5月

研究テーマが無事に決まって進め始める。詳しいことは書かないが、先行研究に対してどう改善したかとかではなくて、新しい手法の提案なのでとにかくアイデアと実装が重要だった。

XCTFで良い成績をおさめたチームが招待されるらしい中国の謎CTFに参加した。確かCyber Mimic Defense Contestとかそんな感じの名前だったと思う。 CTFは微妙だったが運よく2位で200万くらいゲットした。(まだ賞金は振り込まれていない)
中国から戻る途中で日本に立ち寄ってDEF CON CTF Qualsに参加した。19位で終わったので今回も無理か…と思ってたらクソ多い枠のおかげで予選通過して微妙な気持ちになったのを今でも覚えている。
月末には0CTF/TCTFに参加するために再度中国へ。会場が深圳だったので香港から陸路入国しようとしてTokyoWesterns全員で彷徨っていた。

6月

気胸じみた痛みが発生して急遽大学の病院にかかる。結果的には気胸ではなかったが、レントゲンを撮って診断を受けただけなのに$166かかった。
Midnight Sun CTF finalsに参加するためにスウェーデンストックホルムへ行く。途中でニューヨークに立ち寄って観光したりした。当たり前なのだがストックホルムはめちゃくちゃ寒くて、アリゾナの気候で感覚が狂っていたのもあってなかなかつらかった。
ちなみに帰りの便が遅延して、経由地であるロンドンに一日とどまる羽目になった。

月末にはGoogle CTF Qualsに参加してなんとか予選を通過した。

7月

WCTFに参加するためまたまた中国へ。途中で日本に数日滞在できる便を運営に取ってもらって、一時帰国中に寿司を食べに行ったりカラオケに行ったりした。WCTFではめちゃくちゃ運が良くて、なんと優勝することができた。 優勝賞金は$50000だったのだが、WCTFの運営はめちゃくちゃ優秀で、税金の分を払ってくれた上に一か月くらいで送金されてきた。どこかのいまだに払ってくれないCTF運営とは大違いである。
余談だが、帰りの飛行機が欠航になってまたしても一日中国に滞在する羽目になった。

月末にCBCTF Qualを開催した。問題は主にbinjaの人が作ったが、CBOJのジャッジシステム部分をスクラッチで書いたり他の問題をレビューしたりした。

8月

初のDEF CON CTF Finalsに参加。ラスベガスでカジノや射撃場に行ったりした。

SCTF finalsに初参加してギリギリ入賞した。ちなみに初めて韓国に行った。
TWCTF 4thを開催する。過去最高のレート97となってPlaidやHITCONとかと並んだ。

9月

CTFの合間を縫って進めていた研究のプロトタイプ実装が完成する。その後議論を経てアーキテクチャの改善などを進めていく。 大体まとまってきたところで一旦プロジェクトから抜けて、続きを日本で進めながら論文化を進めていくことにした。

ISUCON8の予選に参加する。時差が結構つらかったがなんとか予選を突破する。

帰国直前でDragon CTFに参加する。TokyoWesternsのメンバーが全然参加する気がなかったっぽいのでShellphishとして参加したところ、かなり調子が良くてなんと予選1位で通過してポーランドに行くことになった。

10月

情報科学若手の会に参加する。
この辺りの時期からPCをMacから買い替えようと思い始めてXPS15とX1 extremeを無限に比較検討していた。

運が良かったのでISUCON8本戦で優勝する。

icchy.hatenablog.jp

Google CTF Finalsに参加するためロンドンへ行く。終了30分前くらいで思いついた解法が間に合わなくて、1/2くらいflagが出たところで時間切れになった。これが解けていたら多分3位だったっぽくてつらい。

11月

CBCTF finalsを開催する。システムを作るのがとにかくしんどくて、本番も結構燃えた。Ruby書いたこと全然ないのにRailsを使うのはやめたほうが良い。tyageごめん。 打ち上げの後、CBCTFに来てたDragonSectorと0daysoberと一緒にカラオケに行った。 ようやくX1 extremeを購入してDragon CTFに持っていったところ、タイミングよくMBPがCTF前日にディスプレイが映らなくなって、ほとんどセットアップしてないX1 extremeで戦うことになった。これもあと5分くらいのところでsubmitが間に合わなくて入賞を逃した。ShellphishとしてFinalsに行ったのはなかなか貴重な体験だった。

12月

RealWorld CTFに参加するため今年4回目の中国へ。普通に楽しかったし来年もあれば参加したいなあと思うくらいには良いCTFだった。イベントの規模感がとにかくすごくて、会場にはそこら中に武装した警備員がたくさんいた。

年末に35C3 CTFに参加した。filemanagerという問題はXSS Auditorの挙動を逆手に取った情報リークの手法なのだが、あと少しのところで気づかなくて解けなかった。多分これが解けていたらCTF Timeは3位のまま終わってたんじゃないかと思う。p4強いなあ

まとめ

とにかくCTFで旅をしまくった年だった。飛びすぎてJALのステータスがついたが、ほとんど運営の金である。

TokyoWesternsとしてDEFCONに参加できたので去年の目標は達成できたと思う。来年はもっと多くのオンサイトCTFで入賞したい。

来年何するかはちょっといろいろ考えがあるので、2019年のトンカツにご期待下さい。それではよいお年を。

whywaita Advent Calendar 7日目の記事です

この記事は whywaita Advent Calendar 2018 - Adventar 7日目の記事です。

twiterを開くたびに寄稿者を募っているツイートが目に入って、かわいそうだったので書きます。

twitter.com

whywaitaさんといえば、ISUCONで優勝したり、CTFチームTokyoWesternsにも所属していたり、多才な方ですよね。

そんな多才なwhywaitaさんに、ぜひお伝えしたいことがあります。



































問題を解く気がないならSlackチャンネルに入らないでください。賞金を分ける計算が面倒です。 f:id:icchyr:20181207160005p:plain

以上です。来年もよろしくお願いします。

運が良かったのでISUCON8で優勝しました

Webサービスのチューニングを競い合うコンテストであるISUCONの第8回本戦に出場してきました。
実際に何をやったのか、詳しい内容はwhywaitaが書いてくれているのでこちらでは補足情報などを書きます。

blog.whywrite.it

チームについて

実はISUCON自体に参加するのは今回が初めてではありません。 このチームは2年前に結成してISUCON6から参加していたのですが、ずっと予選落ちで本戦には一度も出場したことがありませんでした。 メンバーはwhywaita, nomeaning, icchyの3人で、TokyoWesternsというCTFチームの中でもインフラ系に明るいメンバーで構成されています。 ISUCONの存在自体は以前から知っており、出てみたいという3人でチームを組むことになりました。

ISUCON6

最初にこのチームで出たときは「院試 in 死」というチームでした。名前の通り院試に苦しんでいた学部4年生の時のチームです。 言語は特に決まっていなくて、得意な言語はnomeaning→Ruby、icchy→Pythonという感じだったので、両方試してみて良い感じの方を使っていました。
結果はほとんど覚えていませんが、学生枠での予選突破すら難しいレベルだったと思います。 予選直後にGo言語がめっちゃ強いという話を聞いて、次からGoにするかみたいな話を冗談半分でしたのを覚えています。

ISUCON7

詳しい経緯は覚えてないのですが、覆面チーム(普段使っている名前を隠したチーム)にするかという動きになり、latin_cross 入院中 latin_cross (fried_chicken,backslash,6sec_dec)というチームで出ました。誰が誰なのかわかりませんね。
この時はGo言語で実装をしていましたが、Cache-Control: publicに気づかなかったのであまり得点が上がらず予選敗退となりました。

f:id:icchyr:20181023115835p:plain

実を言うと偶然40000点くらい出た時があって、そこで止めていれば学生枠で予選突破だったかもしれません。(画像にあるtmpというチームが学生枠最後のチームです) ベンチマーカーガチャの攻略が必要だということを学びました。

ISUCON8

今回のISUCONも覆面チームで出る流れになったのですが、全然案が出なかったところリーダーであるwhywaitaのアイデアで決まりました。

f:id:icchyr:20181023120925p:plain

なぜタイムゾーン云々の話をしているかというと、僕が4月から半年間アメリカに行っていて、予選の時はUTC-7から参加していたためです。 他の二人は日本にいましたが、つじつまを合わせるためにどっちか片方がサマータイムってことにすればええやろってことで、社会人に比べても朝早くから出勤しているnomeaningはJDTになりました。
ちなみにアメリカは18:00~2:00で、研究室から参加していたのですが、終わった後discordやtwitterやSlackに張り付いていたので朝方帰宅しました。 競技時間中はSlackでボイスチャットを繋いでコミュニケーションを取っていたのでリモートワークみたいな感じでサクサク作業できましたが、手書きで情報を共有する手段がなくて、やはり集まるのは大事ですね。

学部4年から3回目ということは全員修士2年で、少なくともwhywaitaは来年就職するため今回が学生チームとして出られる最後のISUCONだったので、今回こそは絶対に本戦行くぞと意気込んでいました。
学生枠で通れたら良いなくらいにしか思っていなかったですが、幸運にも一般枠で通過することができて結構盛り上がってました。

ISUCON本戦

初めての本戦なので結構緊張していました。 事前のマニュアルをほとんど読んでなくて、会場に入ったらまず席を確保しないといけないことを知りませんでした。 我々が到着したのは9:30くらいだったのですがほとんどの机は埋まっていて、円卓はちょっと狭かったので最終的に自販機の前のカウンターテーブルのような場所に座りました。 机が高いので椅子も高く硬く、長いこと座っているとお尻が痛くなるので結構立ったまま作業していたのですが、スタンディングデスク割といいですね。 ISUCONなのに椅子に座らないとはこれいかになんてくだらないことを考えていましたが、意外と作業しやすかったです。

競技の方ですが、whywaitaのタイムラインにもある通り最初1時間くらいはほとんど何もしていませんでした。docker-composeは手元の環境構築を楽にするための優しさなのかなと思いましたが、restartするたびにdocker-compose buildが走って厄介なので何も考えずにnginxとappを剥がしました。 MySQLはバージョンが8だったのでCentOSの上で動かすのがだるいなと思いましたが、そこはwhywaitaがやってくれたので助かりました。

あとは概ね例のタイムライン通りで、僕はコードをあまり書かずにひたすら眺めて、怪しそうだなーと思うポイントを適当に共有していたらいつのまにか高速化されていました。特に何もしてないですね。

今回優勝できたのは本当に運が良かったと思っていて、得点の減点ルールが割合によるものだったことが非常に大きいと思います。 実際takedashiはもっと多くの要素に気づいて高速化をしており、終わった直後に交わした会話からしてもこれは5万点いってるし負けたかな、という印象を受けたのですが、実際には減点で6割くらいまで得点が落ちていました。 今回のアプリケーションは決済系なので、リクエストを落としたら大きな減点に繋がるのはまあ確かに、と思いました。 顧客が増えれば増えるほどわずかなミスが命取りになるわけで、点数をあげればあげるほどエラーに注意しないといけないということですね。
競技終了1時間前に謎のfailが多発していたので安定化を真っ先にやるべきだったのですが、僕はしばらく別の高速化に取り組んでいました。 戦略としては4, 5万点を狙うよりも0にしない方を取るべきなのは当たり前で、ここは反省点です。 特にISUCON8は予選の段階でリクエストが増えてくると死ぬパターンがあったので、安定させて減点を減らすことに注力するのが最善だったのかもしれません。( enable_share を見つけたときにこうなるだろうという予感はしていました)

他の参加者と話して一番なるほどとなったポイントはやはり enable_share のABテストで、確率で返すという発想が全く思いつきませんでした。 確率でボタンが消えるなんて実際の利用者は困惑すると思いますが、サービス全体の体験が悪くなるよりかは確かにマシだと思います。 とはいえ enable_share をtrueにした状態でベンチを通過するのは必要だろうと思っていたので、結果的には良かったです。


繰り返し書きますが今回は本当に運が良かったと思います。 煽りのつもりは一切ないことを事前に断っておきますが、社会人でもあまり調子が出ない問題セットはあるということと、学生でもかなり腕の立つ人がいるということに驚きました。今回特別賞 (もっとも早く15000点を突破したチーム) はNaruseJunでしたし、予選では学生かつ一人チームであるtaildさんは全体4位でした。 懇親会で小耳に挟んだ「社会人はもうダメでこれからは学生だ」というフレーズがあって、僕は学生が社会人に安定して勝つのはまだまだ無理だと思っていますが、特定の分野なら勝てる!というケースはなくはないのかな、と思います。ただ今回は本当に運が良かったです。

本戦で学生が優勝するのは初らしく、優勝賞金100万に加えて学生優勝30万も頂いてしまいました。LINEさんありがとうございます。 パーっと使う気はないですが、アイコンにもある通りトンカツが好きなので美味しいお店を教えてもらえると喜びます。お待ちしております。

最後に、運営のみなさま本当にありがとうございました。(ガチャはありましたが)ベンチマークはサクサクで、開始時間が延期することもなく、問題も予選・本戦ともによく練られた良問でした。 次回も是非参加したいと思います。

第51回情報科学若手の会に参加してきました

毎年9月頃に開かれている情報科学若手の会というイベントがあって、その名前の通り若手と呼ばれる人たちが同じ場所に集まり数日間ディスカッションを交わしたり交流をする場があります。

wakate.org

実際にはディスカッションという堅苦しいものよりかは雑談に近い感じで、Webサイトにもある通りインフォーマルな雰囲気が特徴となっています。
僕はこの会に参加するのは3回目なのですが、実をいうと今まで一度もブログを書いてませんでした。すみません。

会の詳しい内容については他の参加者や幹事の方が数人ブログを書いてくださっているようです。

blog.unasuke.com

情報科学若手の会 #51 に参加してきた | κeenのHappy Hacκing Blog

saho-london.hatenablog.com

kuge-masa.hatenablog.com

hogashi.hatenablog.com

kurochan-note.hatenablog.jp

t.co

例年は山喜旅館という静岡県伊東にあるレトロなところで開催しているのですが、今年はいろいろな事情があって軽井沢にある軽井沢研修所というところで開催されました。

www.kato-karuizawa.jp

避暑地と呼ばれるだけあってやはり軽井沢は涼しくていいですね。朝に適当に付近を散歩していましたが、なかなかに快適でした。
この軽井沢研修所、公益財団法人 加藤山崎教育基金によって運営されているそうで、研修所の敷地内には広大な植物園があったりとなかなか「お金の力」を感じる場所でした。

http://www.kato-karuizawa.jp/pdf/map05.pdf

僕は実際に見ていないのですが、他の参加者によると近くで温泉を掘っていた、なんてこともあったそうです。すごいですね。
じつはこの研修所、天皇陛下も訪れるようで、施設案内には乗っていない謎の部屋があったりしましたがどうやら本当のようですね。

天皇皇后両陛下のご日程:平成30年(7月~9月) - 宮内庁

ご挨拶,少時ご歓談(長野県知事,県議会議長,県警察本部長) とあるのであの謎の部屋を使ってご歓談されるのかと存じます。

セッションの方は面白い発表が多く、一番印象に残ったのはやはりコンテナデータセンタの話でした。「コンテナ即売会」「おっさんレンタル」などのパワーワードに加えて、法律や税金などの問題や実際の作業風景に詳しく言及されていて大変面白い発表でした。世界でも類を見ないトピックなので海外の人の反応はどうなんだろう、と思ったら意外にも英文記事になっていませんでした。少なくとも会場の反応は、質問時に「自分もコンテナデータセンタ欲しくなったんですが」と始める人が多く現れて大人気でした。
他にも面白い発表が多くて、「Lispはすごい」ということがわかりました。Lispはすごいですね。
久しぶりにntddkに会いました。彼の発表 The Art of De-obfuscation は部分的にしか知らなかったことが綺麗に組み立てられていく感じでとても良かったです。Program Synthesisあたりの話は詳しい実装を読んだことがなかったのでなるほど、となりました。

speakerdeck.com

一応僕もショートで「CTFと現実世界」というタイトルで発表をしてきました。

docs.google.com

CTFはあくまでも競技だけど、現実世界のバグとの関わりや実際の研究成果が深く影響する場でもあるという主張です。
この手の話はそもそも最新の情報を知っている人がかなり少ない(実際のCTFプレイヤーくらい)ので、今回発表しました。感想や意見などお待ちしております。

ちなみに今回は幹事の一人であるkyontanの車に5人で乗って移動していました。蕎麦やステーキを食べられたし温泉も入れたしで最高でした。しばらく日本にいなかったこともあって少し感動しました。車の手配・運転をしてくれたkyontanにこの場を借りてお礼を。ありがとうございました。

今年も楽しかったので来年も参加したいですね。幹事の皆さま本当にありがとうございました。

CTFの問題を作るときに気をつけること

たまにポエムっぽい記事を書きたくなることがあるんですけど、今回はそういう回です。長いので結論だけ知りたい人はこちらへどうぞ。

CTFブームもここ数年の話ではなくなり、日本でもCTF開催側にチャレンジするチームや会社などが増えてきました。TokyoWesternsを結成して本格的にCTFを始めた2014年終わり頃に比べると、世界的にも日本的にもその数はだいぶ増えたんじゃないかなと思います。日本に限った話で言えば、やはりSECCONをベースとした団体の貢献が多いのは言うまでもないでしょう。Twitterのbioに「CTF」というワードを追加している人の数もかなり増えてきたように思います。
さて、こうした中で多くのCTFを経験すると「問題の質」というのがなんとなくわかるようになってきます。日本のCTFだけだと正直なところまだまだ数が少ないので難しいですが、海外で開かれている多くのCTFに参加するとなんとなく良い問題とそうでない問題がわかるようになってくると思います。そして、CTFを開催する時は「そうでない問題」側に取られてしまう問題の数をなるべく減らしたい、できれば0になるように努力します。

では一体どのような問題が「良い問題」とされるのでしょうか?(ここでは「CTFは現実世界で役に立つ・立たない問題」を議論する気はなくて、そういうのを期待している人は別のところでやってください。)
これについては正直なところ個人の価値観に大きく依存すると思いますが、なるべく一般論として言える(と僕は思っている)ポイントについて言及します。(これは自戒の念も込めて、多くのブーメランを含んでいます)

パズル要素がある

まず一番重要かつ難しいポイントです。我々は基本的には「ハッカー」という、創意工夫によって面白いなにかを達成することが好きな生き物ですから、解法が自明でないことが求められます。この解法が自明足るか否かは参加者によるところが大きくて、多くにとって非自明であればあるほどその問題は難しいと言えます。もし他のCTFで同様のものが出題されている場合はいわゆる「既出」と言われ、そのCTFに参加した人にとっては自明・簡単になります。逆に言えば、より多くのCTFに参加し解法パターンの引き出しを増やすことで、より多くの問題を解けるようになるということです。大学入試とかと同じ原理です。
このパズル要素を適度に含めるのは非常に難しくて、自明でない要素を考えつくには自明な要素をなるべく多く知っている必要があるからです。「CTFに参加したことがない運営が良い問題を作るのは難しい」と言われる所以だと僕は思っています。

本質から外れた妨害要素を入れない

問題を作る上でよく勘違いする人が多いのですが、難しいから良い問題、多くのチームに解かれなかったから良い問題というわけではありません。
たとえばpwnableやreversingの問題があったとして、PowerPCアーキテクチャとか出てきたらうんざりします。なぜうんざりするかというと、「PowerPCは今では一般的ではない」からです。その問題がもしPowerPCの特徴に関連した、あるいは実際にPowerPCで動いていた何かを模して作られたというのならばまだ良いのですが、単純に解析やエクスプロイトを面倒にする目的でそのようなアーキテクチャを選択するのは避けるべきです。もちろんこれはPowerPCに限ったことではなくて、思考停止してVMProtectを噛ませるとかそういうのも含みます。本質的でないところで時間を溶かすのはプレイヤーにとって非常に悪い体験ですし、なにより作問者がその問題の本質部分に自信がないことの現れです。もし作問者が、プレイヤーがこの妨害を突破することにも意味があると考えているならば、突破した時点で部分点を与えるべきでしょう。部分点がない場合は、途中のチェックポイントを通過していようがいまいが解けてなければ0点です。

問題の使い回しをしない

一番やってはいけないことです。CTFの問題を作るというのは非常に大変な作業ですが、埋め合わせのために過去問を適当に変えて出すというのは禁じ手です。少し手を加えるだけで解法が異なってくるような場合はハッカー心をくすぐられて大いに歓迎されますが、例えばプログラミング系の問題でパラメータを適当に変更しただけとか、そういうのは非常に嫌がられます。
なぜかというと、過去問は既にある程度問題を理解しているチームが存在するためです。そのようなチームは過去のコードを再利用しないはずがなくて、これは大きなハンデになってしまいます。有利になったチームは「ラッキー」くらいにしか思いませんが、不利になったチームからすると相手がチートしたときくらい悔しい思いをするでしょう。もちろんこの場合は運営に責任がありますが。

「面白くない」要素が可能な限り排除されている

できれば問題はなるべく楽しく解きたいものです。もちろん何が楽しい・楽しくないかは個人にもよると思いますが、少なくとも無意味な作業は誰だってやりたくないでしょう。 例えばパスワード辞書を使ってブルートフォースをするとログインできて、そこから問題が始まりますみたいなのは最悪です。adminは適当なパスワードを使っているらしいみたいな文言があればまだマシですが、そう言った文脈を一切抜きに辞書攻撃を求めるのはもってのほかです。パスワード辞書なんて正直無限にありますし、片っ端から試せばサーバに負荷もかかります。CTFはペネトレーションテストではありません。どうしてもそういう問題を出したいのならば、参加者数を限定した状態で、相当のスペックのサーバを用意すべきでしょう。もっともそのような環境が用意できたとしても僕は出題したいとは絶対に思いませんが。
他には .index.php.swp などの典型的なURL予測も含まれると思いますが、これは僕が過去にやってしまったので強い言及は避けます。

解いたときに達成感が得られる

CTFに熱中している人の多くは、「問題を解いた時に得られる達成感が忘れられない」と言います。この達成感は問題を解いたこと自体もそうですが、問題を解く過程における試行錯誤の一つ一つが積み重なって最終的な解を導き出した、という体験を多く含んでいると思います。この試行錯誤の過程が長ければ長いほど問題は難しく感じ、また解けた時の達成感も大きいです。
では試行錯誤の過程をどうやって長くするかというと、

  1. パズル要素部分を難しくする
  2. 要素の数自体を増やす

という2種類の方法があります。
1は先に説明した通りで、なるべく典型ではない新規性のある要素が求められます。これは非常に難しいので、多くのCTFでは2の方法をよく見ます。
2は必ずしも良い方法ではなくて、単純に問題の組み合わせになっているだけ、みたいなのは避けるべきです。よく見かけるパターンの一つに「Webパートを解くとPwnパートが出てくる」といったものがありますが、例えばWebがインターフェースとなって呼び出しているバイナリをexploitする問題といった、何らかの関連性やストーリー性を持たせるべきでしょう(全く独立した2つの問題ならば最初から別々に出すべきです)。ただし各要素がそれなりの難易度を持っている場合、前述したように部分点を与えた方がよいです。

まとめ

少し長くなりましたが、完結にまとめると

  • 運営するなら少しでもいいからCTFに出ろ
  • どうでもいい要素を入れるな
  • 使い回しをするな
  • エスパーをやめろ(CTFはpentestではない)
  • 単に要素をくっつけただけの問題は複合問題ではない

です。よろしくお願いします。

また、CTFを開催するときに気をつけるべきポイントについては、アメリカのPlaid Parliament of Pwning (PPP) というチームの良い資料がこちらにあるので、読んだことない人はぜひ一度目を通して見てください。

pip install --upgrade pip (10.0.0) 後の奇妙な挙動について

TL;DR

pipを10.0.0に更新した後に

~$ pip
Traceback (most recent call last):
  File "/usr/bin/pip", line 9, in <module>
    from pip import main
ImportError: cannot import name main

というエラーが出た場合の対処方法をこちらにまとめた。

こちらの記事で言及したが、pipの挙動がちょっとおかしい。少し調べてみたところ原因がわかったので共有する。一応概要も載せておく。

概要

debian9でaptでpipを導入したあとに pip install --upgrade pip を行った場合、pipが使えなくなる。具体的には

~$ pip
Traceback (most recent call last):
  File "/usr/bin/pip", line 9, in <module>
    from pip import main
ImportError: cannot import name main

のようにエラーがでて、pipコマンドが使えなくなる。
また、python-pip を消して手動でインストールしても pip install でパッケージがインストールされない。debian9またはubuntu17.10を使用しているユーザーは特にハマりやすい。

原因

それぞれの現象の原因を詳しく示す。

ImportErrorの原因

aptでインストールした場合は9.0.1がインストールされるが、pipの最新版は10.0.0である (リリースされたのはつい先日のことである) 。
9系のpipスクリプトの内容は次のようになっている。

pipでインストールしたもの

#!/usr/bin/python

# -*- coding: utf-8 -*-
import re
import sys

from pip import main

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    sys.exit(main())

aptでインストールしたもの

#!/usr/bin/python
# GENERATED BY DEBIAN

import sys

# Run the main entry point, similarly to how setuptools does it, but because
# we didn't install the actual entry point from setup.py, don't use the
# pkg_resources API.
from pip import main
if __name__ == '__main__':
    sys.exit(main())

どちらも pip.main をそのまま実行していることがわかる。またaptはpipのスクリプトをそのまま使うのではなく、ディストリビューション向けにカスタマイズされている。
次に pip install pip で自動的にインストールされる最新版10.0.0のpipスクリプトの内容を見てみると、pip._internal.main を実行していることがわかる。

#!/usr/bin/python

# -*- coding: utf-8 -*-
import re
import sys

from pip._internal import main

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    sys.exit(main())

実際にpythonのライブラリが配置されているディレクトリ構造を確認してみると、明らかに構成が変わっていることがわかる。

9.0.1

~$ ls /usr/lib/python2.7/dist-packages/pip
commands  models      req    vcs      basecommand.py   baseparser.py   cmdoptions.py   download.py   exceptions.py   index.py   __init__.py   locations.py   __main__.py   pep425tags.py   status_codes.py   wheel.py
compat    operations  utils  _vendor  basecommand.pyc  baseparser.pyc  cmdoptions.pyc  download.pyc  exceptions.pyc  index.pyc  __init__.pyc  locations.pyc  __main__.pyc  pep425tags.pyc  status_codes.pyc  wheel.pyc

10.0.0

~$ ls .local/lib/python2.7/site-packages/pip
_internal  _vendor  __init__.py  __init__.pyc  __main__.py  __main__.pyc

これに加えてpythonのライブラリが参照されるパスの順序は、$HOME/.local/lib/python2.7/site-packages の方が /usr/lib/python2.7/dist-packages よりも優先度が高い。

~$ python -c 'import sys; print sys.path'
['', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-x86_64-linux-gnu', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/home/icchy/.local/lib/python2.7/site-packages', '/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/gtk-2.0']

そのため、import pip で参照されるpipは $HOME/.local/lib/python2.7/site-packages/pip (10.0.0) を指している。しかしaptでpipがインストールされている場合、pipコマンドで実行されるスクリプトは先に示した通り pip.main であり、これは10.0.0においては存在しない。
つまり最初の ImportError はpip9を想定しているスクリプトがpip10のライブラリを使おうとしたことによって発生したものである。

この問題についてのissueは上がっているのだが、メンテナーの考えとしては 「_internal に全ての機能を移動させたのにはそれなりの理由があって、pip.main はpip10では絶対にサポートしない」というものらしい。

github.com

おそらくディストリビューションのパッケージ更新によってpipスクリプトがアップデートされるのを待つしかないと思う。もちろん先に自分でpipスクリプトに手を加えて、pip._internal を使うようにするのはアリだとは思う。

pip installが動かない原因

まずDebian9 (stretch), Ubuntu 17.10 (artful) 以降の python-pip パッケージは 9.0.1-2 である。

python-pip 9.0.1-2からパッケージのsourceを取得して展開してみると、debian/patches/set_user_default.patch という名前のパッチが存在し、パッチの説明には以下のような記述がある。

When running as a normal user in a non-virtual environment, default to
--user and --ignore-installed.  When inside virtual environments or when
running as root, keep the default behavior.

つまりvirtualenv環境下でない場合は、デフォルトで --user フラグ ($HOME/.local/ 以下にインストールするオプション) を追加する。これにより、このバージョンの python-pip を使用していると pip install がほとんど成功するようになるため1 、sudoをつけずともインストールされるのが当たり前という感覚になってしまう。

一方pip10ではinstall時にverbosity levelを2以上 (-vv) にしないとエラーメッセージが見えなくなった。これはバグと認識されていて、どうやら返り値のチェックを EACCES ではなく EPERMにしてしまったことが原因らしい。

github.com

そのため、aptの python-pip 9.0.1-2 ユーザーがpip10を使うと pip install はいつも通り --user で実行されたものだと勘違いしてしまい、エラーメッセージも特に出ないのでパッケージがインストールされたように見えてしまうわけである。もちろんよくメッセージを見ると Successfully installed [package名] というメッセージが最後に出ていないので、インストールが正常に終わっていないことがわかるのだがまあなかなか気づきにくいと思う。

解決策

これらに対していくつかの解決策を考えた。

pip9を使い続ける

システムのpipに手を加えるのは少々危険が伴う。もし既存のパッケージに一切影響を与えたくないならば、pip9をそのまま使い続けるのが一つの手である。
set_user_default.patch が当たってるパッケージを使っている場合、もし pip install pip を実行してしまうと $HOME/.local/lib にpip10.0.0がインストールされてしまうため、例のバージョン違いによってpipが起動しなくなる。この場合は pip install pip==9.0.1 などを実行してインポートされるpipパッケージのバージョンを揃えておくと良い。

pip10に乗り換える

aptの python-pip を消して、/usr/local/bin/pip にpipをインストールする。pip10のバグによりインストール失敗のメッセージが見えなくなってしまうが、alias pip='pip -vv' とでもしておくか、普段から気をつければまあ問題はない。
python-pip で入れたpipでインストールするのはなるべく避けた方が良くて、公式が推奨しているget-pip.pyを使うのがよい。先ほど述べたようにインストール失敗のメッセージが出ない上に、本来pipはデフォルトのインストール先に /usr/local/ 以下を使うので、システムにインストールする場合はsudoをつけるのを忘れないようにする。もちろん -vv を使えばエラーは見える。

~$ python get-pip.py -vv
Created temporary directory: /tmp/icchy/pip-ephem-wheel-cache-nCRTal
Created temporary directory: /tmp/icchy/pip-install-n4gtqZ
Collecting pip
...
Installing collected packages: pip


Traceback (most recent call last):
  File "/tmp/icchy/tmp_NGCSM/pip.zip/pip/_internal/commands/install.py", line 335, in run
    use_user_site=options.use_user_site,
  File "/tmp/icchy/tmp_NGCSM/pip.zip/pip/_internal/req/__init__.py", line 49, in install_given_reqs
    **kwargs
  File "/tmp/icchy/tmp_NGCSM/pip.zip/pip/_internal/req/req_install.py", line 748, in install
    use_user_site=use_user_site, pycompile=pycompile,
  File "/tmp/icchy/tmp_NGCSM/pip.zip/pip/_internal/req/req_install.py", line 961, in move_wheel_files
    warn_script_location=warn_script_location,
  File "/tmp/icchy/tmp_NGCSM/pip.zip/pip/_internal/wheel.py", line 314, in move_wheel_files
    clobber(source, lib_dir, True)
  File "/tmp/icchy/tmp_NGCSM/pip.zip/pip/_internal/wheel.py", line 285, in clobber
    ensure_dir(destdir)
  File "/tmp/icchy/tmp_NGCSM/pip.zip/pip/_internal/utils/misc.py", line 86, in ensure_dir
    os.makedirs(path)
  File "/usr/lib/python2.7/os.py", line 157, in makedirs
    mkdir(name, mode)
OSError: [Errno 13] Permission denied: '/usr/local/lib/python2.7/dist-packages/pip-10.0.0.dist-info'
Cleaning up...

システムのpipに変更を加えずにpip10を使う

pipコマンドで起動するのがそもそもの原因なので、python -m pip でpipを起動すればなんら問題はない。

alias pip='python -m pip'

この場合システムのpipはどうなるんだと思うかもしれないが、$HOME/.local/lib をパッケージのパスに追加しているのでrootの場合は /usr/lib/python2.7/dist-packages/pip が参照される。つまり通常ユーザーはpip10を使い、システムはpip9を使うという奇妙な状態になる。もちろん各パッケージ自体はpythonのバージョンにしか依存しないので、pipのバージョンによって異なるパッケージが入ることはまずない。

まとめ

この問題の根本的な原因はpip10でディレクトリ構成の変更が行われたことにあった。変更にはそれなりの理由があって、pip側で解決策が打たれることはおそらくない。ディストリビューションのアップデートを待つか、自分で対応するしかなさそうというのが現状である。
解決策としてはこちらに示した3通りの方法が考えられる。他にも良い解決策があれば是非教えてほしい。

余談

pipのドキュメントは https://pip.pypa.io/en/stable/ で見られる。 pip 9.0.1のドキュメントを見るためにstableの部分を差し替えて https://pip.pypa.io/en/9.0.1/ にアクセスしたのだが、なんと403になっていた。9.0.3や10.0.0も同様なので、おそらくstableにしかアクセスできないようにして、そのシンボリックリンクとかを最新版に向けてるとかそんなところだと思う。


  1. 通常 --user をつけない場合は/usr/local/以下にインストールしようとするため、root権限がないと失敗する。

近況1

案の定日記を書くのが面倒になったので毎日の更新をやめた。

いろいろやっている。取り組むプロジェクトの候補を一つ一つ吟味しながら軽く論文を読んだり、OSSのツールがあった場合は適当に試したりしている。
当初はfuzzingなどの自動解析系をやりたいと思っていたが、ここにきて非常に興味深いプロジェクトが増えたので揺れてきた。

アメリカでの生活にはだいぶ慣れた。残るタスクは各種の住所変更と社会保障番号 (SSN) の取得である。SSNがないと税金が払えなくて、給料をもらえなくなってしまうので餓死する。
新しい宿は、インターネットが突然数分死ぬことがあるという点を除けばかなり快適である。インターネットのために研究室へ移動していると言っても過言ではない。今持ってるSIMはなぜかテザリングが使えない。

所持金がそろそろやばいことになりつつあるので外食や余計な買い物を控えている。と思ったら今日は$13くらいするラーメンを食べてUberで帰った。計画性がない。まあUberが紐づいている口座は日本のやつなので直接の影響はないのだが。
なぜUberを使ったかというと、研究室の人に「夜バスに乗るのは危ない」と言われたからである。聞くところによると、ルームメイトが夜バスに乗って強盗に遭い、持ち物全てを奪われたらしい。流石にPCを取られると終わりなのでかなり警戒している。

オンラインでいくつかのCTFにTokyoWesternsとして参加した。HITB-XCTFはなんと1位、Midnight Sun CTFは2位でどっちもfinalsがあるので時間と金と運があれば行くつもりである。最近CTFであまり活躍できていなかったのだが、出発前に参加した0ctfでは最後5分くらいでWebの解法を思いついて通せたし、先の2つのCTFでは結構問題解けたし幸先が良い。この調子でDEFCON予選とかも突破したい。

Linux環境を一新したらpipが壊れた。

この現象はすごい面白くて (こちらとしては全く面白くないが) 、debian9で python-pip をインストールするとシステム (/usr/bin/pip) にインストールされるが、以降のpipコマンドは $HOME/.local/ 以下にパッケージをインストールするため、もしpipの更新をかけると新しいpipのライブラリが $HOME/.local/lib に配置され、以降はそちらのライブラリが優先的に参照されるがpipコマンドは /usr/bin/pip のままなのでバージョン違いにより実行できないというものである。python -m pip を代わりに実行したり、$HOME/.local/bin$PATH に追加することによってこのエラーは回避できるのだが、なんと $HOME/.local/bin/pip は正しくパッケージをインストールできない。全くもってこの原因はつかめていないが、最新のdebian9でやると発現するので気になる各位は試してほしい。そしてもしよければ解決策を教えてほしい。

また気が向いたら更新する。