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

てくすた

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

業務時間内での社内読書会とその工夫点~SQLアンチパターン編~

勉強会

近頃スライムに転生して魔王になりたい気分の id:necojackarc です。

本記事では、ピクスタで行われている SQL アンチパターン読書会について紹介します。

社内勉強会や社内読書会を開催したいと考えている方の参考になれば幸いです。

きっかけ

目的は会社全体の技術力向上です。

全員に知っておいてほしい内容だと感じた SQL アンチパターンを、どのようにすれば効率良く全員が学べるか考え、業務時間内での定期的に読書会を開催することにしました。

始め方

業務時間内での定期的な時間の確保となるので、調整が必要不可欠です。
また、全員分の本の確保も必要になります。

どちらについても、SQL アンチパターン読書会のメリットを共有し、合意を得ました。

というわけで、社内にはなかなかの冊数の SQL アンチパターンがあります。

f:id:necojackarc:20161207182042j:plain

進め方

毎週決まった時間を1時間(準備時間含む)確保して行っています。

具体的な進め方は以下のとおりです。

  1. 各自で課題の章を読み進める(20 - 30分)
  2. 4人程度のチームに別れ、読んだ内容についての議論を行う(20分程度)
  3. 各チームでの議論内容を共有する(ピクスタでは esa を利用しています

f:id:necojackarc:20161207183332j:plain

工夫点

SQL アンチパターン読書会を始めるにあたって、以下の工夫を行いました。

  • 監訳者による初回講演(動機づけ)
  • 参加のための事前準備を一切なくす
  • 英語チームを作る
  • 少人数のチームに別れて議論を行う

監訳者による初回講演(動機づけ)

「なぜこの本を読むと良いのか」や「どんな本でなにが面白いのか」というのがわかっていると、モチベーションが高まります。

そこで初回は監訳者である @t_wada さんに1時間の講演をお願いしました。

f:id:necojackarc:20161207183000j:plain

これは効果テキメンで、講演後は「読書会めっちゃ楽しみ」や「早く読みたい」という声があがりました。

また、たくさん買った本にもサインをしていただきました。

f:id:necojackarc:20161207183044j:plain

f:id:necojackarc:20161207183127j:plain

参加のための事前準備を一切なくす

なるべく多くのメンバーが意欲的に継続して参加し続けないと意味がないため、参加のためのハードルを可能な限り引き下げました。

本も経費で購入していますし、当日は「会議室に行くだけ」の状態にしています。

これは本自体の特徴になるのですが、SQL アンチパターンは各章を独立して読み進めることが可能です。
そのため、途中参加の場合ですら、事前準備は必要ありません。

英語チームを作る

ピクスタには海外出身のエンジニアやベトナムラボがあり、英語をメイン言語として働くメンバーもいます。

本当の意味で全メンバーが参加できるよう、原著を読み進める英語チームも作りました。

少人数のチームに別れて議論を行う

知識の定着のためには経験や繰り返しが大事ということで、全員がアクティブに議論に参加できるよう、少人数チーム(1チーム4, 5人)に分かれて議論を行っています。

課題点

チーム分け

チームを固定にすべきかどうかや、不参加者が出た場合のチーム分けに少し苦労しています。

これについては、主催者が当日の状況を見て、さくっと分けるようにしています。

遠隔地との連携

具体的にはベトナムラボとの連携ですが、当初は Hangout を繋いで同じ時間で行っていました。
しかし、リモート会議自体の難しさもあり、あまり連携するメリットを得られませんでした。

遠隔地は独立して運用していく方式を取るべきかなと感じています。
また、そのためには現地で開催に協力してくれるメンバーが必須です。

まとめ

ピクスタでは社内読書会を効果的なものにするため、

  • 動機づけ
  • 参加しやすさ
  • アクティブな参加形式

を意識した運用を行っています。

新規事業 fotowa を一緒に育ててくれるメンバーの募集を開始しました!

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:コメントアウトの対義語