サイト脆弱性をチェックしよう! -- 第10回:静的診断で脆弱性を見つけよう
<目次>
目次[非表示]
- 1.静的診断の効果
- 2.脆弱性発見のための静的診断
- 3.静的診断の留意点
- 4.まとめ
アプリケーションの脆弱性を発見する手法として、二つの方法がある。一つは稼働しているアプリケーションに対して、様々なデータを送信して、脆弱性を発見する動的診断で、もう一つはソースコードを読み、脆弱性を発見する静的診断である。静的診断ではソースコードレビューで実施する内容のうち、セキュリティに関する問題のみを指摘する。
静的診断の効果
以下の三つの効果を静的診断により得ることができる。
- 脆弱性対策工数の削減
動的診断では、動作するアプリケーションがある程度完成してから脆弱性を発見することになる。このため、多くの場合リリース直前に脆弱性が発見されることになり、リリース前に修正することは非常に難しいために、脆弱性が放置されたままアプリケーションがリリースされることもある。また、動的診断にかけられる期間は限られており、すべての脆弱性を発見できるわけではない。 静的診断はソースコードを書き上げた直後であっても実施可能であり、脆弱性が発見された場合でもリリースまでの期間という観点では、修正する余裕がある場合がほとんどである。そのため、修正の手戻りが動的診断に比べ大幅に少なく、リリース時期に影響されずに脆弱性を修正できる。 - 診断範囲の網羅性
特定条件下で発現するような脆弱性は、動的診断では時間的な制約により発見できないことがある。しかし、静的診断は全てのコードをレビューするため、このような特定条件下で発現する脆弱性も見つけられる場合もある。 また、静的診断を実施することで、動的診断では見過ごしがちな暗号方式の問題やバッチ処理中のSQLインジェクションなどを見つけることが可能だ。 - セキュアコーディング方法の習得
静的診断の過程でアプリケーションのコードをセキュリティの観点で精査することにより、脆弱性を作り込むコードについて学習できる。結果的に、脆弱性のないコードを書けるようになることが期待できるのだ。
脆弱性発見のための静的診断
一般的なソースコードレビューでは処理の最初に呼び出されるところから、一連の処理が終了するところまで、一行一行バグが存在しないか、コーディング規約に合致しているかを確認しながらソースコードを読んでいく。このため、全部のコードを読んでいくには非常に多くの工数がかかってしまう。
一方、静的診断では、SQLインジェクションなどの脆弱性が発生する可能性がある箇所だけを選んでソースコードを読んでいくことが可能だ。もちろん、それだけではすべての脆弱性を発見することは非常に困難である(例えば、レースコンディションやDoSの一部などの脆弱性はこの方法だけでは発見できない)。
具体的には以下のような手順で行う
- 脆弱性が発生する可能性のあるメソッド・関数をgrepなどで検索する
- 検索対象のメソッド・関数の引数を確認する
- その引数を使用している箇所をメソッド・関数内で確認する
- 検査済みのデータしか受け取らない処理になっているなど、脆弱性を引き起こすデータが引数に含まれる可能性がない場合、1で検索したメソッド・関数では脆弱性が存在しない。そうでない場合、その引数がどこでデータを設定されるか該当メソッド・関数あるいはクラス内で確認する
- データを引数に設定する箇所が該当メソッド・関数の引数である場合、そのメソッド・関数を呼び出す箇所をgrepなどで検索する
- 2~5の確認を、引数に外部(リクエストのパラメータやファイル、DBなど)から取得する値が設定される箇所まで行う。外部から取得する値が設定されている箇所まで遡ってコード上で処理を追えた場合、脆弱性があると判断する
以下のようなコードを含むアプリケーションを例に手順を解説する
- SQLインジェクションの脆弱性が発生する可能性のある関数であるmysqli_query()をgrepで検索する。db_query()関数にmysqli_query()を使用している箇所(db_query()の五行目)が存在することが分かる
- query()の引数$sqlがどこで使用されているかをdb_query()関数の中で確認すると、この関数の引数となっていることが分かる
- db_query()関数がどこから呼び出されているかをgrepで検索すると、user()関数で使用されていることが確認(user()の二行目)できる
- db_query()関数の引数$sqlがどこで使用されているかを確認するとuser()関数一行目で変数$idを文字列結合でSQL文を作成していることが分かる
- user()関数で$idがどのように使われているかを確認すると、この関数の引数として使用されているので、次にuser()関数を呼び出しているところをgrepで検索する
- printuser()関数の一行目でuser()関数が呼び出されていることが確認できる。その引数を見ると$_GET['id']となっているため、パラメータidの値がそのまま使用されていることが確認できる
- user()関数内で作成されるSQL文にパラメータidの値がそのまま使用されるため、脆弱性があると判断できる
静的診断では、このようにして脆弱性の有無を確認していく。この例では簡略化しているが、実際のコードではif文などで一部データが除外されることがあるため、留意が必要である。
以上のように、コードを読むことができれば、静的診断で脆弱性を発見することはそれほど難しいことではない。しかし、脆弱性を引き起こすメソッド・関数がどれなのかを判断するには、ある程度のセキュリティ知識が必要である。例えばSQLインジェクションであれば、SQL文を実行するメソッド・関数となり、XSSであれば、HTTPのレスポンスボディ中に出力するメソッド・関数がそれに該当する。これらの脆弱性を引き起こすメソッド・関数は、言語、フレームワークに依存して異なるためこちらでは記載しないが、詳細はIPAの「安全なWebサイトの作り方」などを参考にしていただきたい。
静的診断の留意点
レビューにかかる工数は、コードを書いた開発者のレベルに大きく依存する。開発者のレベルが低いとレビューしなければならないコードの量が大幅に増え、多くの脆弱性が発見される。一方、開発者のレベルが高いとレビューしなければならないコードの量は少なく、ほとんど脆弱性が見つからない。
また、静的診断で発見された脆弱性の全てが攻撃に利用できるわけではない。これは、入力データが外部から操作不可能であったり、フレームワークやWebサーバなどの設定で攻撃に利用できるデータを除外している場合があったりするためである。しかし、攻撃に利用できなくても、仕様の変更などにより将来的に脆弱性となる可能性があるため、可能な限り修正しておくことを推奨する。
まとめ
セキュリティの観点でコードレビューを行うことは、実施しないことに比べ開発工数が増えるが、脆弱性を大きく減らすことができる。動的な脆弱性検査に比べ、コードを読み書きできる開発者であれば必要なスキルは通常のレビューと大きく異なるものではないので、すぐにでも実施することが可能だ。
書き終わったコードをコミットする前に実施するだけでも脆弱性を減らす効果が得られるので、積極的にコードレビューによる静的診断を実施することを推奨する。