「しっかり出力対策”だけ”するのがセキュリティ対策のベストプラクティス」とする考え方1があります。しかし、これはベストプラクティスどころかアンチプラクティスです。
アンチプラクティスをベストプラクティスと勘違いしている限り、満足のいくセキュリティ対策(=リスク管理)は不可能です。セキュリティ対策は総合的なリスク対策です。「これ”だけ”やれば良い」とするセキュリティ対策は大抵アンチプラクティス2です。
※ 今まで出力対策”だけ”がセキュリティ対策だと勘違いしていた方には確証バイアスが働きやすいようです。論理的/構造的にどうすればリスクを効果的に削減できるのか?を考えると理解るはずです。
なぜ「出力対策”だけ”」はアンチプラクティスなのか?
アンチプラクティスであることの説明は簡単です。HTMLとSQLを例に解説します。例に使うユーザーID($_GET[‘userid’])は数字のみで構成される仕様とします。
HTMLとSQL出力の無害化
HTML
<?php // エスケープ echo htmlspecialchars($_GET['userid']); ?>
SQL (PostgreSQL)
<?php // エスケープ pg_query('SELECT * FROM users WHERE id = '. pg_escape_literal($_GET['user']) .';'); // プレイスホルダ pg_query_params('SELECT * FROM users WHERE id = ?;', [$_GET['user']]); ?>
上記のコードでHTMLとSQL出力は無害化3できます。HTML、SQLに対してインジェクション攻撃”は”できません。
この「HTMLとSQL出力の無害化」はセキュアなのか?
セキュアではありません。上記の出力無害化は”インジェクション攻撃だけ”を防ぎます。
不正な制御文字/記号/大きすぎるデータ/壊れた文字エンコーディングからコードとして実行できる文字列まで、ありとあらゆるリスクのある出力を許可しています。余計な文字/異常なサイズ/出鱈目な形式のデータはリスクしか生みません。
出力対策”だけ”ではリスクを先送り(将来にリスクを残す)ことになります。リスクの先送りが必要なデータ4の場合は仕方ありませんが、それ以外の場合にリスクを廃除せず、リスクを先送りするのはバッドプラクティスです。
リスクの先送りはFail Fast原則に違反しており、問題の原因です。5
不必要にリスクを先送りされたデータが発見しづらいセキュリティ問題の原因となる
は鉄板のセキュリティ問題発生パターンです。「このデータにリスクがあるとは思わなかった」、不必要なリスクの先送り(出力対策”だけ”)は典型的なセキュリティアンチパターンです。
出力するデータの多くは、プログラム内のビジネスロジックで”利用”されています。これらのビジネスロジックが異常なデータによって誤作動しないことを保障するのは困難というよりほぼ不可能です。全ての低レベルライブラリのコード(Cで書かれた共有ライブラリなどまで)を読んで安全性を保障し、開発しているプロジェクトがどれだけあるでしょうか?ほぼ無いと言っても構わないでしょう。6
そもそも正しい出力でない
一見、出力対策”だけ”でもプログラムは誤作動しないように思えます。しかし、次のようなHTML出力は普通、明らかに間違っています。
あなたのユーザーID:<script>alert(1)</script>
次のようなSQL出力も普通、明らかに間違っています。
INSERT INTO users (id) VALUES ('1234 or 1=1');
出力対策”だけ”7では
出力対策”だけ”に頼っていると思わぬ所で脆弱になります。例えば、RDBMSの中にはデータ型には関係なくデータを保存するモノも存在します。SQLiteのカラムは基本的に全て”文字列型”です。
キャッシュシステムによく利用されるRedisやMemcachedなどにも基本的にはデータ型は関係ありません。
出力対策”だけ”に頼って不正なデータを許可していると、DBMSを変えたり、キャッシュシステムを導入したりするとインジェクション攻撃に脆弱になったりします。リスクの先送りが積もり積もって攻撃可能な脆弱性を作ってしまいます。システムが複雑になればなるほど、こういった脆弱性のリスクが高くなります。
大規模なシステムになっていなくても、抽象化レベルが高いコードになるとセキュリティホールを作ってしまったことに気が付かないケースを見かけます。
インジェクション”だけ”を防いでも”十分”なセキュリティ対策ではない
ISO 27000の定義だとITセキュリティが護るべきモノは、機密性、完全性、可用性、信頼性、真正性、否認防止性、としています。
インジェクション攻撃は不正な攻撃者の命令を実行する攻撃なので機密性、完全性、可用性、信頼性、真正性、否認防止性、全てに影響します。このためインジェクション攻撃対策は重要ですが、インジェクション対策がITセキュリティの”全て”ではありません。
不正なデータによるインジェクション攻撃ができないとしても、不正なデータは完全性、可用性、信頼性に影響します。例えば、
- 完全性: 数値のデータに文字列は明らかに正しくない。文字列データに壊れた文字エンコーディングは正しくない。
- 可用性:壊れた文字エンコーディングの場合、ブラウザに空白ページが表示されるたり、サーバーが出力時に例外を発生し表示されるべきページが表示されない。
- 信頼性: 壊れたデータに対するライブラリの挙動はバージョンによって変わることがよくある。
出力対策”だけ”のセキュリティ対策は原則違反のアンチプラクティス
失敗するモノはできる限り早く失敗させる、Fail Fast原則はコーディングの基本原則の1つです。出力対策はプログラム処理の最後の方の処理です。出力対策”だけ”のセキュリティ対策はFail Fast原則に反しています。
セキュリティ設計の基本原則に「検証したモノ以外は信頼しない」があります。8 未検証のデータをプログラムで処理することはこの基本原則にも反しています。
CERT Top 10 Secure Coding Practicesはセキュアなコード原則のトップ10です。第一の原則が「入力バリデーション」、第七の原則が「出力の無害化」です。そして、入力対策は出力対策とは独立した対策(つまり両方必須)であるとしています。
出力対策”だけ”のセキュリティ対策はセキュアなコード原則でも見ても明らかなアンチプラクティスです。
出力対策”だけ”のセキュリティ対策は論理的にアンチプラクティス
セキュアなコードとは開発者の意図通りに動作するコード(=正しく動作するコード)です。半世紀以上前からコンピュータサイエンティストはコードが正しく動作するコードの証明方法を研究しています。現在でもまだまだこれからの研究分野ですが、論理的にハッキリ分っていることがあります。
プログラムは正しく動作できる入力に対して、正しく動作することができる
例えば、整数を要求するパラメーターに文字列を与えても正しく動作しようがありません。プログラムが正しく動作する前提条件として、プログラムが正しく動作できる入力が必要です。9
- 正しく動作する入力 = 正しい入力+入力ミスの入力
- 上記以外は全て不正な入力
入力の妥当性検証がない、正しく動作するハズがないデータを受け入れるアプリケーションは脆弱なアプリケーションです。
出力対策”だけ”のセキュリティ対策は攻撃防御方法としてアンチプラクティス
インジェクション攻撃は致命的であることが多いので、最優先で対応すべき脆弱性です。では、そのインジェクション攻撃はどのように行われるでしょうか?
ほとんどの場合、入力データとして”不正なデータ”をプログラムに渡してインジェクション攻撃を行います。
1つの入力データ(不正な攻撃用データ)は様々な箇所で利用される可能性があります。HTML、SQLは勿論、XML、Xpath、JSON、CSS、LDAP、コマンドやコマンドパラメーター、ファイルシステムのパス、ファイルデータ、JavaScriptの変数や識別子などに利用され、ありとあらゆる場所に保存/再利用される可能性があります。
明らかに不正な攻撃用データだと判ってるデータをプログラムに受け入れてから防御するのは明白なアンチプラクティスです。
出力対策”だけ”でリスク管理するのはアンチプラクティス
セキュリティのベストプラクティスの1つは「リスクとリスク対策を定期的に再評価し、必要であれば追加の対策/対策の修正を行う」です。
入力対策はソフトウェアセキュリティの最大の対策(=セキュアコーディングの第一原則)で、攻撃者からの攻撃用データを廃除する最初のリスク対策です。
入力対策をセキュリティ対策として管理しない、通常のアプリ仕様として管理するとした場合、セキュリティのベストプラクティスの1つである「リスクとリスク対策を定期的に再評価し、必要であれば追加の対策/対策の修正を行う」を行わないことになります。
これは明らかにセキュリティのアンチプラクティスです。
まとめ
セキュアコーディング、リスク管理、防御方法のアンチプラクティスの実施は、問題発生のリスクを増やしているだけです。一般向けアプリケーションの場合、基礎中の基礎くらいは実践していないと、無用な訴訟リスクさえ生まれます。10
全てのソフトウェアにセキュリティは必要ありません。例えば、ライブラリは「利用者がライブラリ仕様に合う正しい入力値を送信してくること」を前提条件としても問題ありません。開発ツールが危険な動作したり、高い特権レベルを要求したり、分離された環境を要求することも問題ではありません。
しかし、広く一般に公開するアプリケーションには必要とされるレベルのセキュリティを持っていなければなりません。
歴史的な経緯や誤ったキャンペーンなど、様々な理由でほとんどのアプリケーションはセキュリティのアンチプラクティスを実装したモノだらけの状態にあります。本来であればセキュリティ専門家が率先してアンチプラクティスであることを指摘し、修正に導くべきですが残念ながらそうなっていません。
※ セキュリティ標準やセキュリティガイドラインの多くは、ベストプラクティスを推奨/要求しています。念の為。
アンチプラクティスを実践しながら、満足いくセキュリティ/リスク管理が達成できないのは当然です。今のアプリケーションは少しのライブラリやフレームワークのバグ/仕様にも影響され脆弱になります。
2017年版OWASP TOP 10から攻撃を検出/対応しないアプリケーションは脆弱なアプリケーションとしています。無理矢理に出力コードで対応することも可能ですが、汚い&脆弱な設計になります。セキュアコーディングの第一原則を実装し、まずそこで検出/対応するのがセキュアなソフトウェアのアーキテクチャーです。
幸い(?)なことにほとんど全てのアプリケーションが出力対策に偏重したセキュリティ構造になっています。今ならまだ言い訳ができる段階だと思います。数年後には”既に遅い状態”になっているかも知れません。
- 例えば、このプレゼンは「出力対策”だけ”しっかりすれば良い」としています。開発者は既に出力対策は不十分でも対策しようとしています。2017年版OWASP TOP 10でも指摘しているように、ほぼ全てのアプリケーションで全く足りていない対策が入力対策であるのに「出力対策”だけ”すればよい」ではより安全なソフトウェアは作れません。 ↩
- 例えば、”プレイスホルダだけ使えば良い”はアンチプラクティスです。 大抵の場合、”これだけ”ではなく、他にしなければならないこと、注意しなければならないこと、があります。「これ”だけ”やれば良い」はセキュリティ業者の使い古されたセールストークです。 ↩
- 現在のPHPの場合、通常の使い方だとこの記述で安全です。文字エンコーディングの整合性が取れていない場合、問題となる場合があります。 ↩
- ユーザーが自由に入力できるコメント等のデータの場合 ↩
- リスクを出来る限り早く廃除/削減する対策はリスク管理の基本中の基本です。 ↩
- ネットワーク管理者はネットワーク全体の安全性に責任を持っています。ファイアーウォールで効果的に外部からの攻撃を防止可能であるにも関わらず、ネットワーク管理者が「未パッチの脆弱なPCが存在して、外部から簡単に攻撃できてしまったのはデバイス管理者の責任なので知りません」と主張することは正しい態度ではありません。アプリケーション開発者はアプリケーション全体の安全性に責任を持っています。入力検証で効果的に外部からの攻撃を防止可能であるにも関わらずアプリケーション開発者が「未パッチの脆弱なライブラリが存在して、外部から簡単に攻撃できてしまったのはライブラリ管理者の責任なので知りません」と主張することは同じように正しい(あるべき)態度ではありません。 ↩
- 出力時のバリデーションは正しさも保証できます。ただし、出力時/ロジック処理時のバリデーションは入力処理時よりいろいろな意味で非効率(網羅性、処理性能など)です。 ↩
- デフォルトでは何も信頼しない、は物理的/ネットワークセ的なキュリティ設計をしている方には当たり前の原則ですが、ソフトウェア設計では無視されていることが多いです。 ↩
- 入力ミスのデータやエラーデータ(存在する場合)もプログラムにとって”正しく動作できる入力”であることに注意。 ↩
- ISO 27000やその他の標準/ガイドラインが求めている入力バリデーションくらいは実装していないと、セキュリティ問題発生時に裁判に負けても当然だと考えられます。入力バリデーションの実施は四半世紀以上から提唱されている基礎/基本セキュリティ対策です。 ↩