読者です 読者をやめる 読者になる 読者になる

てくすた

ピクスタ株式会社のエンジニア・デザイナーがつづるよもやまテクニカルブログです

RubyKaigi 2016で公開したCTF風クイズの紹介・解説

Ruby イベント

こんにちは、開発部で技術基盤を担当しているid:Yasaichiです。
推しメンである乃木坂46の橋本奈々未さんが先日卒業・芸能界引退を発表され、涙でディスプレイが見えづらい日々が続いています。

本エントリでは、RubyKaigiに参加するまでとその成果 - てくすた でも触れられていた、CTF(Capture The Flag)風のクイズの紹介と解説を行います。

クイズの紹介

スポンサーブースでの出し物として、ノベルティと併せて作成しました。

RubyKaigi2016::PIXTA

出し物に関しては、お菓子を始めとする様々な案が出ました。
最終的に、エンジニアらしいことを体験型・参加型で提供したら印象に残るはずだという考えのもと、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="&#x2713;" /><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(&#39;https://pixta.jp&#39;); return false;">ストックフォトならPIXTA</a></li><li><a ,="" href="https://fotowa.com/" onclick="trackOutboundLink(&#39;https://fotowa.com&#39;); return false;">自然でオシャレな出張撮影ならfotowa</a></li><li><a ,="" href="https://snapmart.jp/" onclick="trackOutboundLink(&#39;https://snapmart.jp&#39;); return false;">Snapmart</a></li><li><a ,="" href="https://www.wantedly.com/projects/33014" onclick="trackOutboundLink(&#39;https://www.wantedly.com/projects/33014&#39;); 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(&#39;https://pixta.co.jp&#39;); 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.1MB
  • small-*.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

加工後の画像ファイルを開いてみると、フラグが現れました!

f:id:Yasaichi:20161114164735j:plain

作成にあたって

ピクスタでは、写真・イラスト・動画のデジタル素材をオンライン上で販売するマーケットプレイスサイト「PIXTA」を開発・運営していることもあり、画像を使ったクイズにしようということはすぐに決まりました。
しかし、私自身が画像系のCTFの問題を自力で解けた経験があまりなかったこともあり、どのくらいの難易度にするかという点が問題になりました。

その後、正解ページに「何人目の正解者か」をRubyKaigiのハッシュタグとともにツイートできる機能を用意しようというアイデアが出ました。
これは、ブースに足を運んでいない参加者の方にも認知してもらえるよう期待してのことです。
このアイデアが出たことで、「セッションの合間に気分転換がてらに取り組んでもらい、期間中に解けるくらいの難易度しよう」という方向性が決まり、具体的な問題のロジックを考え始めることができました。

実際にブースでお話しした方に、次の日に「解けたよ〜」と直接報告しに来てもらった時には、苦労が報われた気がしてよかったです。

おわりに

本エントリでは、9月に行われたRubyKaigi向けに作成したCTF風のクイズの紹介と解説を行いました。
作成にあたっては、問題の難易度調整など大変なこともありましたが、おかけで多くの方に取り組んで頂きました。
また、スポンサーブースにおけるコミュニケーションのよいきっかけにもなり、苦労した甲斐があったように思います。

ピクスタでは、Rubyでサービス開発を行いたいエンジニアを募集しています!

*1:HTML5のdownload属性に対応したブラウザの場合

*2:PPM(またはPNM)形式の仕様を知っている必要があるので、ここに気づけるかがポイントになりそうです

*3:コメントアウトの対義語

メンテナンス性に優れたcss設計 - メンテナブルcss

こんにちは、デザイナーの宇田川です

突然ですが、チーム開発をしていて、cssの運用で困ったことはありませんか??

例えば、

- このスタイルを変更して、他のページに影響が出そうだから修正していいか分からない.......

- デザインが似たページをつくる時に、コードを再利用するのに問題はない?

- スタイルを修正したら、他のページのデザインが崩れてしまった....orz

など、スタイル設定が少ないサイトや1人でコーディングしているならまだいいのですが、
大規模なサイトになってくると、複数人でのチーム運用をすることになるので、

1人1人が自分のやり方でコーディングすると、非常にメンテナンス性の悪い(コストがかかる)運用になってしまいます。

そこで、今回は、サイトの規模、チームの人数にも関係なく有用なメンテナブルcssをご紹介します。

まず初めに

PIXTAでは、5人のデザイナーがサイト運用しています。
全員がコードを統一できるように、cssのルールを設定しています。

クラス名

見た目からでなく、目的や役割に基づいてclass名前をつける

  • クラス名は小文字で、ハイフンを使う(アンダースコアではなく)。
  • 過度な省略は避ける。例えばbuttonに対して.btnは分かりやすいけど、.btはなんの意味も無い。
  • 意味のある名前にすること。見た目ではなく、機能や目的を意味する名前をつける。
  • スタイルではなく、挙動を制御するためのクラスには.js-*をつける。そしてこのクラスはスタイル目的には使用しないこと。
/* 悪い例 */
.bt { ... }
.red { ... }
.header { ... }

/* 良い例 */
.btn { ... }
.important { ... }
.global-header { ... }

これによるメリットは、

  • クラス名を見れば何に使われているか理解することが容易になる
  • クラス名がユニークになることにより、想定外の不具合にならない
  • 自動化された機能テストで、特定の要素を探す時に役立つ

BEMの活用

エレメント(Element)

エレメントはアンダーバー2個で統一
※クラス名を区切る場合は、ハイフンで

階層が深くなった場合、クラス名が長くなってしまうので、ブロック(親)の名称を継承する

・2階層の場合の例
<div class=”search-form”>
  <div class=”search-form__inner”>
  </div>
</div>

・2階層以上の場合の例
<div class=”search-form”>
  <div class=”search-form__inner”>
    <div class=”search-form__keyword”>
    </div>
  </div>
</div>

モディファイア(Modifier)

モディファイアはハイフン2個で統一

<button class="btn  btn--medium" type="submit">
  カートに追加する
</button>

クラス名が長くなってしまう場合は、下記で対応
※エレメントの最後の名称を継承

× <section class="plans-table__type  plans-table__type--col-3">
○ <section class="plans-table__type  type--col-3">

複数クラスを適応する場合は、半角スペース2個で記述

複数スタイルを書く場合は、可視性を考慮して半角スペース2個で見やすくする

×半角1個の場合

<button class="btn btn--medium" type="submit">
  カートに追加する
</button>


○半角2個の場合

<button class="btn  btn--medium" type="submit">
  カートに追加する
</button>

これによるメリットは、

  • コンポーネント化に対応できる
  • cssの同じプロパティーを統一でき、重複する設定を省略できる
  • ソースの可視性の向上

コンポーネント化によるcss管理

PIXTAでは、cssをコンポーネント単位で分けて運用しています。

今までは、ページごとにcssを用意して運用していたのですが、
1つのcssが1,000行以上になってしまっていて
可視性や、メンテナンス性が悪かったのです......

PIXTAでは、マイクロサービス化をしているので、
ここでは、検索のマイクロサービスのファイル構成を参考に説明します。

cssフォルダ構成

components
| _layouts.scss
| _heading.scss
| _sidebar.scss
   details
   |_layouts.scss
pages
| _category-index.scss

cssファイルは、一部しか記載していませんが。。(本当はもっと沢山あります)
ここでは、フォルダ構成を簡単に説明します。

まず、componentsフォルダには、ページで共通で使用するレイアウトが
ブロックごとにcssをコンポーネント化しています。

pagesフォルダは、固有のページ(そのページでしか使用しない設定を入れています)

コンポーネント化するメリットは、ブロック(レイアウトごとの部品)で分けているので、
その箇所の設定を管理しやすく、また、上記で説明した、BEM化により、

コンポーネント固有のクラス名になっているので、修正した箇所以外には影響しないです。。
※もちろん、クラス命名規則を作成しておくのが前提です!

つまり、、、ここ直すのが怖い、このクラス他に影響してないかな・・??解消できるのです。

また、デザインのレイアウトの一部が変わった場合、ファイル単位で削除して、
新レイアウトに合った、cssを新しく作成するだけなので、
ファイル整理もできます!

最後に

PIXTAでは、頻繁にデザインレイアウトの変更をしています。
また、多言語対応をしているので....

ここでポイントとなってくるのは、

スタイルを再利用しない

です。

本当に共通の部品は、共通化のスタイルを設定してますが、

パーツ単位のclass名を再利用するとHTMLの肥大化につながり

例
<div class="box1  grid  under  red  left"></div>

こうなるとスタイルを維持することが難しくなり、運用パフォーマンスが低下します。

PIXTAでは、似たようなレイアウトでも共通化できない場合(少しでもレイアウトが変わる場合)は
スタイルを複製もしくは、新しく作成するアプローチを採用しています。

これにより、修正しても他に影響がでず、メンテナンス性に優れているからです。

チーム開発をしているデザイナーのみなさまも、
自分のサイトに合った、メンテナブルcssを設計してみてはいかがでしょうか!