コード”だけ”に着目すると脆弱性が量産されます。それはコードだけでは片手落ちであることが理由です。
- コンピュータープログラムは「コード」と「データ」によって動作する
言い換えると
- コンピュータープログラムは「機能」と「データ」によって動作する
プログラムの目的は「特定の機能」をコードによって実装することが目的ですが、動作する為には「データ」が欠かせません。
「データ」にも着目するとソフトウェアセキュリティは向上します。今実装されているソフトウェアセキュリティ対策は「コード(機能)」に偏重しているために脆弱になっています。
例えば、JSONPが危険な理由は「本来データであるJSONをプログラムとして実行している」ことにあります。
X-Content-Type-Options: nosniff を指定するのは「本来データであるJSONをJavaScriptとして実行させない」ようにすることを目的です。X-Content-Type-Options: nosniff の導入は「データ」に着目したセキュリティ対策と言えます。「データ」として完全に分離し、「コード」として実行できないようにします。
開発者の多くはソフトウェアセキュリティを考える際、「どのようなコードで攻撃を防止するのか?」を考えていると思います。これだけでは何時まで経っても信頼性が高い(=セキュアなソフトウェア)物は”原理的”に作れないです。
※ 「コードだけに着目するセキュリティ対策」とは「攻撃が発生する箇所(コード)だけに着目するセキュリティ対策」を指しています。
TL;DR;
攻撃者はアプリ仕様とは無関係に「あらゆる入力データ」対して「コードを含む出鱈目な攻撃用データ」を送ってくる。攻撃者は「データ」に着目している。
開発者は「コード」に着目している。攻撃されても「データ」ではなく、誤作動する「コード」に着目し対策する。
「コード」に着目し対策も必要だが、開発者が「データ」に着目し攻撃者が送ってくる「出鱈目な攻撃用データ」を廃除する対策を行なえば、より高いレベルの信頼性と安全性を容易に達成できる。「コードを含む出鱈目な攻撃用データ」の多くが単純な「データ形式のチェック」で無効化できる上、そのチェックは正しいコード実行を保証する。
正しく動作するプログラムには「正しいコード」と「正しい(妥当な)データ」の両方が必須の必要条件である。これはプログラムの動作原理から論理的に導き出せる。
残念ながら、ほぼ全てのWebアプリケーションはこのような作り方になっていない。
攻撃者はデータに着目している
遥か昔から攻撃者はデータに着目しています。攻撃を可能にするのは「攻撃用データで誤作動するコード」です。ソフトウェアに対する攻撃が成功するには
- 攻撃用データ
- 脆弱なコード
の両方が必要です。
ソースコードを読んで脆弱なコードを探す攻撃者も居ますが、そんな面倒なことしなくてももっと簡単に探せます。攻撃用のデータを送ってみて、誤作動するかどうか確かめるだけです。
コードを読まなくても、攻撃者はこれから説明するような手順で攻撃可能な脆弱性を見つけます。例えば、OWASPの資料ではJavaScriptインジェクションが可能なコードを探すデータとして次のデータを紹介しています。
javascript:/*--></title></style></textarea></script></xmp><svg/onload='+/"/+/onmouseover=1/+/[*/[]/+alert(1)//'>
\";alert('XSS');//
これらは様々なコンテクストにデータが出力された際、JavaScriptコードを実行可能かチェックする文字列データです。こういった脆弱なコードを探すデータは「Locator(ロケーター)」と呼ばれ、攻撃者は昔から使っています。上記のように比較的簡単な物からエンコーディングを利用した複雑な物まで、色々あります。
SQLインジェクションのロケーターは超簡単です。
'"
シングルクオートとダブルクオート1を混ぜたデータを送るだけです。このデータでデータベースエラーになれば、SQLインジェクションに脆弱だと分かります。
アプリケーションにこういった脆弱性ロケーターを送り、誤作動するようであれば誤作動を起こす脆弱なコードが”どこかに”あります。
一つ一つチェックするのは大変で難しそうだな、と思うかも知れません。しかし、世の中にはFuzzingツール2という便利な物があります。様々な「脆弱性ロケーター」を使って凡そ自動的に攻撃可能な入力データを特定できます。
攻撃者にとってはパラメーターが何であるのか、はどうでも良いことです。アプリ仕様やインターネット標準などは一切無視です。お構いなしに、出鱈目なデータを送ってきます。
記号付き、制御文字付き、壊れた文字エンコーディング、大きすぎる/小さすぎるサイズ何でもありです。ツールが自動でチェックしてくれるのですから手間要らずです。
普通はエラー画面を返すのに、HTTPステータス500番台で突然死する物を見つけたら「ほくそ笑む」時です。入力バリデーション(セキュアコーディング第1原則)、出力無害化(セキュアコーディング第7原則)、例外処理が甘いアプリはチョロいなと。
攻撃者にとっては脆弱なコードがどこに在るのか?そんな事もどうでも良いことです。まず攻撃用データでおかしな動作をする入力(入り口)を見つける努力をします。見つけたらその入力(入り口)を使って実際に攻撃可能か、確認するだけです。
この確認作業はソースコードが入手可能であれば簡単です。デバッガーを使って攻撃用入力データでおかしな動作をする入力を送り、ステップ実行やウォッチポイントを設定して脆弱なコードを超簡単に特定できます。攻撃用データで例外などが発生している場合は何の手間も要りません。デバッガーが勝手に例外/エラーが発生した箇所をピンポイントで教えてくれます。3
脆弱なコードを見つけた後も簡単です。大抵の場合、脆弱なコードを見ればどんな攻撃用入力データでどんな攻撃が可能であるか即座に判ります。
一旦まとめると、攻撃者は次のようにデータに着目した攻撃を行っています。
- データに着目し、様々な攻撃データを送る
- 入力データ(入り口)に着目し、脆弱なコードを探す
攻撃者は「入力データに着目して有利な立場」にいます。攻撃用データをツールで自動送信して、誤作動する入力(入り口)を自動的に見つけ出します。
一方開発者は?
一方、開発者は
- 脆弱なコード
に着目しています。多くの場合、問題が発現するコードに着目し、そこで攻撃用データを何とかしようしています。
「攻撃用データ」を送られてきて、実際に「攻撃が行われる脆弱なコード」に着目するのは当然と言えます。基本的には外部システムへの出力は誤作動しないよう全て無害化されるべきなので、不必要なことでもありません。「攻撃可能な脆弱なコード」は全て見つけ出して修正すべきです。
しかし、もう少し広い視点/全体的な視点からよく考えてみてください。「攻撃可能な脆弱なコードを全て見つけて直す」、これは「悪いモノを定義/見つけて対策」するブラックリスト対策です。ブラックリスト対策で「攻撃可能な脆弱なコード」を全て見つけて直す事は可能でしょうか?
Railsセキュリティガイドには次のように書かれています。
ブラックリストではなくホワイトリストに基づいた入力フィルタを実施することが絶対重要です。ホワイトリストフィルタでは特定の値のみが許可され、それ以外の値はすべて拒否されます。ブラックリストを元にしている限り、必ず将来漏れが生じます。
「自分が書いた文章か?」と錯覚するような文章になっています。しかし、書いたのは私ではありません。Railsプロジェクト関係者の誰かが書いた文章です。この文章は入力バリデーションについて記載していますが、出力についても同じです。ブラックリスト型の対策は構造的に脆弱であり、ホワイトリスト型の対策が常に優先されます。これはソフトウェアセキュリティ対策の基本の「ソ」4です。
参考: OSコマンド実行対策も”コマンドインジェクションの原因はコマンドと引数が分離されていない事にある”と、悪い物(ブラックリスト)だけを定義すると対策漏れや勘違いを起こしやすく不十分で危険な対策になってしまいます。
攻撃者は「脆弱なコード」が何処に在るのか?気にしていないません。
- アプリ開発者自身が書いたコード
- ライブラリ開発者が書いたコード
- フレームワーク開発者が書いたコード
- 他のアプリ開発者が書いたコード
- OS開発者が書いたコード
データに着目している攻撃者にとって「攻撃可能なコードがどこに書かれているか?」といった区別はどうでも良いことです。目的は”攻撃すること”です。
攻撃者と開発者の視点が異なる問題
攻撃者は目的を簡単に達成する為、入力データに着目して攻撃しています。攻撃者は自動的に出鱈目な入力データを送信して、誰が書いたコードなのか無関係に攻撃してきます。
しかし、多くの開発者は問題が発現した出力箇所のコード(処理)に着目し、コード(処理)で何とかしています。アプリケーションの入力データが出鱈目ではないことを検証するコードには無頓着です。
何かがおかしい、と気付いた方も少なくないでしょう。
多くの開発者は攻撃者が出鱈目な入力データを送ってくる点を無視して、一生懸命に攻撃可能なコード(処理)を直そうとしています。これも必要なのですが、もっと手っ取り早く効果的な方法があります。
開発者も入力データに着目した攻撃対策をすれば良いのです。
そもそも一開発者どころか大企業であってもアプリケーションで使う全てのソフトウェア(ライブラリ、フレームワーク、言語、Cで書かれたライブラリ、etc)のコードを完璧に把握、脆弱性を修正するのは不可能です。これはブラックリスト型の対策でRailsセキュリティガイドにも書かれているように、頻繁なメンテナンスが必要で、その場所はコードの中に分散されており、必ずと言ってよいほど漏れが発生します。
よくセキュリティ対策には攻撃者目線が必要である!と言われていますが、残念ながら多くのアプリケーション開発プロジェクトで攻撃者目線の対策、入力データに着目した対策、が疎かです。
アプリケーション開発者のセキュリティ対策の目的は?
アプリケーション開発者のセキュリティ対策の目的は”アプリケーションを守ること”です。アプリケーションを守るには、誰が書いたコードなのかに関係なく、アプリケーション全体を守らなければなりません。
アプリケーションを守るには次の全てが守られている必要があります。
- アプリ開発者自身が書いたコード
- ライブラリ開発者が書いたコード
- フレームワーク開発者が書いたコード
- 他のアプリ開発者が書いたコード
- OS開発者が書いたコード
何処かに脆弱なコードがあった場合、往々にしてアプリケーション開発者は「アプリ開発者自身が書いたコード」には責任はあるが他は他人の責任と認識します。
しかし、攻撃者にとって脆弱なコードが何処にあるのか、誰が書いたのか、はどうでも良いことです。
同じように”アプリケーションを守ること”を目的とするアプリケーション開発者にとっても、脆弱なコードが何処にあるのか、誰が書いたのか、はどうでも良いことです。他人が書いたコードだから自分には責任がない、と言っても攻撃者は構わず攻撃してきます。
次のブログでは「サンプルとして挙げたシェルスクリプトが脆弱」とコメントが付いています。しかし攻撃者にとっても、そして本来はアプリケーション開発者にとっても目的さえ達成できればどうでも良いことです。
※ コマンド実行の話だったので、わざとコマンド実行に脆弱なシェルスクリプトを書きました。しかし、シェルスクリプトに脆弱性がない、つまり仕様通りの動作をする場合にもセキュリティ問題は発生します。詳しくは上記ブログを最後まで読んでください。
アプリケーション開発者の目的は”アプリケーションを守ること”です。アプリケーションが入り口となり、他人が書いたコードも攻撃対象になるのであれば、目的に従って可能な対策を取る必要があります。
繰り返しますが、攻撃者は「データに着目して有利に攻撃」してきます。攻撃者が攻撃対象とするコードは自分が書いたコード、他人が書いたコード、無関係です。
実用的なアプリケーションのほとんどは非常に複雑で、コードによってデータがどのように利用されているのか?1人の開発者が全てを把握することは実質的に不可能です。脆弱なコードを全て見つけて対策することも実質的に不可能です。
開発者がデータに着目すればソフトウェアセキュリティは飛躍的に向上する
少なくないソフトウェア部品が「安全な入力データであることを呼出側が保証することを期待(=利用者が安全に利用することを期待)」しています。
「安全な入力データであることを呼出側が保証することを期待する」コードが悪い!と思う方も居るかも知れません。逆説的に聞こえると思いますが、これは効率的なコード、安全性の高いコードを記述する為に必要な考え方です。
もし、このような期待をするコードが悪いのであれば、ソフトウェア全体に「契約プログラミング」を適用し、運用時にも契約チェックをするコードにしなければなりません。
厳格な契約プログラミングでは、開発時に全ての関数は入力データ(引数)と出力データ(戻り値)が正しいデータであることをバリデーションします。正しいデータであることをバリデーションするには
- データ型の一致
- 数値ならデータ範囲の一致
- 文字列なら
- 長さ
- 文字エンコーディング
- 利用する文字
- 文字列形式(日付、電話番号など)
- 構造を持つデータ(JSONやXML)なら
- 構造の正しさ
- 構造内に含まれるデータの正しさ(上記の数値/文字列チェック)
- 引数/戻り値の数
- 引数/戻り値の過不足
これら全てが正しいかチェックします。
全ての関数でこのようなバリデーションを行うのが「安全なデータであることを呼出側が保証することを期待しない」コードです。現実にはこのようなコードはほどんど存在しません。
実用的に稼働しているコードでは効率良く実行する為に、全ての関数で上記のデータバリデーションをせず、「ここでチェックしないと不味いぞ」という箇所、一部の関数でのみデータのチェックします。しかも、完全なチェックではなく一部のチェックのみで、分散しています。これはブラックリスト型の対策の構造です。
ここでRailsセキュリティガイドの文章をもう一度。
ブラックリストではなくホワイトリストに基づいた入力フィルタを実施することが絶対重要です。ホワイトリストフィルタでは特定の値のみが許可され、それ以外の値はすべて拒否されます。ブラックリストを元にしている限り、必ず将来漏れが生じます。
ブラックリスト型対策は構造的に脆弱です。Railsセキュリティガイドではこう書いています。
特定の項目だけを許可するホワイトリストアプローチは、特定の項目だけを禁止するブラックリストアプローチに比べて、ブラックリストへの禁止項目の追加忘れが原理的に発生しないので、望ましい方法であると言えます。
しかも、APIによってはそもそも呼び出されたら最後、安全性の保証を行えない物も沢山あります。プリペアードクエリもその良い例です。
$sql = "SELECT {$_GET['col']} FROM my_table"; pg_query_param($sql, []);
などとされたら、データベース側では何も対策できません。攻撃者は$_GET[‘col’]を使って好き放題にSQLインジェクション攻撃が可能です。
参考:
入力データが妥当であるかバリデーションすると信頼性が上るのはソフトウェアの動作原理
ソフトウェアが正しく動作する為に必要なモノは何でしょうか?
- 正しいコード(正しいアルゴリズム/構造)
これは絶対条件と言えるでしょう。正しいコードは大切です。全ての開発者が十分に注意していると思います。
ソフトウェアが正しく動作する為にはもう一つ絶対条件があります。
- 正しい(妥当な)データ
どんなに正しいコードでも、データが正しく処理できない不正なデータであれば正しく動作できません。整数データが必要なコードに文字列データを渡しても動作しません。-128から127の整数値を正しく処理するコードに、整数データであっても、1000を渡すと正しく処理できません。名前に垂直タブが混ざっているデータは
入力バリデーションの要素の1つとして
- データ型の一致
を挙げましたが、これは絶対条件の一部に過ぎません。
強いデータ型を持つ言語はデータ型が一致することを強制します。この方が信頼性が高くなりますが、強いデータ型を持つ言語だから安全になる事も、正しく動作するようになる事もありません。
- 数値ならデータ範囲の一致
- 文字列なら
- 長さ
- 文字エンコーディング
- 利用する文字
- 文字列形式(日付、電話番号など)
- 構造を持つデータ(JSONやXML)なら
- 構造の正しさ
- 構造内に含まれるデータの正しさ(上記の数値/文字列チェック)
- 引数/戻り値の数
- 引数/戻り値の過不足
データ内容が仕様に照らし合わせて完全に正しいか、妥当(内部のエラー処理で対応可能なモノ)でないと正しく動作できません。
そもそもコードは正しい/妥当なデータでなければ動かないので、入力バリデーションはできるだけ早い時点で実施し、出来るだけ早く失敗させます。
これはFail Fast原則と言われる基本プログラミング原則の1つです。入力データに対してFail Fast原則を適用すると、確実かつ飛躍的に信頼性と安全性が向上します。これはセキュアコーディングの第1原則でもあります。
※ セキュリティ対策は総合対策なので他の原則も欠かせません。セキュアコーディングの第7原則は「全ての出力データを出力先に対して無害化する」です。
セキュアなデータバリデーションの構造を図にする以下のようになります。
参考:
まとめ
攻撃者は”入力データ”に着目し、ツールを使って楽をしながら脆弱なコードを見つけています。
開発者も”入力データ”に着目し、攻撃者が出来るだけ嫌がる対策をすべきであることは明らかです。
単純に無効な攻撃用データをバリデーション(検出)して拒否するだけではなく、
- 攻撃を記録し、可能な対応を行う
- ログインしているならログオフさせる
- それでもまたログインして攻撃してきたらアカウントをロックする
- それでも繰り返し攻撃が行われるようなら管理者に緊急のアラートメッセージを送信する(機械的にIPアドレスをブロックすると多くの巻き添えを作る可能性があります)
といった対策を行う必要があります。
これはプログラミングの基本原則のFail Fastに則った形で実装する必要があります。できる限り早く攻撃用入力データを検出する方が漏れが少く、余計な問題も作らないからです。この際、攻撃用の不正なデータをブラックリスト型で検出するのは間違いの元です。注意しましょう。
攻撃の検出/記録/対応は2017年度版OWASP TOP 10で要求されているセキュリティ対策です。できていない場合、OWASP的には脆弱なアプリ認定されてしまいます。
厳格な入力バリデーションはISO 27000/ISMSの要求事項でもあります。EUでビジネスをしている会社は特に注意した方が良いでしょう。GDPRが既に施行されています。国際標準として2000年から要求されている基本セキュリティ要件を満たしていないアプリだと、とんでもない制裁金の対象になるリスクが高くなります。
-
- SQL識別子クオート文字が ” (ダブルクオート)である場合 ↩
-
- 予め決められたルールに基き、攻撃用データを送ってアプリケーションの反応をチェックするツール。ランダムデータを送ることも可能。 ↩
-
- 実際には例外/エラーが発生する条件となるデータが揃っていないと、難しい場合が多いです。しかし、簡単に分かるケースも少くありません。 ↩
- 基本の「キ」よりも基本と言う意味でソフトウェアセキュリティ対策の基本の「ソ」 ↩