こんにちは!R&D事業部(以下R&D)のソンヒです。
主にWEBアプリケーションのバックエンド側を開発しています。

今日のテーマは「安全なWEBアプリケーション」を作るために対策すべきことです。

001

そもそも、安全なWEBアプリケーションとは?

単純に言うと、「意図したとおりに動くアプリケーション」ですね。

WEBアプリケーションは基本的に入力、処理、出力の流れで実行されます。
ユーザーから要請をもらうとWEBアプリケーションにはその要請データが入力されます。記録されたデータを元に機能に必要なロジックを処理して、最後に処理の結果をブラウザーに出力。
出力は処理の結果を、処理は入力を元にしていますので、どんなに処理や出力が正しいとしても、入力時に正しい検証を行わなかった値を使うと意図した通りには動きません。

つまり、「正しいユーザーに正しい値で実行される」ことが安全なWEBアプリケーションにおいては特に大事なことです。

正しいユーザー

機能を実行するユーザーが正しいか確認するためには認証と認可が必要です。
認証と認可、似ているように聞こえますが、意味はかなり違います。

認証:個人を特定すること→(例)「Aさんですね。」
認証とは対象の正当性や真正性を確かめることを指します。
WEBアプリケーションでは主に特定の個人を確かめる意味で使っています。IDとパスワードを使ったログインによる認証が一般的ですね。
特定の個人に行動を許可する認可を設定するためには、個人を特定する認証が必ず必要です。

認可:行動を許可すること→(例)「Aさんなら、閲覧していいです。」
認可は特定の行動を許可する権限を設定し、個人の権限情報から行動を許可するかどうか判断することです。

現在開発中のとあるプロジェクトでは、フレームワークで提供している「ACL」という認可機能を使っています。
ACLはアクセス制御リスト(Access Control List)の略語で、「権限(ARO)」がアクセスできる「行動(ACO)」をリスト化したものです。

例えば、数多くのイベント情報が掲載されている情報ポータルサイトがあるとしましょう。このポータルサイトの管理画面にはサイト運営者と、各イベントの掲載を行う複数の興行主がログインできます。

興行主は自分のイベントを管理する機能にはアクセスできますが、サイト内のすべての興行主を管理できるのはサイト運営者だけです。

このような権限関係をR&Dでは以下のような表で管理しています。

002

権限情報はデータベースに保存されていて、認証されたユーザーがある機能に対してリクエストを送ったとき、権限を確認しアクセスできるかどうか判断します。

アクセスできる権限を持っていない場合は、403のHTTPレスポンスを返してアクセスを拒否します。

正しい値

認証と認可が正しくても、安心はできません。
上述した例で興行主は以下のURLのページにアクセスすると自分のイベント情報を編集できます。

https://イベントポータルサイト/管理画面/events/15/edit

このURL内の15という数値は該当イベントが持つIDで、この興行主独自のものです。自分のイベントですから、基本的には他人には編集されたくないのは当然のこと。

ですが、このURLの数字を15ではなく、他の興行主のイベントIDである20に変更してアクセスできるとしたら…?IDに制限が何もかかっていなければ、自分のものではないイベントを勝手に編集できてしまうという問題が発生してしまいます。

このような問題が起きないよう、R&Dではアプリケーションの本格的な処理を行う前に、【正しいユーザー】のリクエストに含まれるデータが本当に正しいかどうか検査を行っています。

データに機能を実行する妥当性がない場合、アプリケーションの要件によっていろんな処理を行っています。
機能を実行するにはデータの不備が致命的すぎて機能を中断したい場合は「Exception」としてHTTP Status Codeを返してエラーページを表示します。
同じく機能の実行を中断したいけどユーザビリティやSEOなどを考えてエラーページ以外のページを出したいときには、他の有効なページへのリダイレクトや準備しておいた画面をレンダリングすることで対応しています。

そして、データの不備はあっても機能を進めるには特に問題ない場合があります。
そのときは、適切なデータに切り替えるかデータそのものを除外して次の処理に進めます。

この中でもよく使われるExceptionの場合は、リクエストされたデータの種類と検査の内容によってR&Dでは以下のStatus Codeを送るようにしています。

003

バリデーション

権限もアクセス時のリクエストも正しい場合、本格的にアプリケーションの処理を行います。
アプリケーションによっては、入力フォームを使ってユーザーから直接入力を求めるページもありますが、その入力される値もまた、アプリケーションで求めている値とは限らないため検査が必要です。
ユーザーからフォームで入力された値をそのまま使っていいかどうか検査する処理のことを「バリデーション」と呼びます。

例えば、先述の「興行主がイベントの情報を編集する画面」にイベントの対象年齢を設定する入力フォームがあるとしましょう。このフォームには、対象の最小年齢と最大年齢を入力するインプットフォームが二つ必要です。

常識的に考えて最大年齢が最小年齢より高いのは当たり前ですが、この場合入力フォームで何も制限をかけないとユーザーは自由に入力できてしまい、「最小30歳から最大20歳まで」のようにありえない入力をすることが可能になってしまいます。
これではアプリケーションの処理として望ましくありません。

このように正しくない値を入力されたときに次のステップに進まないように、入力フォームでバリデーションを行います。

セキュリティ脆弱性への対策

ユーザーからの入力の形式が正しくても、値によっては致命的なセキュリティ問題が起こる可能性があります。ユーザーが悪意を持ってスクリプトや予約語などを値に埋め込み、セキュリティの脆弱性を突いた攻撃をしてきた場合です。

004

この代表的な例にはSQLインジェクション、クロスサイトスクリプティング(XSS)、クロスサイトリクエストフォージェリ(CSRF)などがあります。
特にここに挙げた三つは、安全なWEBアプリケーションを作るためには対策が必須とされているものです。この三つの脆弱性に対してR&Dが行っている対策を簡潔に紹介いたします。

SQLインジェクション
SQLインジェクションとは、ユーザーが入力する値にSQL文の予約語を入れると、開発者の意図していないSQLが実行されてしまう脆弱性のことです。

例えば、何の対策もしてないWEBページの入力フォームに悪意を持ったユーザーが全てのユーザー情報を削除するSQL文を入れ込んだ場合、膨大な量のデータがなくなる危険性があります。

そのような問題を防ぐ方法の一つが静的プレースホルダという方法です。
静的プレースホルダは入力されたデータ以外の部分をあらかじめ解析しておいて、入力データを後から代入する仕組みになっています。
後から代入されたデータにSQL文が入っていたとしても、すでに解析が終わっている状態ですのでSQLインジェクションが起こりません。

クロスサイトスクリプティング(XSS)
Webサイトでユーザーが値を決定できる部分に悪意を持ったスクリプトを埋め込んで攻撃する脆弱性のことです。

例えば、ユーザーが悪意のあるスクリプトが埋め込まれている掲示板にアクセスした場合、掲示板の中に含まれているスクリプトが動作して、ユーザーのログイン情報を悪意を持ったサイトに送ってしまう場合もあります。

ユーザーから入力される値にはスクリプトが埋め込まれている可能性がありますので、「<>“&」のように特殊な意味合いを持つ文字はそのまま出力したら危険です。
掲示板などのように不特定多数が利用するような画面で入力された値を出力するときは、スクリプトが実行されないようにエスケープ処理を通じて無力化することが大事です。

クロスサイトリクエストフォージェリ(CSRF)
CSRFはユーザーに意図していない操作を強制的に実行させてしまう攻撃です。

例えば、ユーザーがECサイトにログインしている状態で、悪意あるサイト(いわゆる罠サイト)にアクセスしたとします。このとき罠サイトではセッションデータを含んだリクエストをECサイトに送るスクリプトが埋め込まれていて、アクセスしただけで被害者の情報で買い物をするなどの大事な処理が勝手に行われる危険性があります。

CSRFの対策方法ではトークン(一回限り使用できる使い捨てパスワードを表示する機械)を使う方法があります。
フォームのリクエスト時にワンタイムトークンを発行し、受信する側でトークンが一致するか確認することで、罠サイトで送ったリクエストではないことを確認できます。

実は、今回述べた三つの脆弱性も含めたセキュリティ対策については、開発用フレームワークの機能の一部として提供していることが多いのです。
実際にR&Dの開発で採用しているフレームワークでも、上記で説明した三つの脆弱性に対して対応ができています。
ただし、一部手動で対策を行うべきところもあります。
フレームワークだけ信じてセキュリティへの対策を考えないのは重大なセキュリティ問題に繋がるかもしれませんので、常に注意が必要です。

最後に

005

正しいユーザー、正しい値で実行されるWebアプリケーションを作るためにR&Dで行っていることを紹介いたしました。
正しいユーザーとは誰か、正しい値はどんな値かは開発者が定義するしかありません。小さな間違いも大きな問題に繋がりかねないので、常に安全を意識して開発することを心がけています。

また、脆弱性への対策もフレームワークを使って開発すると大抵カバーしてくれますが、どんな脆弱性があってどんな処理をしてくれるのかは分かった上で開発しなければ、安全なWEBアプリケーションとは言えないと思います。
開発者一人一人が意識と知識をもって、あらゆる状況を想定し不安なところを一つずつ潰していくことで安全は生まれるのです。

これからも安全なWebアプリケーションを作るためにセキュリティの意識をみんなで向上させていきたいと思います!