いろいろ備忘録

雑記です。

安全なウェブサイトの作り方 脆弱性対策 要約

SQLインジェクション

根本的な対策

SQL 文の組み立ては全てプレースホルダで実装する

SQL 文を動的プレースホルダで生成する場合は、DBエンジンのAPIを用いる。

APIを使うと以下の注意をしなくて良くなる

文字列型の注意

値をエスケープしないと、値を囲むシングルクォートがエスケープされてしまう危険性

数値型の注意

値を数値型にキャストするなど、値が数値型であることを確実にする

保険的な対策

エラーメッセージをそのままブラウザに表示しない。

データベースの種類やエラーの原因、実行エラーを起こした SQL文等が知られる可能性

データベースアカウントに適切な権限を与える

OSコマンドインジェクション

根本的な対策

シェルを起動できる言語機能の利用を避ける。

PHPのexecなど、そういう関数を使わないようにする。

どうしても利用したい場合は、その引数を構成する全ての変数に対して チェックを行い、あらかじめ許可した処理のみを実行する。 チェックにはホワイトリストを利用する。 ブラックリストだと、記号に漏れが生じる場合があるので。

パス名パラメータの未チェック/ディレクトリトラバーサル

根本的な対策

外部からのパラメータでウェブサーバ内のファイル名を直接指定する実装を避ける。

例えばhiddenで指定する方式だと、そのパラメータが改ざんされることで、 任意のファイルをウェブページとして出力される可能性。

ファイルを開く際は、固定のディレクトリを指定し、かつファイル名にディレクトリ名が含まれないようにする。

例えば、open(fn)のfnに絶対パス名が渡されてしまう事で任意のディレクトリのファイルが開かれてしまう これを防ぐには、open(publicDirName + basename(fn))とする。 publicDirNameは固定のディレクトリ。 basenameは、パス名からファイル名のみを取り出す関数。

保険的な対策

ウェブサーバ内のファイルへのアクセス権限の設定を正しく管理する

ファイル名のチェックを行う。

「/」「../」「..\」など、OSのパス名解釈によってディレクトリを指定できる文字列をチェック。 しかし、それらをURLエンコードした「%2F」や、 二重エンコード(パーセント記号を%25にエンコード)した「%252F」などが有効になる場合がある。 よってデコードを終えてからチェックする必要がある。

セッション管理の不備

根本的な対策

セッション ID を推測が困難なものにする

生成アルゴリズムに暗号論的擬似乱数生成器を用いるなど、予測困難なものにする。

セッション ID を URL パラメータに格納しない

セッション ID は、Cookie に格納するか、POSTメソッドの hidden パラメータに格納して受け渡しする

HTTPS 通信で利用する Cookie には secure 属性を加える

ログイン成功後に、新しくセッションを開始する

セッションIDの固定化攻撃の対策。 セッションIDの固定化攻撃とは、 1.攻撃者がログイン前のセッションIDを渡す 2.ユーザがログインする 3.攻撃者がユーザとしてログインする

ログイン成功後に、既存のセッション ID とは別に秘密情報を発行し、ページの遷移ごとにその値を確認する

セッションIDが2つに増えるイメージ。 秘密情報(トークン)をログイン成功時に発行し、それを毎回確認する。 セッションIDはログイン前から成功した後もずっとある。秘密情報はログイン成功後から。

秘密情報を発行しなくて良い方式
  • ログイン時に新しくセッションを開始する方式(すぐ上に書いた)
  • ログイン前にはセッションIDを発行しない方式

保険的な対策

セッション IDを固定値にしない

セッションIDが利用者ごとに固有の値だと、 古いセッションIDを入手されたときセッションハイジャックされる。 セッションIDは利用者のログインごとに新しく発行する。

セッション ID を Cookie にセットする場合、有効期限の設定に注意する

expiresを短い日時に設定し、必要以上の期間、クッキーがブラウザに保存されないようにする。 expiresを省略するとブラウザの終了時に破棄となるが、 ブラウザを立ち上げっぱなしのとき破棄されない。それはそれで困る。

XSS

HTML テキストの入力を許可しない場合

根本的な対策

ウェブページに出力する全ての要素に対して、エスケープ処理を施す。

実体参照(例:<)、文字参照(例:&#0000;)を用いる。 HTML タグを出力する場合は、その属性値を必ずダブルクォートでくくる。 入力値だけエスケープする方針だと必ず対策漏れが出るため、出力の全てをエスケープする。 忘れがちだが、JSのdocument.writeやinnerHTMLで書き換える際もエスケープが必要。

URL を出力するときは、「http://」や 「https://」で始まる URL のみを許可する

入力されたURLをそのままaタグで出力すると、 javascript:と書かれた時任意のJSが実行されてしまう。 http://かhttps://で始まる文字列のみ許可するホワイトリストで実装すべき。

要素の内容を動的に生成しない

危険なスクリプトかどうかを確実に判断するのは難しいので、 入力やDBを元にJSを生成するのは良くない。

スタイルシートを任意のサイトから取り込めるようにしない

スタイルシートには、expression() 等を利用してスクリプトを記述することができる。 上と同様に、スクリプトが危険かどうかの判断は難しいので、 取り込めるような仕様は良くない。

保険的な対策

入力値の内容チェックを行う

ウェブアプリケーションの仕様が、幅広い文字種を許すときは対策にならない。 対策になるのは仕様が英数字のみ許す場合など。

HTML テキストの入力を許可する場合

根本的な対策

入力された HTML テキストから構文解析木を作成し、スクリプトを含まない必要な要素のみを抽出する

入力された HTML テキストに対して構文解析を行い、 「ホワイトリスト方式」で許可する要素のみを抽出する。 ただこの場合、複雑なコーディングが要求される上に、処理に負荷がかかる。

保険的な対策

入力された HTML テキストから、スクリプトに該当する文字列を排除する

「\<script>」や「javascript:」のサニタイズは、 「\<xscript>」「xjavascript:」のように、その文字列に適当な文字を付加する。 他の排除方法として文字列の削除があるが、 削除した結果、却って危険な文字列を形成してしまう可能性がある。

ウェブアプリケーションに共通の対策

根本的な対策

HTTP レスポンスヘッダの Content-Type フィールドに文字コード(charset)を指定する

一部のブラウザは、HTMLテキストの冒頭部分等に特定の文字列が含まれていると、 必ず特定の文字コードとして処理してしまう。 具体的には、+ADw-script+AD4-という文字列が一部のブラウザではUTF7として解釈され、 その結果<script>のように扱われてしまう。 エスケープ処理をしているときはShift_JISUTF-8などのはずなので、 上記の文字列が適切にエスケープされない。

保険的な対策

発行する Cookie に HttpOnly 属性を加える

HttpOnlyはJSからクッキーにアクセスすることを防ぐ。

ブラウザのXSS対策機能を有効にするレスポンスヘッダを返す。

  • X-XSS-Protection

HTTP レスポンスヘッダに「X-XSS-Protection: 1; mode=block」

  • Content Security Policy

HTTP レスポンスヘッダに「Content-Security-Policy: reflected-xss block」

CSRF

根本的な対策

秘密情報(トークン)を埋め込む

入力→確認→登録というページ遷移があるとする。 利用者の入力内容を確認画面として出力する際、 合わせて秘密情報をhiddenパラメータに出力する。 その後、登録処理を行う前に、秘密情報とhiddenパラメータの比較を行う。 秘密情報にはセッションIDをそのまま使うか、もしくは別の予測困難な乱数を用いる。 確認画面へのリクエストにはPOSTを用いる。GETだとRefererでバレるので

処理の直前でもう一度パスワードの入力を求める

確認画面にPOSTメソッドでのパスワードの再入力をさせる。 上の方法だとセッションではなくBasic認証を用いている場合は 新たに秘密情報を作らなければならない。実装上の手間がかかる。 この方式だと、パスワードをそのまま用いれば良いので採用しやすい。 Basic認証は自動的に認証情報が送られるので、当然そのままだとCSRF対策にならない。 あくまでPOSTで入力させることに注意。

Referer が正しいリンク元かを確認し、正しい場合のみ処理を実行する

Referer が空の場合も、処理を実行してはいけない。 これはRefererを空にしてページを遷移する方法が存在するから。 しかし、ブラウザやパーソナルファイアウォール等の設定で Referer を送信しないようにしている利用者もいるので注意。 また、攻撃者がそのウェブサイト上に罠を設置出来る場合は対策にならない。

保険的な対策

重要な操作を行った際に、その旨を登録済みのメールアドレスに自動送信する

メール本文にはプライバシーに関わる重要な情報を入れないことが重要。

HTTPヘッダインジェクション

根本的な対策

ヘッダの出力を直接行わず、ヘッダ出力用 APIを使用する

ヘッダ出力用APIウェブアプリケーションの実行環境や言語に用意されている。

ヘッダ出力用APIを利用できない場合は改行を許可しないよう処理する。

保険的な対策

外部からの入力の全てについて、改行コードを削除する

メールヘッダインジェクション

根本的な対策

メールヘッダを固定値にして、外部からの入力はすべてメール本文に出力する

「To」、「Cc」、「Bcc」、「Subject」を外部からの入力で生成すると 改行コードによってメールヘッダの挿入や メール本文の改変、任意の宛先へのメール送信に悪用される可能性がある。

メールヘッダを固定値にできない場合、メール送信用 API を使用する

メール送信用APIウェブアプリケーションの実行環境や言語に用意されている。 改行コードの対策には、

  • 改行コードの後に空白か水平タブを入れることで継続行として処理する
  • 改行コード以降の文字を削除する
  • 改行が含まれていたら処理を中止する

がある。 継続行とは、一行が長くなった時に見た目でのみ改行するもの。 システム的には改行されない。 Javaではバックスラッシュ、VBではアンダーバーが使われる。

保険的な対策

外部からの入力の全てについて、改行コードを削除する

クリックジャッキング

一例としては、 罠ページにサイトAのコンテンツを表示し、それをクリックさせることで サイトAにログイン済のユーザに意図しない処理を実行させる。

根本的な対策

HTTP レスポンスヘッダに、X-Frame-Options ヘッダフィールドを出力する

ユーザが罠サイト内のframeからサイトAにリクエストを送信するとき、 サイトAがレスポンスヘッダにX-Frame-Options: DENYのように出力すれば 対応したブラウザにおいてその読み込みは中止される。

  • DENY 全ドメイン禁止
  • SAMEORIGIN 同一オリジンのみ
  • ALLOW-FROM 指定したオリジンのみ

ALLOW-FROMは提携サイトのフレーム内に自コンテンツを表示させたいときなど。

処理を実行する直前のページで再度パスワードの入力を求める

保険的な対策

重要な処理は、一連の操作をマウスのみで実行できないようにする

キーボード操作などを挟む。

バッファオーバーフロー

根本的な対策

直接メモリにアクセスできない言語で記述する

PHP,Perl,Javaなどはできない。 しかしJavaは整数オーバフローは起こる。

直接メモリにアクセスできる言語で記述する部分を最小限にする

可能な限りC, C++, アセンブラで記述する部分を減らす。

脆弱性が修正されたバージョンのライブラリを使用する

アクセス制御や認可制御の欠落

根本的な対策

認証機能に加えて認可制御の処理を実装する

複数の利用者が存在する場合は、どの利用者に どの操作を許可するかを制御する認可(Authorization)制御の実装が必要