Skip to main content

XPathインジェクション攻撃を防ぐ方法

著者

Marcelo Oliveira

blog feature pypi spoof

2023年5月10日

0 分で読めます

Webアプリケーションはさまざまな攻撃に対して脆弱ですが、特にコードインジェクション攻撃の影響を受けやすい傾向があります。その一つであるXPathインジェクションは、ユーザーが入力した情報を使ってXML形式で保存されたデータにアクセスするWebサイトを悪用します。XML形式のデータベースを使用するサイトはすべて、この攻撃に対して脆弱な可能性があります。

XPathは、WebサイトがXMLデータストアを検索するために使用できるクエリ構文です。適切に実行すれば、XPathクエリは要素や属性に含まれるデータを検索する正当な手段です。XML形式のデータを含むデータベースを使用するWebサイトでは、必要なデータを検索するために、ユーザー入力(ユーザー名やパスワードなど)をXPathクエリに組み込みます。

しかし、一般的なSQLインジェクション攻撃など、他のインジェクション攻撃と同様に、悪意ある攻撃者は不正な形式の情報を使ってこの処理を悪用できます。攻撃者はXMLデータの構造を特定し、通常はアクセスできないデータにアクセスできます。この情報を入手した攻撃者は、アクセス権限を昇格させ(「権限昇格」攻撃)、アプリケーションや機密データを侵害したり、その情報やアクセス権を使って組織全体を攻撃したりする可能性があります。

データの削除や破損にとどまらず、データの窃取によってハッカーがアプリケーションコマンドを実行したり、サーバーへのさらなる攻撃の足がかりを得たり、その他の攻撃で組織全体を機能停止に追い込んだりするおそれがあります。こうした攻撃は、アプリケーションの評判を損なったり失墜させたりし、顧客からの企業の信頼を傷つける可能性があります。

幸い、XPathインジェクション攻撃からサイトを保護する方法はあります。この記事では、XMLの脆弱性について解説し、アプリケーションのデータが侵害されないように防ぐ方法を紹介します。

XPathインジェクションの脆弱性を特定して修正する

XPathインジェクションを防ぐ方法を理解するため、まずその仕組みを見てみましょう。そのうえで、この攻撃からアプリやデータを守る最善の方法を学びます。

脆弱な認証アプリを見てみる

ユーザー名とパスワードでログインするアプリケーションを使って説明します。

xpathという名前のフォルダーを作成し、その中にusers_info.xmlという名前のファイルを作成して、以下のXMLデータを入力します。

<users>

  <userinfo>
    <user_id>1</user_id>
    <username>jennifer</username>
    <password>4uyFh6v0</password>
    <account_no>757456</account_no>
  </userinfo>

  <userinfo>
    <user_id>2</user_id>
    <username>kimkimani</username>
    <password>6wreD1678</password>
    <account_no>870965</account_no>
  </userinfo>

  <userinfo>
    <user_id>3</user_id>
    <username>michael</username>
    <password>gf5dG63g</password>
    <account_no>345689</account_no>
  </userinfo>

  <userinfo>
    <user_id>4</user_id>
    <username>robert</username>
    <password>yH8wej3h</password>
    <account_no>096417</account_no>
  </userinfo>

</users>

0xpathフォルダー内にmain.pyという新しいファイルを作成し、次のコードを貼り付けます。

# Import the required modules
from lxml import etree

# Parse the XML data
tree = etree.parse("users_info.xml")

def login(username, password):
	expression = "/users/userinfo[username='" + username + "' and password='" + password + "']"

	results = tree.xpath(expression)

	# if no results, which means login details did not match
	if len(results) < 1:
    		print("Incorrect credentials! Check your username or password.")

	# if login details match
	else:
    	for result in results:
        	print(f'Login successful! username: {result[1].text}, password: {result[2].text}') 
# Get the user-provided input
username = input("Enter your username: ")
password = input("Enter your password: ")

login(username, password)

このコードはXMLデータを読み込み、ユーザーが入力した認証情報に一致するXMLデータを選択するXPathクエリを実行します。一致するデータが見つかると、ユーザーを認証します。一致するデータが見つからない場合、アプリには「認証情報が無効です。ユーザー名またはパスワードを確認してください。」というメッセージが表示されます。

このコードをテストする前に、XMLデータを処理するためのPython lxmlパッケージをインストールします。次のコマンドを実行してください。

pip install lxml

コードをテストするには、login関数を呼び出し、ユーザー名とパスワードを渡します。main.pyファイルの末尾に次のコードスニペットを追加してください。

login(username="kimkimani", password="6wreD1678")

ファイルを実行するには、ターミナルで以下のコマンドを実行します。

python3 main.py

成功メッセージが表示されます。誤った認証情報でログインを試すこともでき、その場合は失敗メッセージが表示されます。

このコードは、ユーザー入力をXPathクエリに結び付けてクエリの構造を変更し、悪意あるコードを挿入できるため、XPathインジェクション攻撃に対して脆弱です。

一般的なXPathクエリは、XMLデータから選択する要素を指定するテキスト文字列です。

上記のコードでは、クエリ"/users/userinfo[username='" + username + "' and password='" + password + "']"によって、ユーザーが入力したユーザー名とパスワードに一致するすべてのuserinfo要素を選択します。

しかし、XPathクエリで使用される特殊文字やキーワードを含むユーザー名とパスワードを入力すると、クエリの構造が変わり、攻撃者がログインシステムを回避できるようになります。たとえば、ユーザー名とパスワードの両方に"' or 1=1"を入力すると、次のXPathクエリが生成されます。

//users/userinfo[username = '' or 1=1' and password = '' or 1=1' ]

このクエリは、userinfo要素のうち、username属性とpassword属性が空、または"1=1"と等しいものをすべて選択します。論理式"1=1"は、username属性とpassword属性の実際の値に関係なく、常にtrueと評価されます。

攻撃者が正しいユーザー名やパスワードなしでアクセスしようとしているとします。有効な認証情報の代わりに、ユーザー名とパスワードの両方として、次のいずれかのXPathインジェクションをログインフォームに入力できます。

  • 'or'1'='1

  • ' or 1=1 or 'a'='a'

  • ' or ''=''

  • ' or '1'='1'

  • 'a' or true() or '

  • 'text' or '1' = '1'

上記の各文字列はXPathクエリのロジックと構造を悪用し、アプリケーションのセキュリティを回避します。これらの入力から生成されるXPathクエリはすべて常にtrueと評価されるためです。また、攻撃者がユーザー名(たとえばkimkimani)を知っている、または推測できる場合、次のクエリを実行できます。

kimkimani' or '1'='1

このクエリをusernameとpasswordとして入力すると、生成されるクエリは、username kimkimaniのuserinfo要素、kimkimaniまたは'1'='1'という名前の任意のuserinfo要素、あるいはusername属性の値とパスワード属性の値がそれぞれ'1'='1'と'1'='1'である任意のuserinfo要素を選択します。すべてのuserinfo要素がこれらの条件を満たすため、ログインシステムは攻撃者のログインを許可します。

さらに、XMLドキュメントの構造によっては、攻撃者はノードの位置に基づくインジェクションを次のように送信できます。

'or position()=4 or'

このクエリは、XMLドキュメントの構造におけるユーザーの位置を基に、攻撃者が認証を回避できるようにします。position()関数は、現在のノードが親ノードに対してどの位置にあるかを返す、XPath組み込み関数です。上記の式はXMLドキュメントの4番目のノードでtrueと評価され、攻撃者のログインを許可します。

XPathインジェクションの脆弱性を防ぐ

安全でないXMLデータを悪用する方法はいくつもありますが、Webサイトやアプリケーションのこうした脆弱性を軽減する方法もあります。いくつか見ていきましょう。

入力をサニタイズする

XPathインジェクション攻撃を防ぐ方法の一つは、ユーザーが入力できる文字をフィルタリングすることです。完全な対策ではありませんが、インジェクションクエリの構成に使われる可能性のある入力をブロックできます。

この方法を実装するには、xpathフォルダー内にregex.pyファイルを作成し、次のコードを貼り付けます。

# Import the required modules
from lxml import etree
import re

# Parse the XML data
tree = etree.parse("users_info.xml")

# Create an XPath evaluator
evaluator = etree.XPathEvaluator(tree, namespaces=None)

# login function
def login(username, password):
    if len(re.findall('[^a-zA-Z0-9]', username)) > 0 or ⏎ len(re.findall('[^a-zA-Z0-9]', password))>0:
        print("Suspected characters detected! Login failed!")
        return False
    else:
        expression = "/users/userinfo[username='" + username + "' and password='" +⏎  password + "']"
        results = tree.xpath(expression)

        # if no results, which means login details did not match
        if len(results) < 1:
            print("Incorrect login details!")

        # if login details match
        else:
            for result in results:
                print("Login successful for user", result[1].text)

login(username="kimkimani", password="6wre1678")

このコードは正規表現(regex)を使い、英数字以外の文字をすべて検出します。そうした文字が検出されると、ターミナルに「ログイン失敗」というメッセージが表示され、後続のコード実行を防ぐために、呼び出し元の関数にFalseを直ちに返します。前のセクションで説明したXPathインジェクションに使われた文字を使って、コードをテストできます。

この方法はデータセキュリティを強化しますが、入力検証で除外できるのは、サニタイズ関数に含めることを覚えていた文字だけです。また、パスワードには特殊文字を含める必要があるため、こうした入力をブロックするのは望ましくありません。

そこで、XPathインジェクションの脆弱性を回避する別の方法を見ていきましょう。

パラメーター化(プリペアド)XPathクエリを使用する

この方法を説明するため、xpathフォルダー内にparams.pyファイルを作成し、次のコードを貼り付けます。

# Import the required modules
from lxml import etree

# Parse the XML data
tree = etree.parse("users_info.xml")

def login(username, password):
    expression = "/users/userinfo[username=$username and password=$password]"

    results = tree.xpath(expression, username=username, password=password)

    # if no results, which means login details did not match
    if len(results) < 1:
        print("Invalid credentials! Check your username or password.")
        return False
    # if login details match
    else:
        for result in results:
            print(f'Login successful! username: {result[1].text}, password:⏎ {result[2].text}')

login(username="kimkimani", password="6wreD1678")

パラメーター化XPathクエリでは、ユーザー入力をXPathクエリに結び付けるのではなく、パラメーターとして渡します。クエリの安全性が高まるだけでなく、柔軟性と再利用性も向上します。ただし、すべてのインジェクションを防げるわけではありません。次の対策を見てみましょう。

プリコンパイル済みXPathクエリを使用する

プリコンパイル済みXPathクエリは、ユーザー入力のデータから構築されません。そのため、インジェクション攻撃を完全に防ぐ唯一の安全な方法です。プリコンパイル済みXPathクエリの効果を確認するため、 compiled.pyという名前のファイルを作成し、次のコードを追加します。

import lxml.etree as et

xml = et.parse("users_info.xml")
find = et.xpath("/users/userinfo[username=$username and password=$password]")
results = find(xml, username="'or'1'='1", password="'or'1'='1")
print(results)

このコードではetree.xpath関数を使ってXPathクエリをコンパイルし、find変数に格納します。クエリを毎回コンパイルし直すことなく、find関数を使ってこのコンパイル済みクエリを複数回実行できます。エスケープすべき文字について心配する必要がないため、この方法が最も効果的です。

XPathインジェクションの脆弱性を発見・回避するためのベストプラクティス

上記の予防策(特にベストプラクティスであるプリコンパイル済みクエリの使用)に加え、XPathインジェクションの脆弱性を発見・回避するために、次のベストプラクティスを実践しましょう。

パッケージとコードの脆弱性をチェックする

Snyk Open Source Advisorなどのツールを使って、使用しているオープンソースパッケージにXPathインジェクションの脆弱性がないかスキャンしましょう。Snyk Advisorは包括的な健全性の概要を提供し、Webアプリのセキュリティに対する安心感を高めます。

同様に、Snyk Codeを使ってソースコードをスキャンし、XPathインジェクションを含む潜在的なセキュリティ脆弱性を特定できます。Snyk Codeなら、コードに潜む脆弱性をすばやく簡単に特定し、修正に取り組めます。

以下のスクリーンショットは、Snyk Codeを使ってプロジェクトフォルダーのソースコードをスキャンした結果を示しています。

Snyk Codeのテスト結果を示すターミナル出力。main.pyで重大度の高いXPathインジェクションの脆弱性が検出されています

ユーザー入力をXPathクエリに結び付けない

ユーザー入力をXPathクエリに直接連結しないでください。これはXPathインジェクションの脆弱性が発生する最も一般的な原因の一つです。代わりに、パラメーター化XPathクエリを使えば、ユーザー入力のプレースホルダーを使用できます。コードの可読性と保守性が高まり、ユーザー入力のサニタイズやXPathインジェクション攻撃の防止も容易になります。

特殊文字に注意する

パラメーター化クエリを使用できない場合は、XPathクエリで使う前に、ユーザー入力に含まれる特殊文字を適切にエスケープしてください。

プリペアドステートメントとバインド変数を実装する

XPathクエリでプリペアドステートメントとバインド変数を使用すると、入力をクエリの一部ではなく変数の値として扱うため、攻撃者による任意のXPathクエリの挿入を防ぐのに役立ちます。

コードを定期的にレビューする

Snyk AdvisorやSnyk Codeなどのツールを使うだけでなく、XPathインジェクションの潜在的な脆弱性がないか、コードを定期的にレビューしてテストすることも重要です。攻撃者に悪用される前に、潜在的な脆弱性を特定して修正できます。手動でのコードレビュー、Snyk Codeのような自動コード解析ツール、ペネトレーションテストを活用して、コードの脆弱性を特定・修正できます。

まとめ

XPathインジェクションは、Webサイトやデータ、組織の評判に深刻な損害を与え、将来的な侵害につながるリスクもあります。しかし、少しの工夫でアプリケーションを保護できます。Snyk AdvisorやSnyk Codeなどのツールを活用し、コード内のXPathインジェクションの脆弱性を発見・回避するベストプラクティスに従い、脆弱性を慎重に修正しましょう。

ユーザー入力を適切にサニタイズし、パラメーター化XPathクエリを使い、特にプリコンパイル済みXPathクエリを使用することで、XPathインジェクション攻撃からアプリケーションを保護できます。プリコンパイル済みクエリはユーザー入力のデータから構築されないため、インジェクション攻撃を完全に防ぐ唯一の安全な方法です。XPathインジェクション攻撃がもたらす影響を考えると、こうした予防策は不可欠であり、決して軽視できません。

Capture the Flagを始めよう

オンデマンドのバーチャル入門ワークショップを見て、Capture the Flagの課題の解き方を学びましょう。