プログラミングを覚えたら先ず知るべきコーディングガイドラインを紹介します。このブログではこれらのガイドラインを時々紹介していましたが、まとめて紹介するのは初めてだと思います。これから紹介するガイドラインはセキュアプログラミング/防御的プログラミング/セキュアコーディングと呼ばれる考え方に基づいたガイドラインです。
ここで紹介する考え方や基本はコンピューターサイエンティストらによって原理/論理から導き出された概念/考え方です。
論理的に出鱈目なセキュリティの考え方が当たり前かのように啓蒙され、脆弱なアプリケーションの作成を助長しています。最後にアンチプラクティスの例として紹介します。
なぜセキュアプログラミング/セキュアコーディングを知らなければならないのか?
ITセキュリティ分野、特にソフトウェアセキュリティ、その中でもWebアプリセキュリティはセキュリティ対策の基本が疎かにされています。基本が疎かな状態で良いのか?というと社会の状況はそうではありません。
- 海外の法律ですがGDPRでは科学的論理に基づいたITセキュリティを要求
- 日本に於てもITシステム構築時にセキュリティの基礎を無視した損害に対し、契約以上の賠償金支払が確定
- ようやくですが昨年からIPAもマトモなセキュアコーディングを啓蒙開始
このような状況です。科学的なソフトウェアセキュリティ対策はシンプルな原理に基づいて論理的/体系的に作られています。
- 原理: コンピュータープログラムは「正しいデータ」と「正しいコード」でのみ正しく動作する
※ 正しいデータ = 仕様的に正しいデータ + 仕様内の入力ミスのデータ
セキュアプログラミングは2つの正しさを保証することにより、プログラムを(出来る限り)正しく動作させることにより必要な安全性を確保します。
セキュアプログラミング/セキュアコーディングは国際情報セキュリティ標準やセキュリティガイドラインで要求されている技術です。全ての開発者が理解・実践する必要があります。
セキュアプログラミングの基本
現在では、予定した(期待通り)の入力で正しく動作するだけのプログラムでは不十分と考えられています。セキュアプログラミング(防御的プログラミング)がどのようにして生まれたのかは別のブログに記載しています。いつ頃から予定した(期待どおり)の入力で正しく動作するだけのプログラムでは不十分と考えられるようになったのかは、そちらを参照してください。
スライドだけでは解りづらいかも知れませんが、基礎/基本はこちらです。
https://www.slideshare.net/yohgaki/5-77628242
セキュアプログラミングを理解するには基本的なセキュリティ概念の理解が欠かせません。これを間違えているとセキュリティ対策をセキュリティ対策ではない、と根本的な勘違いをしてしまいます。
セキュリティ対策とは何であるか?はISO 27000(ISMS認証の基盤となっている国際セキュリティ標準)の「リスク対応」として定義されています。リスクを変化させる物すべてがセキュリティ対策と考えて構いません。リスクを増やす選択もセキュリティ対策の1つです。詳しくは標準と基本概念から学ぶ正しいセキュリティの基礎知識を参照してください。セキュリティ対策はPDCAサイクル(Plan Do Check Actionのマネジメントサイクル)で管理します。ここでは詳しく解説しません。ISO 9000やISO 27000の管理方法が参考になります。
セキュアプログラミングのベストプラクティス
セキュアプログラミングの基本概念は簡単です。以下のベストプラクティスは基本要素を簡潔にまとめています。
- CERT Top 10 Secure Coding Practices (日本語)
- OWASP Secure Coding Practices – Quick Reference Guide (日本語)
- CWE/SANS TOP 25 Monster Mitigations (日本語)
詳しい説明は省略します。英語ですが長い文章ではないので、詳しくはリンク先の解説をご覧ください。
セキュアプログラミングの基本は防御的にコードを書くことが基本です。データ、コード、全て安全だと保証されるまで安全ではないと想定します。全てのデータ、コードは安全ではないので、まず自分が担当する範囲のコードの境界(信頼境界)となる入力と出力を確実に制御します。入出力はブラウザやデータベースの入出力だけではありません。ライブラリの入出力も安全だと検証するまでは危険だとして取り扱います。
「自分が担当する範囲のコードの境界」とはアプリケーションならアプリケーションの入出力(ライブラリの入出力含む)、ライブラリならライブラリの入出力を確実に制御します。「確実に制御する」とは「確実に安全であることは保証する」こと意味します。
入力を確実に制御するには
- ホワイトリスト型の厳格なバリデーション
を行います。ソフトウェアのセキュリティ対策では基本的にブラックリスト型の対策は使いません。現実的に不可能どうしようもない場合にのみ選択する対策です。このようなケースはあまりありません。例外はホワイトリスト型の場合だと十分な性能が得られない、公開サーバーに対して繰り返し攻撃を行うIPアドレスをブロックする、場合などです。
出力を確実に制御するには
- 安全に出力する為に必要なエスケープ(エンコーディング)を行う
- エスケープ(エンコーディング)が必要ないAPIを利用する
- 入力時と同じように厳格なバリデーションを行う
を行います。「安全なAPIを利用する」は一番最初に知るべきセキュリティ対策ではありません。(コーディング規約などではOK。しかし、セキュアプログラミングを学ぶ上ではNG) 安全だ、と言われているAPIでも安全でない物も多くあります。
SQLのプリペアードクエリはその代表例です。プリペアードクエリはSQL文のパラメータのエスケープの必要性は無くしますが、SQL識別子のエスケープやSQL語句のバリデーションなどは行えない仕組みです。「安全に出力する為に必要なエスケープを行う」を理解しないでプリペア文に変数を埋め込む間違いをしてしまうプログラマは後を断ちません。
SQLインジェクション攻撃によって不正なSQL文を実行されなくても、出鱈目なデータが保存できてしまうプログラムも安全なプログラムとは言えません。遅すぎる段階でエラーになっても問題の原因になります。完全性、信頼性も重要なセキュリティ要素です。そもそもプログラムは妥当なデータでないと”原理的”に正しく動作しません。DBのデータ操作だけでなく、プログラム内には妥当なデータしか存在/処理されないことを保証する必要があります。
セキュアプログラミングのベストプラクティスを理解し、実践するだけで遥かに安全なプログラムを書けるようになります。
参考:
- 入力値の種類は3種類しかない
- バリデーションには3種類のバリデーションがある 〜 セキュアなアプリケーションの構造 〜
- 出力対策の3原則 + 1原則
- 出力対策の3つの役割 – フェイルセーフ頼みはNG
- ”形式的検証”と”組み合わせ爆発”から学ぶ入力バリデーション
契約プログラミング
契約プログラミングは上記のセキュアプログラミングのベストプラクティスをより良く理解する上で役立ちます。契約プログラミング(契約による設計)の基本概念も簡単です。
- 呼び出し側が呼び出される側の契約(制約/約束)を守る(事前条件)
- 呼び出された側は呼び出された後の結果(出力および状態)の契約を守る(事後条件)
言葉でいうよりコードで書いた方が分かりやすいです。
<?php function foo($a, $b) { // 呼び出し側が守るべき契約(事前条件) assert(is_int($a) && $a > 0 && $a < 100); assert(is_int($b) && $b > 10 && $b < 20); $result = $a * $b; // 呼び出された側が守るべき契約(事後条件) assert($result > 0 && $result < 100); return $result; } // 契約違反なし var_dump(foo(3, 11)); // 引数と戻り値が契約違反 var_dump(foo(-1, 15)); // 戻り値が契約違反 var_dump(foo(90, 15));
assert()で設定された条件が「契約」になります。通常、assert()は開発時にしか利用しません。運用段階ではこれらの「契約チェック」が無くても、正常に動作するようにプログラムします。
ここでは事後条件に戻り値以外の状態を確認していませんが、グローバル変数や外部システムなどを利用している場合はグローバル変数や外部システムの状態も確認します。
実行結果(PHP 5.6)
int(33) PHP Warning: assert(): Assertion failed in - on line 4 PHP Stack trace: PHP 1. {main}() -:0 PHP 2. foo() -:19 PHP 3. assert() -:4 PHP Warning: assert(): Assertion failed in - on line 10 PHP Stack trace: PHP 1. {main}() -:0 PHP 2. foo() -:19 PHP 3. assert() -:10 int(-15) PHP Warning: assert(): Assertion failed in - on line 10 PHP Stack trace: PHP 1. {main}() -:0 PHP 2. foo() -:22 PHP 3. assert() -:10 int(1350)
契約プログラミングのメリットは以下の通りです。
- 開発時にプログラムが正しく動作することが容易に判る → 契約違反が発生する場合、プログラムにバグがある
- 運用時にプログラムが高速に動作する → assert()は評価されないので高速に動作する
サンプルプログラムのように簡単な関数でも契約が無い場合、不正な処理となってしまうパラメータを許可してしまい、プログラムの問題が隠蔽されてしまいます。かと言って運用時にこのようなチェックを全ての関数で実施すると遅くて使い物にならないプログラムになる可能性が高くなります。契約プログラミングは安全性と性能をバランスよく実現します。
全ての処理に契約プログラミングを適用した場合、運用時にも評価する契約はどこになるでしょうか?外部から「入力」と外部への「出力」が残ります。
CWE/SANS TOP 25 Monster Mitigationのベストプラクティスの第一位が「入力の安全な処理」、第二位が「出力の安全な処理」であることは、たまたまそうなった訳ではありません。論理的に一位と二位は決まっています。入出力はソフトウェアの「境界」(信頼境界線)になります。信頼境界線での防御はソフトウェアのセキュリティ対策以外でもセキュリティ対策の原則でもあります。
※ CERT Top 10 Secure Coding Practices では出力対策は第7番目の原則としています。これは策定された当時、出力対策に偏重し過ぎた対策ばかりだったこと、も影響しているかも知れません。IPA版では出力対策は2番目になっています。
契約プログラミングを原理的に実施し、形式的入力データバリデーションは入力処理/論理的データバリデーションはロジックだけ、と仮定すると心配になりませんか?私は心配でなりません。別のセキュリティの原則を適用する必要があります。
信頼境界線を越えてきたデータ(つまりバリデーション済みのデータ)であっても、境界内で更にチェックします。契約プログラミングにおける契約を全て有効にするのでは、契約プログラミングのメリットである「高速な実行」がなくなります。「適切な場所」ではassert()を用いず、運用時にもチェックする「縦深防御」(多層防御)の仕組みを取り入れます。
因みに契約プログラミングはセキュアプログラミング(防御的プログラミング)の要素の1つとされています。契約プログラミングをサポートする言語は多くあり、契約プログラミングの要素(assert関数)をサポートする言語がほとんどです。言語として契約プログラミングをサポートするDやEiffelでなくても、ライブラリ化されたものが利用できる場合が多いです。
実際に契約プログラミング機能や要素を使わないとしても、契約プログラミングを意識したコーディングを行なうと適切な「境界防御」と「縦深防御」(多層防御)をプログラムの中に取り入れることができます。
まとめ
プログラミングを学習する場合、動くプログラムを作ることで精一杯だと思います。プログラミングを覚えたらまず次にセキュアプログラミングを覚えると良いです。個々の脆弱性、SQLインジェクションやJavaScriptインジェクション対策を覚えることも重要ですが、セキュアなプログラムの概念を知れば全ての脆弱性に自分で対処できるようになり、コードのセキュリティを担保するコード検査を行いやすいコードが書けるようになります。
プログラミングを覚えたら、まずセキュアプログラミングの概念を理解し、個別のセキュリティ問題に対処する。このような順序で学習すると安全かつ効率良いコードが書けるようになります。
セキュアプログラミングを行わなくても安全なプログラムを書くことも可能です。しかし、このようなプログラムは職人技で書かれたコードであり、エンジニアリング的に書かれたコードではありません。セキュアプログラミングを行っていないコードは第三者にとって安全性が解りずらく、メンテナンスも行いづらいコードであることが多いです。
少なくとも一度は本格的なWebアプリのリスク分析も行った方が良いです。モグラ叩きではない(=◯◯脆弱性だけを拾うブラックリスト型でない)本格的なリスク分析の経験がないと、正しくWebアプリのリスクを理解することは困難だと思います。
最後に、セキュアプログラミングと反対の主張(入力バリデーションはセキュリティ対策ではない、ブラックリストもホワイトリストと変わらない)をされる方を時々見かけます。こういう考えで作ったアプリケーションで利益を得るのは攻撃者とセキュリティ業者だけです。
アンチプラクティス紹介
国際セキュリティ標準やコンピューターサイエンティストが考えるセキュアプログラミング/セキュアコーディングとは似ても似つかないアンチプラクティスを「セキュアコーディング」としている場合もあります。原理・原則から異なる別のモノで、このスライドの通りだとセキュアプログラミング/セキュアコーディングのアンチプラクティスを実践することになります。
https://www.slideshare.net/ockeghem/php-conference-2017
”筋金入りのブラックリスト好き”のようなので、こういうブラックリスト型の”ダメなモノだけ廃除する”のがセキュアだ、という説明になってしまうのだと思われます。
セキュアプログラミング/セキュアコーディングでは「ダメな物を廃除する」というブラックリスト思考で対策しません。「正しく動作することを保証する」とするホワイトリスト型の思考で対策します。
セキュリティ対策の基本中の基本は”ホワイトリスト型対策”です。ブラックリスト型の対策は原理的に脆弱だからです。ブラックリスト型のセキュリティ対策だけに頼る対策は基本的に全てアンチプラクティスです。
Leave a Comment