なぜ強い型を持つ言語はセキュリティ的に強いのか? – データ型のバリデーションと構造化されたセキュリティ対策

(Last Updated On: 2018年10月12日)

強いデータ型を持つ言語は弱いデータ型の言語(PHPやJavaScriptなど)に比べよりセキュアなのか?「なぜよりセキュアなのか?」簡単に解説します。

結論から書くと「強いデータ型」はそれだけでは「強いセキュリティ構造」を作るモノではなく、少しだけ安全なコードを書く手助けくらいの効果しか期待できません。安全かつ正しく動作するプログラムを最小のコストで作りたい場合、契約プログラミングを行うのが効果的です。

強いデータ型の言語がよりセキュアである理由

強いデータ型を持つ言語がよりセキュアである理由は単純です。

  • 言語が特定の変数のデータ型が宣言したデータ型であることを保証

するからです。これは弱いデータ型を持つPHPのコードサンプルでも、SQLインジェクションやJavaScriptインジェクションで簡単に証明できます。

<?php
// PHPは弱いデータ型の言語だが、(int)キャストによりint型であることを保証

$sql = "SELECT * FROM mytable WHERE id = ". (int)$row_id;
execute_sql($sql);

$the_row = (int)$row_id;
echo '<div id="'. $the_row . '">レコードID: '. $the_row .'</div>';
?>

弱いデータ型を持つ言語でも、数値型のデータ型を持つ変数であることを保証すれば、強いデータ型を持つ言語と同様に”テキストデータ出力先”の安全性を保証1できます。

 

データ型を保証しても安全ではない

データ型を保証しても安全ではない(つまり、強いデータ型を持つ言語が自動的に安全性を保証する訳ではない)こともインジェクション脆弱性で簡単に証明できます。

<?php
// $limit行数までのレコードを取得
$sql = "SELECT * FROM mytable LIMIT ". (int)$limit;

// ユーザーがスーパーユーザー($uid == 0)であるかチェック
if (!(int)$uid) {
  // スーパーユーザーだけ実行可能な処理
} else {
  throw new Exception('ユーパーユーザーのみ実行可能;
}
?>

mytableに多数のレコードが保存されていて、$limitが999999999でも問題ないシステムは少ないでしょう。普通は$limitを999999999にできてしまうとDoS攻撃に脆弱になってしまいます。

$uidが何らかの不具合(バグ)でテキスト(例えば、SQLite3はIDを除くカラムは全てテキスト)である場合、スーパーユーザー用のコードが実行できてしまいます。

 

データ型の保証とは何か?

「データ型の保証」とは「弱いデータ形式バリデーション」です。

  • int型の場合、整数形式であることを保証
  • float型の場合、浮動小数点形式であることを保証
  • (ここでは取り上げませんが、string型の場合、文字列(PHPの場合バイナリ含む)であることを保証)

これらしか保証しない、つまり範囲や値の妥当性は保証しません。

”数値形式”の「データ型の保証」が「弱いデータ形式バリデーション」であっても、プログラムの安全性を向上させること先の例からも明らかです。

そして「データ型の保証」が「強いデータ形式バリデーション」であれば、より強固なプログラムの安全性を提供できることも明らかです。

参考:

 

なぜセキュアプログラミングの第一位の対策が「入力バリデーション」なのか?

セキュアプログラミングの第一位の対策が「入力バリデーション」である理由は単純です。

  • プログラムのセキュリティを「構造的」に向上させるには、まず「入力バリデーション」を行うべき

だからです。

図:ソフトウェアセキュリティの基本構造

どのようなソフトウェアでも基本構造は同じです。「入力」→「処理」→「出力」の基本構造を持っています。

並列性が必要とされるソフトウェアアーキテクチャを構造化する際に「副作用」は嫌われます。並列性がないソフトウェアでも副作用は問題になります。構造化セキュリティに於ても副作用が問題になりますが、話を簡単にするためここでは取り上げません。

図:ソフトウェアのセキュリティ構造はより細かい単位のプログラムの入れ子構造

ソフトウェアは「入力」→「処理」→「出力」の基本構造を持っています。ソフトウェアを安全に動作させるには、以下の3つの条件が必要です。

  • 入力が正しいこと
  • 処理が正しいこと
  • 出力が正しいこと

整数型/浮動小数点型のデータを受け入れるプログラムに文字列型のデータを渡しても正しく動作しないことは明らかです。従って、”データ型を保証する”ことは”プログラムが正しく動作することを保証”する為には”必須の前提条件”です。

前提条件が守られていない場合に、プログラムの

  • 処理が正しいこと
  • 出力が正しいこと

が保証できないことは論理的に明白です。

プログラム動作の正しさが担保できていないことがセキュリティ脆弱性の大きな一因となっています。一般に「ソフトウェアのセキュリティ対策」というと「出力が正しいこと」を保証するような対策が第一番目に挙げられていることが多いです。

しかし、「ソフトウェアの動作が正しいこと」を保証する大前提は「入力が正しいこと」が保証されていることが必要です。この為、国際標準を含むセキュリティ標準/ガイドラインは「入力の正しさ」を保証する対策を第一のセキュリティ対策として重要視しているのです。

「データ型の保証」は「弱いデータ形式バリデーション」でしかありませんが、それでも無い場合と比べればより良い状態である、と言えます。

 

構造化セキュリティに必須の信頼境界線

セキュリティ対策を構造化したプログラムでは「入力」「出力」の「境界」を明確に識別する必要があります。ソフトウェアセキュリティにおける「信頼境界線」とは「入力/出力の境界」です。

以下の図では赤枠が信頼境界線になります。

信頼境界線はとても重要なセキュリティ概念の1つですが、残念ながら誤解されていることが多いです。このエントリの論点は「非常に弱いデータ形式のバリデーションでさえ、セキュリティ対策として有用である」ことを解説することです。信頼境界線については別のエントリで解説しますが「構造化されたセキュリティ対策」を持つソフトウェア/システムには必須の概念です。少なくとも「入力/出力の境界」であること、「セキュリティ対策を実施する場所」であることは覚えておきましょう。

 

まとめ

論理的に構造化したセキュリティ対策には

  1.  入力
  2.  処理
  3.  出力

これら3つの箇所でセキュリティ対策が必要です。

データ型の保証(強いデータ型の言語)はセキュリティ対策として有用ですが、十分ではないです。特にテキスト処理が中心のWebアプリケーションではデータ型の保証では全く不十分です。Webアプリケーションセキュリティで最も問題となる箇所のほとんどが「文字列型」の「データ形式が保証」されていないことが原因だからです。

強いデータ型を持つ言語で開発したシステムでも数多くのセキュリティ問題が存在します。現在、稼働している多くのソフトウェアが「構造を持たないセキュリティ対策」を行っていて、最も問題となる文字列型データのバリデーションを行っていないので当然でしょう。

参考:

皆さんの周囲にあるソフトウェアは十分に入力値の正しさを保証しているでしょうか?「十分に正しさを保証している」というには程遠い状態であるのが現状ではないでしょうか?

「構造化したセキュリティ対策」の第一条件である”入力の検証/バリデーション”が不十分極まりない状況にも関わらず

「従来からバリデーションはセキュリティ対策としてとらえられてきて『世界の常識』となっているが、実はそれはおかしいのではないか?」という問題提起をしているのです。なので、(入力バリデーションが)「世界の常識だろ」と言われても、それでは反論になっていません。

考えてるセキュリティ専門家までいます。しかし、「セキュリティが問題」となっているソフトウェアで「常識として入力データの検証/バリデーション」を十分おこっているモノは皆無と言ってよいでしょう。このような状況では「いつまで経ってもセキュアなソフトウェアが作られない(作れない)」のは当然でしょう。

 

少し脱線気味でしたが、まとめると以下になります。

  • ”強いデータ型を持つ言語”は”弱いデータ型を持つ言語”よりは安全なソフトウェアを作りやすい(しかし無いよりはマシな程度に過ぎない)
  • 安全なソフトウェアを作る為に”強いデータ型を持つ言語”は必須ではない(数値型の保証だけでは不十分である)
  • ”言語が提供する強いデータ型”はセキュリティ対策の構造ではなく、言語の仕様であり、プログラムに”構造化されたセキュリティ対策”を導入するモノではない(セキュリティ対策として不十分でも当然である)
  • ”安全なソフトウェアを作る為”の必要条件は”データの形式/妥当性を厳密かつ完全に保証”することにある(セキュリティ対策に必要とされるモノはデータ型の保証ではなく、構造化されたセキュリティ対策である)
  • (構造化されたセキュリティ対策の図から自明ですが、出力の妥当性を厳密かつ完全に保証することが2番目に重要です。これも契約プログラミングで考えると普通は自動的にこうなります)

より安全なコードを書くなら”契約プログラミング”(DbC)を行う方が良いです。正しく契約プログラミングを行なえば、自動的に「構造化したセキュリティ対策」を導入でき、確実により安全なソフトウェアを開発できます。契約プログラミングを導入の有無に関わらず、信頼境界線を強く意識したプログラミングを行わないと脆弱になるので注意してください。2

 

 


  1. 少なくとも”不正なコード”を挿入されるタイプのインジェクション攻撃が行えないことは保証できる。 
  2. セキュリティ標準/ガイドラインでは信頼境界線でのセキュリティ対策を要求しています。「外部からの入力を信頼しない」つまり「入力をバリデーションする」、「外部への出力は全て無害化する」つまり「エスケープしたり、エスケープが必要ないAPIを使ったり、バリデーションする」はどのようなプログラミング設計でも必須です。 

投稿者: yohgaki