こんにちは、開発部で技術基盤を担当しているid:Yasaichiです。
推しメンである乃木坂46の橋本奈々未さんが先日卒業・芸能界引退を発表され、涙でディスプレイが見えづらい日々が続いています。
本エントリでは、RubyKaigiに参加するまでとその成果 - てくすた でも触れられていた、CTF(Capture The Flag)風のクイズの紹介と解説を行います。
クイズの紹介
スポンサーブースでの出し物として、ノベルティと併せて作成しました。
https://rubykaigi2016pixta.herokuapp.com
出し物に関しては、お菓子を始めとする様々な案が出ました。
最終的に、エンジニアらしいことを体験型・参加型で提供したら印象に残るはずだという考えのもと、CTF風のクイズを作成することになりました。
エンジニア向けのクイズとしては、2013年のRubyKaigi向けに任天堂さんが作成されたCode Puzzleがあり、こちらをお手本にして作成しました。
"巨人の肩の上に立つ"戦略が功を奏したのか、おかげで期間中に100以上の回答を頂き、正解者も40名弱いました。
クイズの解説
それでは、前述のクイズを順番に解説していきましょう。
1. 狙いを定める
まずは、問題ページのHTMLを見てみます。
<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8" /><meta content="IE=edge" http-equiv="X-UA-Compatible" /><meta content="width=device-width, initial-scale=1" name="viewport" /><title>RubyKaigi2016::PIXTA</title><meta content="CTF for RubyKaigi 2016" name="description" /><meta content="RubyKaigi2016::PIXTA" property="og:title" /><meta content="website" property="og:type" /><meta content="https://rubykaigi2016pixta.herokuapp.com" property="og:url" /><meta content="https://rubykaigi2016pixta.herokuapp.com/assets/small-dd2d7a8d5d1a4f9b384656ef57d1b8eb028dac9149097ab74a9adcfc5997624a.jpg" property="og:image" /><meta content="RubyKaigi2016::PIXTA" property="og:site_name" /><meta content="CTF for RubyKaigi 2016" property="og:description" /><link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" /><link href="https://code.getmdl.io/1.2.0/material.red-deep_orange.min.css" rel="stylesheet" /><link rel="stylesheet" media="screen" href="/assets/application-25df561411b4511777772130607dd3c04fb0476d4f8e83f4f85c023b5b519f94.css" /><script defer="" src="https://code.getmdl.io/1.2.0/material.min.js"></script><script src="https://www.google.com/recaptcha/api.js"></script><script src="/assets/application-7eb45342b79f84cd010e41837d139a813d4b3892b87159c33ef08b5d0e970fff.js"></script><script src="/assets/footerFixed-7d7088a2610e4c4a0e8f09431a72950c623f382f1dabbc5c8d7a4f32165ed8b9.js"></script><script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-83676201-1', 'auto'); ga('send', 'pageview');</script></head><body><div class="mdl-layout"><p class="ruby_love"><a href="https://pixta.jp"><img class="logo" src="/assets/logo-e4299cd062326f2748f6fcb9d5aaa5cde75b5368bccbcedb5984cf6ebb2eb08c.png" alt="Logo" /></a></p><div class="pixta-card-wide mdl-card mdl-shadow--2dp mdl-typography--text-center"><div class="mdl-card__title"><div class="mdl-card__title-text"><a download="rubykaigi.jpg" href="/assets/rubykaigi-90ca46a974a5309ec1e0e24e9fc819f03f70aabc14deb002a846e32693808372.jpg"><img class="main_img" src="/assets/small-dd2d7a8d5d1a4f9b384656ef57d1b8eb028dac9149097ab74a9adcfc5997624a.jpg" /></a></div></div><div class="mdl-card__actions mdl-card--border"><form action="/answer" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="✓" /><input type="hidden" name="authenticity_token" value="LH90inMEc5XZOFKX2cbKZqUrRhgmrsJrpisUZm6WU+NYaufhPIzvxy1Cgb0DkusutbTxWaCtAU2o+rBKXn44nw==" /><div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label"><input ,="" class="mdl-textfield__input" id="answer" name="answer" type="text" /><label class="mdl-textfield__label" for="answer">Flag</label></div><div class="recaptcha"><script src="https://www.google.com/recaptcha/api.js" async defer></script> <div class="g-recaptcha" data-sitekey="6Led-CgTAAAAAK_1k7jH14BWjNhNjfAsr4Dm4t2g"></div> <noscript> <div style="width: 302px; height: 352px;"> <div style="width: 302px; height: 352px; position: relative;"> <div style="width: 302px; height: 352px; position: absolute;"> <iframe src="https://www.google.com/recaptcha/api/fallback?k=6Led-CgTAAAAAK_1k7jH14BWjNhNjfAsr4Dm4t2g" frameborder="0" scrolling="no" style="width: 302px; height:352px; border-style: none;"> </iframe> </div> <div style="width: 250px; height: 80px; position: absolute; border-style: none; bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;"> <textarea id="g-recaptcha-response" name="g-recaptcha-response" class="g-recaptcha-response" style="width: 250px; height: 80px; border: 1px solid #c1c1c1; margin: 0px; padding: 0px; resize: none;" value=""> </textarea> </div> </div> </div> </noscript> </div><button class="mdl-button mdl-js-button mdl-button--raised" type="submit">Submit</button></form></div></div></div><footer class="mdl-mini-footer" id="footer"><div class="mdl-mini-footer__left-section"><ul class="mdl-mini-footer__link-list"><li><a ,="" href="https://pixta.jp/" onclick="trackOutboundLink('https://pixta.jp'); return false;">ストックフォトならPIXTA</a></li><li><a ,="" href="https://fotowa.com/" onclick="trackOutboundLink('https://fotowa.com'); return false;">自然でオシャレな出張撮影ならfotowa</a></li><li><a ,="" href="https://snapmart.jp/" onclick="trackOutboundLink('https://snapmart.jp'); return false;">Snapmart</a></li><li><a ,="" href="https://www.wantedly.com/projects/33014" onclick="trackOutboundLink('https://www.wantedly.com/projects/33014'); return false;">採用情報</a></li></ul></div><div class="mdl-mini-footer__right-section"><ul class="mdl-mini-footer__link-list"><li>© 2016 <a ,="" href="https://pixta.co.jp/" onclick="trackOutboundLink('https://pixta.co.jp'); return false;">PIXTA Inc.</a></li></ul></div></footer></body></html>
ざっと眺めた感じ、HTML自体にフラグが隠されているわけではなさそうです。
そこで、ページ内に表示されている女性の画像にフラグが隠されていると考え、画像の上にカーソルを持っていきます。
すると、なぜかリンクが設定されており、クリックすると画像をダウンロードすることができます。*1
ここで改めて画像周りのHTMLタグを確認すると、表示されている画像とダウンロードできる画像は異なることがわかります。
<a download="rubykaigi.jpg" href="/assets/rubykaigi-90ca46a974a5309ec1e0e24e9fc819f03f70aabc14deb002a846e32693808372.jpg"> <img class="main_img" src="/assets/small-dd2d7a8d5d1a4f9b384656ef57d1b8eb028dac9149097ab74a9adcfc5997624a.jpg"> </a>
画像ビューアで確認すると、表示される内容は同じです。
しかし、2つのファイル容量を比較すると、明らかな差があることがわかります。
rubykaigi.jpg
: 2.1MBsmall-*.jpg
: 334KB
ここから、前者に何らかの情報が埋め込まれているだろうと推測できます。
2. 画像ファイルを解析する
解析にあたり、まずはfile
コマンドを実行してファイルの種類を確認しましょう。
$ file rubykaigi.jpg rubykaigi.jpg: Netpbm PPM image text
拡張子は.jpg
になっていますが、ファイル形式はPPM(portable pixmap format)のようです。
head
コマンドを実行するとP3
から始まっていることから、ASCIIでエンコードされたテキストであることがわかります。(参考: Netpbm format - Wikipedia)
$ head rubykaigi.jpg P3 500 333 255 121 98 66 115 94 63 109
PPMではコメントを埋め込むことができるので、ここにヒントが含まれているかどうかを探してみましょう。 *2
# `#`で始まる行がコメント $ grep -c "^#" rubykaigi.jpg 91501
とても怪しそうですね!先頭のものをいくつか見てみます。
$ grep -n "^#" rubykaigi.jpg | head 204004:# Uncomment the following lines 204005:# 0 204006:# 0 204007:# 0 204008:# 0 204009:# 0 204010:# 0 204011:# 0 204012:# 0 204013:# 0
204005行目以降をアンコメント*3すればよさそうです。
3. 画像ファイルを加工し、フラグを取得する
あとはアンコメントをするだけなのですが、該当行が多いため、エディタを使って行うのは大変そうです。
そこで、以下のコマンドで対処することにしました。
$ sed -e "s/^# \+\([0-9]\+\)/\1/g" rubykaigi.jpg | convert - result.jpg
加工後の画像ファイルを開いてみると、フラグが現れました!
作成にあたって
ピクスタでは、写真・イラスト・動画のデジタル素材をオンライン上で販売するマーケットプレイスサイト「PIXTA」を開発・運営していることもあり、画像を使ったクイズにしようということはすぐに決まりました。
しかし、私自身が画像系のCTFの問題を自力で解けた経験があまりなかったこともあり、どのくらいの難易度にするかという点が問題になりました。
その後、正解ページに「何人目の正解者か」をRubyKaigiのハッシュタグとともにツイートできる機能を用意しようというアイデアが出ました。
これは、ブースに足を運んでいない参加者の方にも認知してもらえるよう期待してのことです。
このアイデアが出たことで、「セッションの合間に気分転換がてらに取り組んでもらい、期間中に解けるくらいの難易度しよう」という方向性が決まり、具体的な問題のロジックを考え始めることができました。
実際にブースでお話しした方に、次の日に「解けたよ〜」と直接報告しに来てもらった時には、苦労が報われた気がしてよかったです。
おわりに
本エントリでは、9月に行われたRubyKaigi向けに作成したCTF風のクイズの紹介と解説を行いました。
作成にあたっては、問題の難易度調整など大変なこともありましたが、おかけで多くの方に取り組んで頂きました。
また、スポンサーブースにおけるコミュニケーションのよいきっかけにもなり、苦労した甲斐があったように思います。
ピクスタでは、Rubyでサービス開発を行いたいエンジニアを募集しています!