はじめに
XMLをデータ保存に用いるケースが増えています。本記事では、XPathインジェクション攻撃、その防止方法、チートシートの例、そしてブラインドXPathインジェクションについて解説します。
XMLデータは、SQLに似た言語であるSQLのようなXPathを用いて問合せることができます。XPathはXML文書中の特定要素を抽出するための問い合わせ言語です。SQLとは異なり、データベースのテーブルやカラムに対するアクセス制御がないため、XML文書の任意の部分を参照可能です。
XPathインジェクション攻撃は、悪意ある入力によりXML文書の構造や内容といった機密データに無断でアクセスしたり漏洩させたりする攻撃です。これは、利用者の入力をもとにクエリ文字列が生成される際に起こります。SQLインジェクション攻撃では、SQLの特性上利用できる手法が限られるのに対し、XPathインジェクション攻撃はより柔軟で多彩な手法が可能です。
攻撃シナリオの例
以下の状況を考えてください。
顧客の資格情報などのデータは、XMLを利用してあるサイトに保存されています。以下はそのXMLファイルの例です。
<?xml version="1.0" encoding="utf-8"?>
<Employees>
<Representative ID="1">
<Name>Sam</Name>
<UserName>Johns</UserName>
<Password>This is Secret</Password>
</Employee> <Representative ID="2">
<Name>Peter</Name>
<UserName>Pan</UserName>
<Password>Ssssshh</Password>
</Employee>
</Employees>
顧客はサイトにアクセスするため、ユーザ名とパスワードを入力します。その後、以下のXPathクエリが生成され、データが問合せられます。
"//Employee[UserName/text()='" and Request("UserName") and "' And Password/text()='" and Request("Password") and "']"
もし顧客名に悪意あるペイロードが挿入されると、以下のXPathクエリが生成されます:
Username : test' または 1=1 または 'a'='a
Secret word : test
XPath Query: //Employee[UserName/text()='test' または 1=1 または 'a'='a' かつ Password/text()='test']
これは次と同じです:
//Employee[(UserName/text()='test' または 1=1) または ('a'='a' かつ Password/text()='test')]
このように、クエリの最初の部分だけが有効となり、残りは無視されます。結果として、パスワードは意味をなさず、攻撃者はサイトへ自由にアクセスできてしまいます。
ブーリアナイズやXMLクロール、いわゆるブラインドXPathインジェクションを用いることで、上記の方法に加えXML文書全体を取得することも可能です。サーバは、攻撃者が正しくログインできた場合にTrue、ログインに失敗した場合にFalseを返します。さらに、XML内のデータを確認するために様々なXPathのサブ関数が利用でき、ノード数は固定されません。以下はいくつかの例です:
この関数はノードの数を返します:
count(//client/child::node()
パスワードノードが6文字かどうかを確認します
string-length(//user[position()=1]/child::node()[position()=3])=6
攻撃者は「ブーリアナイズ」手法を用いて、指定されたXPath式がTrueかFalseかを判断できます。攻撃者があるウェブアプリのアカウントに接近していると仮定した場合、正しいログイン試行は「Valid」を、失敗した試行は「Bogus」を返します。分解された文字や数字は、データの一部に焦点を合わせるために利用されます。攻撃者は、文字列内の各文字を一つずつ確認することで、その文字列全体を割り出すことが可能です。
コード:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<user>
<login>admin</login>
<password>test</password>
<realname>SuperUser</realname>
</user>
<user>
<login>rezos</login>
<password>rezos123</password>
<realname>Simple User</realname>
</user>
</data>
攻撃者は以下の手法を用いてXML文書の構造を調査することができます:
count(expression)
count(//client/child::node()
このクエリはノードの数を返します(本例では2)。
stringlength(string)
string-length(//user[position()=1]/child::node()[position()=2])=6
このクエリを用いると、最初のノード(クライアント『administrator』)のパスワードが6文字で構成されているかどうかが確認できます。
substring(string, number, number)
substring((//user[position()=1]/child::node()[position()=2]),1,1)="a"
このクエリは、クライアント(『administrator』)のパスワードの最初の文字が「a」であるかどうかをTrueまたはFalseで確認します。
ログイン構造は次のようになります:
C#:
String FindUser;
FindUser = "//user[login/text()='" + Request("Username") + "' かつ パスワード/text()='" + Request("Password") + "']";
その後、攻撃者は次のコードを挿入します:
ユーザ名: ' または substring((//user[position()=1]/child::node()[position()=2]),1,1)="a" または ''='
XPathの構文は通常のSQLインジェクション攻撃に似ていますが、この言語では残りの部分をコメントアウトできないため、この制約を回避するために攻撃者はOR式を用いて全ての文を無効化しようと試みます。しかし、その結果、攻撃が成立しない場合もあります。
ブーリアナイズによって生成されるクエリの数は、僅かなXMLファイル内でも非常に多くなる可能性があり(数千、数万、さらにそれ以上)、この攻撃は手作業では実施困難です。数個の基本的なXPath関数だけで、攻撃者は文書の構造を再構築し、データを抽出するスクリプトを迅速に作成できます。
XPathインジェクション対策は、アプリ開発時に入力フィールドの内容を分離し、利用者が入力する文字がXPathクエリとして解釈されないように実装すべきです。
また、利用者が入力すべきでない文字を事前に除外することで、将来のゼロデイ攻撃に対する安全性を高めることが可能です。
XPathの関数名も一切入力できないよう制限すべきです。クライアント側ではスクリプトで、サーバ側ではファイアウォールのルールなどにより制御します。
準備されたクエリの利用が推奨されます。実行時に利用者の入力を直接用いるのではなく、あらかじめ準備されたクエリに入力を割り当てることで、実行時の不正なコード実行を防げます。
シングルクォート(')などの利用者入力は適切にサニタイズし、必要に応じて "and apos;" を使用してください。検証はクライアント側とサーバ側の両方で実施する必要があります。
準備されたクエリ(SQLにおけるPrepared Statementsのようなもの)は、クエリを事前にコンパイルし、利用者入力をパラメータとして渡すため、実行時に不正なコードが実行されるのを防ぎます。
エラー発生時には、攻撃者に情報が漏れない適切なエラーページを表示すべきです。
コード作成時、XPathインジェクションの影響を受けないよう、貴社のチームで十分に確認してください。安全なコード開発プロセスの一環として、XPathインジェクション対策のテストスクリプトを作成することが望まれます。
監査人は、アプリ開発の手法や運用方法を監査し、PCI DSS Requirement 6.5.1およびインジェクション脆弱性対策に適合しているかを確認します。
WallarmのCloud WAFは、このような攻撃から貴社を守るのに役立ちます。従来のクラウドWAFとは異なり、Wallarmはアプリを守り、またAPIセキュリティプラットフォームがAPIを守るため、手動での調整や継続的なメンテナンスが不要です。拡張性があり、効果的です。
最新情報を購読