猶予8時間!脆弱性だらけのサービスを堅牢化する実践型研修

Speeeエンジニアの西岡(@nisshieeorg)です。

去る7/5(水)、Speeeでは社内の全エンジニア向けに、セキュリティ研修を実施しました。本研修では、株式会社リクルートテクノロジーズの西村宗晃様に講師としてお越しいただきました。この場を借りてお礼申し上げます。

f:id:nisshiee:20170718161021j:plain

研修の目的

Webを主戦場にして戦う私達エンジニアは、自分たちのサービスそのものや、ユーザー、顧客を守るために、より安全なWebアプリケーションを構築する知識・技術を求められます。もちろんSpeeeでも、このような知識を身につけることは必須とした上で、日々の開発ではソースコードレビューや各種脆弱性診断ツールを用いて、私達が構築するアプリケーションの安全性を高めるよう努めています。しかし一方これまでSpeeeでは、「セキュリティに関する体系的な知識の学習法が会社から提示されていないため、個人の学習に依存している」「小さなチームがそれぞれの事業に分散しているため、セキュリティに関する知識レベルにバラツキがある(可能性がある)」という問題がありました。

そこで今回、社内の全エンジニアに参加してもらい、研修の場を設けました。改めてセキュリティに関する知識を身につけてもらいつつ、継続的な学習の重要性を再認識してもらうためです。

f:id:nisshiee:20170718162836j:plain

研修の内容

研修の内容ですが、今回はより実践的に、「予め用意された脆弱性のあるWebアプリケーションを堅牢化する」という課題を、ワークショップ形式で行いました。

9:00〜9:15    集合・アイスブレーク
9:15〜9:45    演習の説明
9:45~       演習(堅牢化)
          ランチ
  ~17:30   演習(堅牢化)
17:30~17:45 休憩
17:45~19:40 攻撃と原因の調査
19:40〜20:00 休憩・4階ラウンジ移動
20:00~21:00 懇親会(寿司・お酒)

Speee側社員は2人1組で堅牢化に取り組みました。ただし・・・

  • 僕はなぜか1人チーム(1人で2倍探さないといけないから大変;;)
  • 技術顧問の藤さんも参加(社内のリードエンジニアとペアだった。強すぎだろ)
  • 開発部顧問の井原さんとランサーズCTOの横井さんペアも参戦(!!?)

f:id:nisshiee:20170718164504j:plain

堅牢化が終わったら、アプリケーションを動作させて、実際に脆弱性を突くアクセスをしてもらいます。

結果

今回、事前に用意された脆弱性を突く攻撃が9種用意されており、9個中何個の攻撃を防ぐことができたかを競いました。

Aチーム: 6/9個防がれました 3個刺さりました!
Bチーム: 6/9個防がれました 3個刺さりました!
Cチーム: 7/9個防がれました。2個ささりました!パスワード盗めました!
Dチーム: 7/9個防がれました!2個刺さりました!パスワード獲ったどー!
Eチーム: 7/9個防がれました 2個刺さりました!
Fチーム: 7/9個防がれました!2つ刺さりました!
Gチーム: 7/9個防がれました 2個刺さりました!
Hチーム: 7/9個防がれました。2個ささりました。パスワードが盗める脆弱性が2件です。
Iチーム: 8/9個防がれました。1個しか刺さらないぃぃぃぃー、キィー悔しぃぃぃぃぃぃーー!!
Jチーム: 7/9個防がれていました!
Kチーム: 6/9個防がれました!ぬぅー、管理者アカウント取ったけど、その先に対策が入ってた!!
Lチーム: 7/9個防がれました 2個刺さりました!Lのみある攻撃を防ぐことができました!
Mチーム: 7/9個防がれました。2個ささりました。が、そのうち1個は若干防御されていて、完全にささったとは言えない状況でした。
Nチーム: 7/9個防がれました 2個刺さりました!

※ Hチーム=藤さんチーム、Iチーム=井原さん・横井さんチーム、Mチーム=西岡ソロプレイ

僕は2個刺さってしまった。くやしーーーーー

ちょっとだけ言い訳すると・・
僕は縛りプレイとして、「脆弱性であるという確信があるものしか手を加えない」というルールを自分に課していました。明らかにこのあたりの書き方おかしいんだけどなーという箇所はあったけれど、そこを突くリクエストの投げ方を思いつかなかったのでルールに則ってそのままにしておいたら、見事に突かれました・・・
(藤さんも同じ縛りプレイしていたそうですw)

本当は、突かれた脆弱性の中身の話もしたいのですが、ネタバレしないように言われているのでここでは控えます。すみません!

研修を終えて

僕もWebアプリケーションのセキュリティに関しては一定以上知っているつもりでしたが、改めていろいろ学ばせていただきました。

  • まずは基礎知識が大事(当たり前だけど)
    • 特に最近はセキュリティに関しても「フレームワークがよしなにやっている」ことが多いので意識する機会は少ないが、知識がないと知らぬ間に穴を空けてしまいかねない
    • Railsで開発するのであれば、Railsセキュリティガイドは必読
  • 継続的な知識獲得も大事
    • WEBアプリケーションそのものの作り方も日々変化しているので、それに伴って新しい防御が必要な場合もある
    • 実はずっと「SPAでCSRF対策はどうやってやるんだろう?」と悩んでいたのですが教えてもらいました(後述)
  • 組織の仕組みづくりも大事
    • 基礎的な防御はエンジニア一人ひとりが知っておく必要がある一方、開発の片手間では拾いきれない高度な部分が存在するのも事実
    • 意識だけでなく、組織として自社サービスのセキュリティを高めていく仕組みが重要

おまけ:SPAでのCSRF対策

伝統的には、CSRF対策は、Webサイトにアクセスしているユーザしか知り得ない情報(セッションIDなど)をトークンとしてFormに埋め込むことによって行われていました。例えばRailsも、デフォルト設定では、自動でFormへのトークン埋め込みとリクエストされたときのトークン照合を行うようになっています。

SPAの場合も、HTMLヘッダ等にトークンを出力しておいて、JSによるAjaxリクエスト生成時にそのトークンを埋め込むという実装をすれば、同じ対策を実装することはできます。しかし、せっかくSPAを実装するのであれば、HTMLから動的要素を全て排除し、その気になればCDNから配布することもできるという恩恵も享受したいものです。このためには、CORSの仕組みを利用するそうです。

仕様では、以下のようになっています。

  • Cross-Originリクエストには、JSから任意のHTTPヘッダをセットすることはできない
  • ただし、リクエスト先にプリフライトリクエストを送り、カスタムHTTPヘッダを許可された場合はセットできる

したがって、以下のような実装をすることでCSRF対策となるそうです。

  • JS側でAjaxリクエストにカスタムヘッダをセットする(ヘッダ名や内容を秘匿する必要はない)
  • サーバサイドでヘッダがセットされているか確認する

実装内容自体は非常にシンプルなんですね!

ただし、以下の点には注意。

  • プリフライトリクエストに無条件で許可返答をするようになっていないこと
    • 逆にサービスがCross-Originで提供されている場合は、プリフライトリクエストへの返答が正しく実装されている必要があります
  • 「任意のHTTPヘッダをセットできない」という仕様どおりにブラウザが実装されていることを期待した方法です
    • 古いブラウザなどでは回避されてしまう可能性があるので、この対策手法で十分と取るかはビジネス判断になると思います