ソフトウェアのセキュリティを向上させよう!と日々奮闘している方も多いと思います。しかし現在、ソフトウェアセキュリティは十分、と言える情報からは程遠い状態です。
なにが足りないのか?なぜ不十分になるのか?基本的な部分の見落としに原因があります。
ソフトウェアセキュリティに足りない何かとは?
ソフトウェアはコード、つまりプログラムで作られています。コードのセキュリティについて十分に注意している開発者は多いと思います。このブログにも安全なコードを書くためのTipsに対するアクセス数は多いです。
ソフトウェアはコードだけでは意味がありません。ソフトウェアを動作させるための入力データが必要です。できるだけ精確な円周率を求めるといった特殊なプログラムを除けば、実用的なプログラムのほぼ全てが何らかの入力データを必要としています
多くのプロジェクト/開発者はコードのセキュリティには注意を払っていますが、データのセキュリティはどうでしょうか?特に入力データのセキュリティは大丈夫でしょうか?
セキュアなソフトウェアには、コードの安全性とデータの安全、これら両方が必要です。
データのセキュリティにも気をつけているよ!
「何を言っているんだ。データのセキュリティには既に十分気をつけている!」と思った方も少なくないと思います。
ソフトウェアに対する攻撃のほとんどは”データ”を使って攻撃するタイプです。なのでデータに十分を気を使っている方も多いでしょう。
しかし、そのデータは”何データ?”でしょうか?ほとんど場合、注意を払っているデータは”出力データ”ではないでしょうか?
出力データに気を使っているコードはこんなコード
例えば、出力データに気を使っているコードとはこんなコードではないでしょうか?
<?php $status = pg_prepare('my_select', 'SELECT * FROM my_table WHERE id = $1'); $result = pg_execute('my_select', [$_GET['id']); ?>
プリペアードクエリのプレイスホルダを使えば、コード(SQL文)とデータ(SQLクエリパラメーター)を分離できます。仕組み的にコードとデータが分離され、RDDBMSへの出力データに何が入っていてもSQL文として実行できないので安全です。(実はこれは嘘です、念のため)
<?php function h($str) { return htmlspecialchars($str, ENT_QUOTES, 'UTF-8'); } ?> <p>This is safe ID output: <?php echo h($_GET['id']); ?> </p>
このコードは出力データをHTMLエスケープした後にブラウザに出力しているので、出力データがJavaScriptなどと解釈されてJavaScriptコードとして実行されることがないので安全です。(実はこれも嘘です、念のため)
SQL文やJavaScriptコードとして実行できないので安全です!これは本当に”安全”と言えるのでしょうか?
データの中身を考慮しない安全は”安全”なのか?
データを使うとき(≒ 出力するとき)の安全性については多くが語られています。どういうコードを書けば出力するときに安全になるのか?解説した書籍やWebページも多くあります。
しかし、出力データが”安全”であるかどうか?は”余計なコードが実行されない”ことだけ、では評価できません。
先ほどのSQLクエリに利用したIDはRDBMSのINT8型の整数だとする場合、IDに文字列の場合はデータベースクエリエラーになります。このクエリは参照クエリなので「検索エラーになりました」とエラーメッセージを表示するだけで済むかも知れません。
更新クエリや挿入クエリの場合にクエリエラーが発生するとどうでしょうか?メッセージキュー利用した非同期データベース処理の場合、クエリ実行時にエラーが発生するとその後のエラー処理が大変です。既にメールやブラウザへの出力は完了しているかも知れません。「処理を受け付けましたが、エラーをなり失敗しました」と改めてユーザーに通知しなければなりません。
HTML出力でも同じ問題があります。htmlspecialchars()でHTMLエスケープすれば余計なJavaScriptなどを出力されることはありません。しかし、htmlspecialchars()は文字エンコーディングバリデーションを行い、バリデーションエラーの場合は空文字列を返します。これは不正な文字エンコーディングデータをブラウザに出力してしまうと、どうしても正しく処理することが不可能だからです。
壊れた文字エンコーディングデータでも保存してしまうモノは少なくありません。代表例はファイルです。ファイルは文字データであるか、バイナリであるか全く関知しません。何でも保存します。ファイルのように何でも保存できるストレージが保存先/キャッシュに使われている場合、「既にMVCモデルもMでデータが保存できているから出力先に出力しても大丈夫」とする前提情報は成り立ちません。
攻撃用コードさえ実行されなければ”安全”なのか?
どういう状態が”安全”なのか?は人の感覚によって異なります。こういう場合はITセキュリティ標準の考え方に従うのが誠実なエンジニアと言えるでしょう。
ISO 27000ではITセキュリティの3大要素として
- 機密性(Confidentiality)
- 完全性(Integrity)
- 可用性(Availability)
追加のセキュリティ要素として
- 信頼性(Reliability)
- 真性性(Authenticity)
- 否認防止(Non-repudiation、 Accountability)
が必要であるとしています。これを見ただけでも”攻撃コードが実行されない”だけ、では不十分であることが分かります。
※ ISO 27000:2014からの追加の要素ですが、最初に定義された国際ITセキュリティ標準のISO 13335では策定当初からセキュリティ要素として挙げられています。
SQL実行時にデータベースエラーが起きたり、HTML出力時に空っぽの部分ができたりするアプリケーションが”信頼性に足る”とは到底言えません。
”安全”なプログラムの作り方
データを出力する際に”コードが実行できない”だけでは”安全なプログラム”ではないです。”データを安全にする”必要があります。
データを安全にするとは、プログラムが期待する(プログラムの仕様に合った)データであることを保証することです。
そもそも、プログラムは正しい”コード”と”データ”があって初めて正しく動作するモノです。正しい”入力データ”は勝手に送られてくるモノではありません。ソフトウェアの信頼境界線上で、プログラムの仕様に合った正しい入力データであることを保証しなければなりません。(セキュアコーディングの第一原則)
その上で出力データが出力先に対して無害であることを保証して出力します。
世の中のほとんどのWebアプリが、安全なWebアプリ、とは言えない
「世の中のほとんどのWebアプリが、安全なWebアプリ、とは言えない」この原因は”データの安全性”という視点が抜け落ちていることが原因ではないでしょうか?
コードが完璧であっても、データがダメならプログラムは正しく動作できません。
※ 入力データ検証がないプログラムは”完璧”とは言えませんが、ロジックと出力コードが完璧であっても、出鱈目なデータではプログラムは原理的に正しく動作できません。
プログラム/システムの仕様にあった入力データであることを保証した上で処理を開始しないと、ありとあらゆる不具合が発生する可能性があります。そしてこれらの不具合は全てセキュリティ問題と言えます。いつ、どこで、不具合が発生するのか分からないプログラムの信頼性は低いからです。
信頼性が低いだけでなく「データの安全性」を考慮していないプログラムの多くが不正なコードを実行するインジェクションにかなり脆弱です。ちょっとした変更やミスでインジェクション攻撃が可能になるケースが後を絶ちません。
安全なプログラムではデータのセキュリティを考慮する
”コードのセキュリティ”に注意することと同等以上に”データのセキュリティ”に注意が必要です。
残念ながら、多くのアプリケーションは”データのセキュリティ”を保証するには不適切な構造を持ったモノばかりです。
しかし、”データのセキュリティ”は難しくありません。ソフトウェアの信頼境界上で入力バリデーションを行い、出力の無害化を行うだけで、9割程度の”データのセキュリティ”が確保できます。
入力バリデーションはセキュリティ対策ではないとか、出力対策はコードが実行できないことだけを保証すればよい、といった考え方では「安全なプログラム」は作れないのです。考え方を少し変えるだけで、より堅牢で安全性の高いプログラム設計・コーディングが可能になります。
”不正なコード実行ができないようにするセキュリティ対策”を重視している場合でも、”データのセキュリティ”を見落として危険なコードが安全だと勘違いしているケースが少なくありません。”データのセキュリティ”、重要です。