(Last Updated On: )

セキュリティを考えると全ての入力データはアプリケーションがバリデーションすべきで、長さ/形式は厳格にバリデーションすべきです。1

厳格なバリデーションは開発者が意識/把握していない各種インジェクション脆弱性にも対応できること、インジェクション攻撃が持たらす被害が致命的であることが、その理由です。

適切なバリデーションは最強のセキュリティ対策の1つ2です。強いデータ型は弱いバリデーションの一種ですが、それでもセキュリティ対策として高い評価を得ています。にも関わらず強いバリデーションである厳格なデータバリデーションはコンピューターサイエンティストのセキュリティ専門家3以外にはあまり評価されていないように感じます。

今回は低レベルのライブラリにもコードインジェクション脆弱性のリスクがあること、その対策としてバリデーションが如何に効果的であるか紹介します。

CVE-2017-14952

これはICU バージョン59.1までのICUライブラリに存在する脆弱性です。ICUは広く利用されている文字エンコーディングとロケール処理ライブラリです。

あまり話題になりませんでしたが、2017年の秋に任意コード実行脆弱性として報告されています。

CVSS Severity (version 3.0):
CVSS v3 Base Score: 9.8 Critical
Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (legend)
Impact Score: 5.9
Exploitability Score: 3.9

一言でいうと「文字データで任意コード実行が可能になる」問題4です。CVSS Base Scoreで9.8なので攻撃に成功した場合のインパクトが非常に大きいことが解ります。

ICUライブラリを利用されている方なら59.1というバージョンは比較的新しいバージョンで、多くのディストリビューションではこれより古いICUを利用していると知っていると思います。例えば、RHELではこの脆弱性は修正しないとアナウンスしています。攻撃が困難であることがその理由だと思われます。

ダブルフリー脆弱性とは?

同じメモリ領域を2回以上解放してしまうプログラムの問題です。

この問題の修正と思われるパッチ

Index: trunk/icu4c/source/i18n/zonemeta.cpp
===================================================================
--- a/trunk/icu4c/source/i18n/zonemeta.cpp
+++ b/trunk/icu4c/source/i18n/zonemeta.cpp
@@ -691,5 +691,4 @@
                     if (U_FAILURE(status)) {
                         delete mzMappings;
-                        deleteOlsonToMetaMappingEntry(entry);
                         uprv_free(entry);
                         break;

メモリを解放する行が削除されていることから、ダブルフリー問題だと解ります。

この脆弱性を利用して任意コードを実行を行うには、メモリマネージャーが管理するポインタ操作と攻撃用マシン語を保存したメモリ領域とそのアドレスが必要になります。5

最近、攻撃者にとって都合が良いCPU脆弱性が見つかりました。誰もが最近よく聞くMeltdownとSpectreです。これらはメモリ内容を不正に読み取る脆弱性で、ダブルフリー問題を攻撃するにはとても都合が良い脆弱性です。攻撃に必要なメモリリーク脆弱性を探さなくても、Meltdown/Spectreが使えれば十分です。6

通常、ダブルフリー問題の攻撃は、攻撃に必要なメモリリーク脆弱性も一緒に見つける必要があり、攻撃が困難であることが多いです。Meltdown/Spectreが利用できるとメモリ内容を自由に盗めるので、攻撃可能になります。

対策:文字エンコーディングのバリデーションの効果

コードインジェクション脆弱性のリスクを軽減するには

  • 文字エンコーディングのバリデーション

が欠かせません。単純に文字エンコーディングの妥当性を確認するだけで、知っていなくても文字エンコーディング処理の脆弱性を利用したインジェクション攻撃からソフトウェアを守れます。

マシン語のコードインジェクション攻撃に対してはどうでしょうか?

UTF-8だけ、ASCIIの英数字だけでもShell Codeは作れます。しかし、文字エンコーディングバリデーションが無ければ、攻撃者はShell Codeだでなくあらゆるマシン語命令を自由自在に実行できてしまいます。わざわざ自由にマシン語の攻撃コードを書けるようにして、攻撃者を助ける必要は一切ありません7

妥当なUTF-8文字列のShell Code(41バイト)

greuff@pluto:/tmp$ hexdump -C out
00000000  31 d2 90 52 68 6e 2f 73  68 68 2f 2f 62 69 d6 89  |1..Rhn/shh//bi..|
00000010  e3 90 90 52 53 d6 89 e1  90 90 52 58 40 40 40 40  |...RS.....RX@@@@|
00000020  40 40 40 40 40 40 40 cd  80                       |@@@@@@@..|
00000029
greuff@pluto:/tmp$ iconv -f UTF-8 -t UTF-16 out && echo valid!
1Rhn/shh//bi4RSRX@@@@@@@@@@@@valid!

x86のASCIIプリンタブル文字のみのShell Code(28バイト)

 aPjjX4jHf5eOf5W0PZZj0X43P[HH

x86_64のASCII英数字のみのShell Code(111バイト)

 jZTYX4UPXk9AHc49149hJG00X5EB00PXHc1149Hcq01q0Hcq41q4Hcy0Hcq0WZhZUXZX5u7141A0hZGQjX5u49j1A4H3y0XWjXHc9H39XTH394c

工夫をすればASCII英数字だけのShell Codeが書けることが分りました。より複雑なコード実行も工夫次第で書けると考えるべきでしょう。

文字エンコーディングのバリデーションは、マシン語のコードインジェクション攻撃対策としては緩和策以上の効果を期待すべきではないです。

しかし、だからと言って文字エンコーディングバリデーションは無意味ではありません。自動的にマシン語を妥当な文字エンコーディング/文字のみの攻撃コードに変換する仕組みが作られない限り、緩和策として有効に機能します。8

文字エンコーディングのバリデーションには重要なセキュリティ上の効果があります。不正な文字エンコーディングだと正しい処理が不可能なので文字エンコーディングバリデーション用のコードが含まれていることが多くあります。

文字エンコーディングバリデーション用のコードがあること自体は良いことのなのですが、アプリレベルでバリデーションしていないと思わぬ誤作動が起きる場合があります。9 10

文字エンコーディングバリデーションはインジェクション対策用としてのみではなく、アプリケーションが正しく動作することを保証する11為には必要不可欠12です。13

対策:データのバリデーションの効果

上記のShell Codeから、文字エンコーディングのバリデーションだけではインジェクション攻撃対策としては不十分で、可能な限り厳格なバリデーションでなければバリデーションの意味がなくなってしまうケース、があることが分かります。

  • データサイズのバリデーション(できる限り小さく)
  • データ形式のバリデーション(できる限り具体的&限定的)

このような発見しづらい(&対応しづらい – 少なくともRHELではICUの更新版配布の予定がない)脆弱性のリスクに対応するには、これら2つバリデーションが必須であることが分かります。つまり、

できる限り厳格な入力バリデーションが必要

ということになります。(ISO27000が求める入力データバリデーションがコレです)

  • 小さいサイズのデータしか必要ないのに、無制限にデータを受領&処理するコード
  • 商品コード形式が決まっているのに、文字種(英数字)であることしか検証しないコード

こういったコードは、開発者が意図していなくても攻撃者の攻撃を助けるコード、となるリスクがあるコードです。

紹介したShell Code作成の解説を見ると判るように、限定された条件であるため色々と工夫が必要であることが分かります。14

制限が多ければ多いほど(バリデーションが厳格であればある程)、ライブラリ等に存在する脆弱性を攻撃するために、より多くの工夫が必要になり、条件が合わず任意コード実行攻撃ができなくなる可能性が高くなります。

データバリデーションとビジネスロジックバリデーション

アプリケーションのソースコード検査ではデータとビジネスロジックのバリデーションがそれぞれ必要な部分で行われているかチェックします。

ここでは詳しい解説を省略しますが、データとビジネスロジックのバリデーションは異るバリデーションで、実施する場所が異なります。

データのバリデーション

  • 入力データのバリデーションができる限り早い段階で行われているか?(データの正規化/デコード直後)
  • 出力データのバリデーションが適切な箇所で行われているか?(エスケープもエスケープが必要ないAPIも使えない場合、バリデーションが必要)

ビジネスロジックのバリデーション

  • データのビジネスロジックに対する整合性チェック(例:予約時間が過去の時間でないか?予約が許可される時間か?予約の権限をもっているか?など)

今あるアプリケーションの現状

データのバリデーションでは

  • データサイズのバリデーション(できる限り小さく)
  • データ形式のバリデーション(できる限り具体的&限定的、文字エンコーディング検証も含む)

を行うべきです。15 しかし、残念ながら、ほとんどのアプリケーションはデータバリデーションは無いか、在っても不適切/不十分な物です。Fail Fastの原則16はエラー処理の一般原則ですが、不正なデータ(そもそもプログラムが正しく処理できないデータ)にこの原則が適用されているアプリケーションはとても少ないです。

まとめ

今回はマシン語のコードをインジェクションして実行させるダブルフリー脆弱性を例に、セキュアコーディングが求める厳格な入力バリデーションの有効性を紹介しました。

「妥当なデータであることを厳格にバリデーションする」とは「ソフトウェアが正しく動作することを保証する」です。そもそもソフトウェアは妥当なデータ(正しく動作できるデータ)でしか、正しく動作しません

ここではマシン語のインジェクション攻撃を例にしましたが、HTML、JavaScript、SQL、Xpath、XML、LDAPなどあらゆるインジェクション攻撃を防止できます。17

出力対策は重要ですが、出力対策(一時的に攻撃できない形に変換)”だけ”では問題の先送りをしているだけです。”一時的に攻撃できない形に変換すること”だけに頼るのは明らかに悪手です。そもそも誤作動を起こすような不正なデータは入力処理の時点で完全に廃除するのがセキュアなコードです。

不正なデータを入力処理で廃除する、と解説すると何も考えず反射的に「できない」と思ってしまう人が居ます。この場合、入力データは3種類しかないことを理解していないか

入力データのバリデーションには「データのバリデーション」と「ビジネスロジックのバリデーン」の二種類があり、分けて処理する必要があることを理解していない、が多いようです。18

大多数のソフトウェアセキュリティ問題はソフトウェアの誤作動19です。セキュアコーディングが求める厳格な入力バリデーションを

  • セキュリティ的には重要ではない、必要ではない

とするのは明らかにセキュアなコード記述を助ける考え方/設計ではありません。攻撃しやすい脆弱なコード作成を支援する考え方/設計であると、この解説から理解できればと願います。

Struts 2がHTTPヘッダーのContent-Lengthで任意コード実行ができてしまった例もあります。妥当な入力でない場合、コンピュータは整数演算でさえ仕組みとして正しく演算できません。文字列データのみでなく全てデータのバリデーションが等しく重要です。

最後に、幾ら頑張って文字データをバリデーションしても、例えばバイナリデータである暗号データ(ユーザーが送信した暗号ファイルの読み込みなど)などがあると、それに攻撃用マシン語を埋め込んで攻撃できてしまう、という可能性は無視できません。高度なセキュリティが必要なアプリケーションの場合、テキスト処理をするプログラムと暗号データ/画像ファイルなどを処理するプログラムは分離する方が良いです。

残念ながらメモリに対するコードインジェクション攻撃を完璧に防止することは、アプリケーションレベルでは難しいです。しかし、RHELのように理解っていて脆弱性を修正していないケースもあります。都合が良いメモリ内容をリークする脆弱性が使えるケースは限られます。このため、”強いバリデーション”は緩和策として十分に機能すると言えるでしょう。


  1.  まだ誰も知らない脆弱性に備える方法 = ホワイトリスト型のセキュリティ対策(バリデーション) 
  2. ITシステムリスクの廃除で最強は「使わない」です。次に物理的に分離(モノのセキュリティ)。次にネットワーク的に分離(ネットワークのセキュリティ)。そしてソフトウェアセキュリティで最強の手法は、誤作動を狙った攻撃を廃除する「厳格なバリデーション」(データを不正なデータと妥当なデータに分離)です。出力の無害化(エスケープやエスケープが必要ないAPI)は攻撃用データを一時的に攻撃できない形に変換しているだけです。セキュリティ対策として、出力対策は重要ですが、出力対策(一時的に攻撃できない形に変換)だけに頼るのは明らかに悪手です。一時的に攻撃できない形に変換しても問題の先送りをしているだけだからです。このためCERT Secure Coding Practiceでは入力バリデーションが第一原則、出力無害化が第七原則になっている考えられます。 
  3. サイバー犯罪の多くは不正な入力によってコンピューターを誤作動させることが目的です。コンピューターサイエンティストはプログラム実行の正しさを証明する為の前提条件を知っています。プログラムの正しく実行するため(=誤作動させないため)には、まず第一に入力データがプログラムにとって妥当(正しく処理可能なデータ)であること保証しなければならないことを知っています。なのでカーネギーメロン大学のCERT Secure Coding Practicesは第一のセキュリティ対策が入力バリデーションです。コンピューターサイエンスなどは関係ない、としている人にこれが理解できていないケースが多いようです。 
  4. 正確には文字データである必要はありません。メモリレイアウトさえ分かれば、画像データや圧縮ファイルのデータなどもで構いません。しかし、このICUの脆弱性を攻撃する場合、普通はICUが管理するUnicode文字データを利用して攻撃するでしょう。 
  5. 攻撃者は予めメモリレイアウトなどを知る必要があり、メモリ内容は外部から容易には分かりません。この為、VCSS Exploitability Score: 3.9 と低いスコアになっています。 
  6. ダブルフリー問題は開発者にとって発見することが比較的難しい問題として知られています。ここではICUのダブルフリー問題を取り上げましたが、C/C++プログラムの多くにダブルフリー問題が見つかっています。要するにダブルフリー問題はよくある問題です。 
  7. 攻撃者の多くがスクリプトキディと呼ばれる、攻撃用コードをそのままコピー&ペーストして実行するレベルのスキルしか持っていないケースは多いです。 
  8. もし普通のマシン語から文字データとして妥当なマシン語へ変換するツールが作られたとしても、機能は限定されてしまいます。ツールが在る場合でも、「厳格なバリデーション」がより重要になるだけ、入力データのバリデーションの重要性がより増すだけ、になります。 
  9. メッセージキューにキューイングした情報が送信先でエラーとなる(完全性、信頼性の問題) 
  10. データを表示しようとした時に表示されない(可用性、信頼性の問題) 
  11. 「アプリケーションが正しく動作することを保証する」がセキュアコーディングの目的です。 
  12. 国際ITセキュリティ標準で定義されるITセキュリティ対策とは機密性、完全性、可用性、信頼性、真正性、否認防止性を持たせる対策です。”不正なコードを実行させない”はセキュリティ対策の一部でしかありません。 
  13. 一部にはアプリケーションプログラマが文字エンコーディングをバリデーションする必要はない、とする誤った認識があります。アプリケーションプログラマが文字エンコーディングをバリデーションしなくて良いのは、フレームワーク/環境が文字エンコーディングの妥当性を保証している場合のみで、このような環境は少数派です。 
  14. 現在ではこの種の任意コード実行でシェルを奪うことは少数派だと思われます。シェルの実行ではなく、不正なプログラムのダウンロードとインストールを自動で行う、といったより複雑な操作を行います。しかし、クライアントへの攻撃と異り、サーバーが攻撃対象の場合は今でもシェルを奪う攻撃は十分にあり得る攻撃だと思われます。 
  15. 2017年版 OWASP TOP 10からバリデーションした上で報告&対応しないアプリケーションは脆弱なアプリケーションとなりました。OWASPのガイドラインでは10年以上前からバリデーションを重要セキュリティ対策として掲載していますが、ほぼ全てのWebアプリが脆弱なデータバリデーションしか行っていません。 
  16. 「失敗するモノはできる限り早く失敗させる原則」正しく処理できないモノをエラーとせず、いつまでも誤作動の原因となる不正なデータを”処理し続ける”ことは、愚かな行為でしかありません。 
  17. 入力データでユーザーが自由に入力できるコメントやタイトルなどが脆弱になっているモノは今時は少なくなってきています。特定形式をもつ入力で「これでインジェクションできたの?」「APIを使っていたので大丈夫かと思っていた」という”うっかり”や”知らなかった”はとても多いです。因みに入力対策と出力対策は独立した対策です。両方共実施することがセキュアなコードの前提条件です。 
  18. プログラミングの大原則、Fail Fast原則により不正な入力はできる限り早く廃除する。この原則を効果的/効率的に実施するには”入力処理でのバリデーション”と”ロジック処理でのバリデーション”に分ける必要がある。 
  19. 不正なデータによる誤作動以前の設計ミス(セキュリティ設計の不在)もかなり多い問題です。「データのバリデーションがない」も設計ミスの1つです。。世の中にある開発フレームワークの多くが設計から脆弱であるとも言えます。 

投稿者: yohgaki