アーカイブ

第1章 4.より良いWebアプリケーション設計のヒント

公開日:2007年6月28日

独立行政法人情報処理推進機構
セキュリティセンター

本ページの情報は2007年6月時点のものです。
記載の資料は資料公開当時のもので、現在は公開されていないものも含みます。

ここで述べるのは、脆弱性が生まれにくいWebアプリケーションを構築するために設計段階、あるいはそれ以前の段階で考慮しておくとよい事項の例である。

開発基盤選定における考慮事項の例

(1)開発環境の選択

1) プログラマが脆弱性をつくり易い環境を避ける

今日のWEBアプリケーション開発環境は、プログラミング言語の処理系に加えて、開発フレームワークやコンテンツ管理システム(CMS)、さらに外部のテンプレート言語までを加えた総合的な環境となってきている。

短時日で素早くサイトを立ち上げることを目的として、「軽量言語」と呼ばれる各種スクリプト言語が標準で備えているWEBアプリケーションを手軽に開発するための機能やライブラリをそのまま利用することは悪くない。しかし、その手軽さ故に、セキュリティの観点からは多くの脆弱性を生んできた経緯がある。

例えば、下記の事例が挙げられる。

  • PHPの4.1以前のバージョンの環境は、「register_globals」という設定を有効として開発することを許容する危険な環境となっていたため、避けることが考慮された。
  • 経緯上、PerlにはサンプルCGIプログラムが添えられているが、それらについて脆弱性をチェックせずに利用することは避ける必要がある。
  • 各種軽量言語のライブラリ関数の中には、文字列中のナルバイト(「%00」)を見過ごしてしまう関数(いわゆる「バイナリセーフ」でない関数)が残っているが、このような関数を使ってしまうと、悪意あるデータが混入される脆弱性を生む懸念がある。

開発環境の仕様について十分な知識とスキルを有する開発要員を確保できない場合、そのような環境を避けるという選択肢もある。

2) 大規模システム開発においてフレームワークの機能を使う

大規模なシステムにおいては下記のような考慮事項があるが、フレームワークにはこれらを支援する機能がある。

  • 開発するシステムの骨組み(アプリケーションアーキテクチャ)を開発環境の中に確保して、プログラマが個々のビジネスロジックの記述に集中できるようにする
  • 共通的に利用する機能をメンテナンスし易くする
  • コーディング規約を制定してプログラマに遵守させることによって、ソースコードの質がなるべく均一になるようにする
  • コンパイル時の型チェックを厳密に行わせることによって、バグを早期に発見する

Webアプリケーションのセキュリティにおいてセッション管理の設計が肝要であり、大規模システムにおけるセッション管理は複雑なものとなる。複雑なセッション管理を適切に設計するためにもフレームワークが提供する機能を利用することは有用である。

(2)データベースの選択

データベースをSQLでアクセスする構造を採用するのであれば、WebアプリケーションはSQL注入攻撃の危険に曝される。データベース製品を選択する際にはその製品およびDBアクセスAPIがどのような特徴を持っているかあらかじめ把握しておくとよい。

1) そのDBで「言語に統合されたクエリ」が使えるか?

  • DBMSと親和性がある「言語に統合されたクエリ」をサポートしているプログラミング言語があるかを確認する。
  • 上記のプログラミング言語を学習する際の難易度を把握する。

2) そのDBでプリペアドステートメントが使えるか?

  • データペースAPIがプリペアドステートメントのAPIを備えており、適切にサポートしているか否かを確認する。
  • データベースAPIの中には、プリペアドステートメントと互換のインタフェースを用意していて、内部で特殊文字のエスケープ処理を行っているものがある。このエスケープ処理に漏れがある場合、SQL注入攻撃を防ぎきれないおそれがある。

3) そのDBはどのような特殊記号に反応するか?

  • 「;」でステートメントを区切るマルチプルステートメント記法が使えるか否かを確認する。
  • 「\」によるエスケープ記法が使えるか否かを確認する。
  • マルチバイト文字に含まれる0x5Cのバイトを「\」と認識するか否かを確認する。

サイト設置設計における考慮事項の例

1) サイトの用途別分離

用途の異なるコンテンツがひとつのサイトに混在しないよう、複数のサイトへの分離をはかる。

  • http:専用サイト と https:専用サイト を分ける
    同じサイト上にhttp:ページとhttps:ページが混在しないようにできるのであれば、https:専用サイトのCookieが平文でネットワークに流出する問題を防ぎやすい

  • 商品カタログサイト と ショッピングカートサイト を分ける

  • 会員向けサイト と 管理者向けサイト を分ける 等
    あらかじめサイトを分離しておくことで、アクセス制御(本人認証とアクセス認可)のロジックが複雑になるのを避けることができる。これは、アクセス制御が迂回される脆弱性を起こりにくくする効果がある。

  • http:専用サイトとhttps:専用サイトを分けるイメージ図
    図1-7: 目的ごとにサイトを分ける

アクセス制御設計における考慮事項の例

(1) ユーザ認証

ユーザ認証をどの方式で行うか決める。

1) HTTP認証(ベーシック認証、ダイジェスト認証)

  • メリット
    • ユーザ認証が済むと、HTTPリクエストに毎回ユーザ認証データが含まれるようになるので、ユーザの識別が容易である
    • セッションIDの仕組みを持たずに済ませることもできる
  • デメリット
    • ベーシック認証の場合、毎回HTTPリクエスト上をパスワードが流れる
    • ブラウザからサーバにリクエストが投入されるときには必ずユーザ認証データが伴うので、リクエスト強要(いわゆるクロスサイトリクエストフォージェリ)攻撃を受けやすい

2) ログインフォーム

  • メリット
    画面設計における自由度が高い
  • デメリット
    本人認証手続きと認証済み状態の保持のロジックをアプリケーションの側で実装する必要がある
  • ログイン処理における留意事項
    • ログイン失敗時に親切すぎるエラーメッセージを表示すると、攻撃者がパスワード破りをする際の手がかりをあたえてしまう
    • エラーメッセージの中に入力されたユーザIDを含めると、スクリプト注入(いわゆるクロスサイトスクリプティング)脆弱性が生じる
    • ログイン状態の追跡のための何らかの手段を講じることなくログイン前とログイン後で同じセッションIDを用いていると、セッション・フィクセーション攻撃を受けるおそれがある

3) OpenID等の外部のユーザ認証サービス

  • メリット
    ユーザが提供するパーソナル情報を自ら運用管理する必要がない
  • デメリット
    顧客をユーザとする場合に、ユーザの属性情報の分析などを自らのサイトで行うことができない

(2) アクセス認可

例えば、各コンテンツの冒頭にてアクセス認可を次のように実装する。

1) ログイン済み確認ロジック

  • ユーザが未ログインであればそのコンテンツを開示しない

2) ページ・アクセス認可処理

  • アクセス許可テーブルを引き、現在のユーザがこのコンテンツにアクセスする許可(パーミッション)を得ていないならこのコンテンツを開示しない

3) パラメータ・アクセス認可処理

  • 入力パラメータが何らかの情報を取り出すキーになっているとき、現在のユーザがそのキーのデータへのアクセスの許可(パーミッション)を得ていないなら該当コンテンツを開示しない
  • パラメータ・アクセス制限の方法には、ユーザの権限とパラメータでテーブルを引く方法もあるが、データベースの検索条件に「現在ログインしているユーザID」という制約を常にかけるという方法もある

セッション設計における考慮事項の例

(1) ページの連続性追跡の方式

  • 複数ページからなる会員登録フォーム等の場面を想定し、ページの連鎖を追跡するためにログイン前からセッションIDを用いるか否かを決める
  • ログイン前からセッションIDを用いるのであれば、このセッションIDの取り扱いに加えて、ログイン状態を管理するロジックをもつ必要がある

(2) ログイン維持の方式

1) HTTP認証を使用する場合

  • HTTPリクエストにユーザ認証データが含まれているため、特別なロジックはいらない
  • 第三者に傍受されないよう、ユーザ認証データがやりとりされる通信にはすべてhttps:を使用する

2) ログインフォームとセッションIDの2つを使用する場合

ユーザがログインに成功したら次を行う。

  • それまで使っていたセッションIDを無効にし、新しいセッションIDを発行する
  • Webアプリケーション動作環境が提供してくれるセッション変数メカニズムを用い、セッション変数に「ログイン済み」であることを記録する
  • 第三者に傍受されないよう、セッションIDがやりとりされる通信にはすべてhttps:を使用する

3) ログインフォーム、セッションID、およびログイン追跡パラメータの3つを使用する場合

ユーザがログインに成功したら次を行う。

  • 特定のユーザがログイン済みであることを示す意味を持ち、第三者が予測困難であって内容の解読が困難な値──すなわち暗号や乱数──からなるログイン追跡パラメータを発行する
  • ログイン追跡パラメータはCookieやhiddenパラメータの値としてブラウザに預け、サーバに返送されるようにする
  • 第三者に傍受されないよう、ログイン追跡パラメータをやり取りする通信にはすべてhttps:を使う

(3) リクエスト強要(CSRF)対策

1) ログインセッションごとに単一の照合値を使う方式

  • ユーザがログインに成功した時点で次を行う
    リクエスト強要(クロスサイトリクエストフォージェリ)対策の照合に用いる値を乱数等を用いて生成し、セッション変数等に記憶させておく
  • 重要な処理を行う直前の場面で次を行う
    第三者がお膳立てしたフォームから発せられたリクエストではないことを判別するための、この照合値をフォームに埋め込んでブラウザに送る
  • 重要なリクエストを処理するプログラムで次を行う
    送られてきたリクエストに、サーバがフォームに埋め込んだのと同じ照合値が含まれているか否かを判定し、パラメータが含まれていないか値が合致しない場合は重要な処理を行わないようにする

2) ページを呼び出すごとに異なる値をとるトークンを使う方式

  • 重要な処理を行う直前の場面で次を行う
    • リクエスト強要対策の照合に用いる値を乱数等を用いて生成し、セッション変数等に記憶させる。毎回異なる値を用いる
    • 第三者がお膳立てしたフォームから発せられたリクエストではないことを判別するための、この照合値をフォームに埋め込んでブラウザに送る
  • 重要なリクエストを処理するプログラムで次を行う
    送られてきたリクエストに、サーバがフォームに埋め込んだのと同じ照合値が含まれているか否かを判定し、パラメータが含まれていないか値が合致しない場合は重要な処理を行わないようにする

(4) https:適用範囲

https:は、ログインページや個人情報入力・表示ページのみならず、ログインの状態を追跡する場面でも使用する。

1) HTTP認証を使用する場合

ユーザ認証データがブラウザからサーバへ送られるすべての通信でhttps:を使用する

2) ログインフォームとセッションIDの2つを使用する場合

ログインの時点で新しい値が与えられたセッションIDがやりとりされるすべての通信でhttps:を使用する

3) ログインフォーム、セッションID、ログイン追跡パラメータの3つを使用する場合

ログインの時点で発行されたログイン追跡パラメータがやりとりされるすべての通信でhttps:を使用する

(5) ログイン前セッションフィクセーション対策

1) ログイン前セッションフィクセーション

  • 会員登録等、ログイン前に複数の入力フォームが連なっているような場面を想定する
  • 攻撃者が入力フォームを呼び出し、そのフォームとそのときのセッションIDを用いて会員登録をするよう被害者に仕向けることができた場合、被害者は攻撃者が知っているセッションIDを用いて一連の画面に個人情報等の入力操作をすることになる
  • このとき、複数画面から受け付けた入力データがセッション変数に保持されるものであるなら、それらの値は攻撃者に参照されるおそれがある

2) hiddenフィールドを用いた対策

ひとつの対策は、セッション変数に入力値を保持するのではなく、入力途中においてはそれまでの入力値をhiddenパラメータで持ち歩くというものである。ただし、入力値をhiddenパラメータの値としてHTMLに埋め込む際、スクリプト注入(クロスサイトスクリプティング)対策が必要である

ログ記録設計における考慮事項の例

1) ログ記録方式の設計

  • どのようなイベントを記録するか
  • どの情報を記録するか
  • 記録する媒体を何にするか
  • ログの保護策としてどのようなものを講じるか
  • 複数コンピュータの時計の同期をはかる

業務仕様設計における考慮事項の例

(1) トランザクションに関する設計

1) 入出力項目設計

  • サーバとブラウザの間でやり取りされるデータ項目を、HTTPプロトコル・レベルにおける姿で列挙し、仕様を記述する
  • これにもとづいて入力検査を行うことになる

(2) ユーザインタフェース設計

1) ページレイアウト

本物であることの確認手段を封じないウィンドウレイアウトデザインをする

  • フレーム多用の回避
  • 目立つ場所への「ログアウト」ボタンの配置
  • ブラウザのアドレスバーおよびステータスバーを非表示にしない
  • マウス右ボタンコンテキストメニュー等、ページのプロパティを参照する手段を使用不能にしない

2) エラー時のページ遷移

エラーの種類に応じた画面遷移を設計する

  • ユーザ認証未了 → 「ログインしてください」、ログインページへ
  • ユーザ認証失敗→「ログインできません」、ログインページへ
  • アクセス認可違反→「このページ/リソースにはアクセスできません」、メニュー等へ
  • リクエスト強要の疑い→「順路から外れています」、メニュー等へ
  • 受け入れられない入力データ→エラーメッセージ、元の入力フォームへ
  • プログラム内部の異常→異常のお詫び、メニュー等へ

(3) コードに関する設計

1) コードの積極的使用

チェックボックス、ラジオボタン、選択肢項目等、ユーザが直接値を入力しないフォーム項目も、悪意あるユーザによって値が改ざんされているおそれがある。

これらの選択項目については、次のように取り扱う。

  • とり得る値について数字や英字からなるコードを割り当てる
  • HTMLタグにはコードの値を埋め込む
  • 入力の時点でコードの妥当性を検査する
  • 名称表示の必要があればテーブルを引いて名称を得、それを表示する

これにより、外部からプログラムに不正な値が送り込まれる機会を減らすことができる。

推奨しない例
<select name="pref">
 <option value="北海道">北海道
 <option value="青森県">青森県
 <option value="岩手県">岩手県
 <option value="宮城県">宮城県
 <option value="秋田県">秋田県
 ...
</select>

→ 入力パラメータ pref の値をデータベースに格納

推奨例
<select name="pref">
 <option value="01">北海道
 <option value="02">青森県
 <option value="03">岩手県
 <option value="04">宮城県
 <option value="05">秋田県
 ...
</select>

→ 入力パラメータ pref の値が2桁の数字であることをチェック
→ 値が 01~47 の範囲内であることをチェック
→ 2桁のコードをデータベースに格納

モジュール分割設計における考慮事項の例

1) ソフトウェア全体のモジュール構造のデザイン

  • フレームワークとして共通に整備する部分はどこか
  • ビジネスロジックとして個別に開発する部分はどこか

2) 共通モジュールの企画

  • フレームワークモジュール
  • 入力検査モジュール
    • データの種類に応じた、文字種検査、桁数検査、論理検査等のモジュール
  • エラー処理モジュール
    • エラーメッセージ表示等にスクリプト注入(クロスサイトスクリプティング)脆弱性が発生しない考慮を加える
    • 余計な情報を表示して攻撃のヒントを与えないようにする
  • 特殊記号エスケープ用のモジュール
    • スクリプト注入(クロスサイトスクリプティング)対策用のエスケープ関数
    • SQL注入(SQLインジェクション)対策用のエスケープ関数
    • コマンド注入(コマンドインジェクション)対策用のフィルタ関数 等

3) エラー時ページナビゲーションのモジュール化

  • エラー時ページナビゲーションに対応したエラー表示共通ページの用意
  • エラー処理を担当するモジュールの用意