4.Webシステム設計/実装の脆弱性
4.1. 安全でないデシリアライゼーション
安全でないデシリアライゼーションは、Webアプリケーションがシリアライズされたデータ(オブジェクトの状態を保存するために平坦化したデータ形式)を安全でない方法でデシリアライズ(元のオブジェクトに復元)する際に発生する脆弱性のこと。
攻撃者はこのプロセスを悪用し、アプリケーションのロジックを操作したり、リモートでコードを実行したりできる。
この脆弱性の根本的な原因は、アプリケーションが信頼できないソース(ユーザー入力など)からシリアライズされたデータを受け取り、そのデータの完全性や安全性を検証せずにデシリアライズすることにある。
まとめると以下の通り。
- 信頼できないデータのデシリアライズ:
- ユーザーが制御できるデータ(HTTPリクエストのパラメータ、Cookie、隠しフィールド、JSONデータ、ファイルなど)をシリアライズされた形式で受け取り、それをそのままデシリアライズする。
- 攻撃者は、このシリアライズされたデータ内に、悪意のあるオブジェクトやデータ構造を注入できる。
- マジックメソッド/特殊なオブジェクトの悪用:
- 多くのプログラミング言語(PHP, Java, Python, Rubyなど)のデシリアライゼーションメカニズムには、オブジェクトがデシリアライズされる際に自動的に呼び出される「マジックメソッド」や特定のインターフェース、コールバック関数が存在する(PHPの__wakeup()や__destruct()、JavaのreadObject()など)。
- 攻撃者は、これらのマジックメソッドやコールバック関数を悪用し、デシリアライズの過程で、本来意図しないコードの実行、ファイルシステムの操作、データベースへのアクセスなどを引き起こす。
- 既知の脆弱性を持つライブラリ/フレームワークの使用:
- デシリアライゼーションを処理する際に使用されるサードパーティ製のライブラリやフレームワークに、既知の脆弱性が存在する場合、それを悪用される可能性がある。
- 不十分なサニタイズ/バリデーション:
- シリアライズされたデータをデシリアライズする前に、その内容が期待される形式であるか、悪意のあるデータが含まれていないかを適切に検証しない
PHPのシリアライズ化
PHPのシリアライズオブジェクトは以下の形式になる。
0:4:"Data":1:{s:4:"data;a:1:{i:0;s:8:"test";}}
形式は<データの形式>:<データバイト数>:<データの値>
という形になる。
安全でないデシリアライゼーションは外部由来のシリアライズオブジェクトをサーバサイドでデシリアライズされることにある。この脆弱性が存在するとしても悪用する方法を見つける必要がある。
PHPの場合はデシリアライズ時に実行される、unserialize()
やwakeup()
などをを探して、さらに調査する必要がある。これらの関数は悪用するためのGadgetとなる可能性がある。
また通常PHPの場合安全でないデシリアライゼーションを悪用するにはソースコードレベルへのアクセスが必要となる。
4.2. SSTI(サーバーサイドテンプレートインジェクション)
SSTIはサーバサイドのテンプレートエンジンで使用されている構文を攻撃者が挿入可能で、挿入したものがテンプレートエンジンで解釈されてしまう脆弱性。
その最終的な目的は、多くの場合サーバ上でのリモートコード実行(RCE)となる。
発生原因は以下の通り。
- テンプレートエンジンにユーザー入力が直接連結されている
- データとして渡されるのではなく、テンプレート構文の一部として扱われる
- テンプレートエンジンがユーザー入力をサニタイズ(無害化)することなく処理する場合
- テンプレートエンジンが、本来想定されていない危険な関数やオブジェクトへのアクセスを許可している
SSTIの検出方法
SSTIの検出は、主に以下の2つのステップで行う。
- インジェクションポイントの特定とファジング
- 使用されているテンプレートエンジンの特定
インジェクションポイントの特定とファジング
Webサーバが入力された文字列をただの文字列として返すのではなく、何らかの計算結果や内部情報として返す場合、SSTIの脆弱性がある可能性が高い。また、エラーメッセージの中にテンプレートエンジンの情報が含まれている場合も、脆弱性の手がかりとなる。
SSTIを検出できる可能性の高い文字列(特殊文字のシーケンス)は以下の通り。
文字列 | 説明 |
---|---|
${{<%[%'"}}%\ | テンプレート構文の開始文字としてよく使われる記号や、エスケープ文字の組み合わせ。 |
{{5*5}} | 多くのテンプレートエンジンで単純な計算式が評価されるか確認する。結果が25と返れば脆弱。 |
${{5*5}} | ${}もテンプレート構文として使われることがあるため試す。結果が25と返れば脆弱。 |
{{7*'7'}} | 文字列と数値の演算がどのように評価されるか(例:7777777と結合されるか、エラーになるか)。 |
- URLに入れる例: 入力パラメータを変更して脆弱性を確認
http://[IPアドレス]:5000/home/${
http://vulnerable-website.com/?greeting={{data.username}}
- 入力フィールドに入れる例: Webフォームの入力フィールドに上記の特殊文字列や計算式を挿入し、サーバーのレスポンスを確認
{{5*5}}
${{5*5}}
SSTIの検出時には、XSSとの区別が重要となる。{{5*5}}
のような入力に対して、サーバーが25
という計算結果を返す場合はSSTIの可能性がある。もし{{5*5}}
という文字列がそのままHTMLに表示されるだけであれば、それはXSSの可能性を示唆する(スクリプトタグなどを挿入することでXSSになる)。
使用されているテンプレートエンジンの特定
SSTIが検出できたら、次にどのテンプレートエンジンが使用されているかを特定を行う。
- 識別マップ(ディシジョンツリー)の例:
${7*7}
を試す:- もし 49 と評価されるなら、Makoなど、${}構文を使うエンジンである可能性が高い。
- a{*comment*}b (abと評価され、{*comment*}がコメントとして無視される) や ${“z”.join(“ab”)} (azbと評価される) など、さらに特定の構文を試して絞り込む。
{{7*7}}
を試す:- もし 49 と評価されるなら、Jinja2、Twig、Smartyなど、{{}}構文を使うエンジンである可能性が高い。
- さらに
{{7*'7'}}
を試します。- もし 7777777 と評価されるなら、Jinja2やTwigの可能性が高い。
- エラーになる場合は、別のエンジンか、あるいは特殊な設定がされている可能性がある。
PayloadsAllTheThings - Server Side Template Injectionにて各テンプレートエンジンごとの検出ペイロードと悪用ペイロードが網羅的にまとめられている。
SSTIの悪用方法
テンプレートエンジンの種類を特定できれば、そのエンジンに特有の構文やオブジェクトアクセス機能を利用して、RCEを試みれる。
【一般的な悪用アプローチ】
- システムコマンドの実行: テンプレートエンジンが、サーバーのシステムコマンドを実行する関数やオブジェクトにアクセスできる場合
- ファイルシステム操作: テンプレートエンジンが、サーバー上のファイルシステムにアクセスする関数(ファイルの読み書き)を公開している場合
- プログラミング言語ごとの典型的な悪用ペイロードの例:
- Java:
${7*7}
や${{7*7}}
は基本的な検出用。- より複雑なJavaオブジェクトのメソッド呼び出しや、Runtime.getRuntime().exec() などのシステムコマンド実行に繋がるペイロードを試みる。
- JavaScript (Lodashなど):
{{= _.VERSION}}
や${= _.VERSION}
、<%= _.VERSION %>
は Lodashのバージョン情報を表示する検出用。- _.template メソッドがユーザー入力で悪用可能な場合、JSのeval()やchild_processモジュール経由でコード実行を試みる。
- Python (Mako, Jinja2など):
- Mako:
<% import os %>
${os.system('ls')}
や${system('ls')}
- Jinja2 / Twig: Jinja2はサンドボックス化されていることが多いが、サンドボックスをバイパスするペイロードが存在する。例えば、
{{''.__class__.__mro__[1].__subclasses__()[<num>].__init__.__globals__['__builtins__']['__import__']('os').popen('ls').read()}}
のように、オブジェクトチェーンを辿ってシステム関数に到達する。
- Mako:
- PHP (Twig, Smarty):
- Twig:
{{_self.env.getCompiler().getTemplateClass('foo')}}
などでオブジェクトにアクセスし、PHPのsystem()関数などを呼び出せるようにする。 - Smarty:
{{system('ls')}}
や{system('cat index.php')}
のように、直接コマンドを実行できる場合がある。
- Twig:
- Ruby (ERB, Slim):
- ERB:
<%= system('cat /etc/passwd') %>
(Linuxのパスワードファイルを表示)- ``<%= \ls /` %>``` (ルートディレクトリのファイル一覧を表示)
<%= IO.popen('ls /').readlines() %>
(別のプロセスでコマンドを実行し結果を読み込む)
- Slim:
#{ system('ls') }
のように、直接コマンドを実行できる構文がある。
- ERB:
- Java: