(Last Updated On: 2021年6月17日)

JSONPathはCSSのセレクタやXPathのクエリのような形でJSON形式のデータを選択/クエリする仕様です。最近のRDBMSはJSONPathクエリをサポートしているので、SQLインジェクション対策の一種として必要となる場合もあります。

JSONPathの説明はしないので仕様などはオンラインの評価環境で確認してください。

https://jsonpath.com/

JSONPathクエリは上記のような”意味を持つ文字”を使ってクエリを実行します。インジェクション攻撃は一文字でも意味がある文字があると攻撃される、と思って構わないです。JSONPathクエリもインジェクション攻撃が可能です。

典型的なJSONPathインジェクション

典型的なJSONPathインジェクションはSQLインジェクションやXPathインジェクションと同じく、OR条件を利用した検索範囲制限の無効化です。

先ほど紹介したオンラインJSONPathクエリ実行サービスの検索表現の仕様がよく分らなかったのでPostgreSQL 12のJSONPathを利用してJSONPathインジェクションを紹介します。

参考:

PostgreSQLはjsonb_path_query()などでJSONPathクエリを実行できます。実用的な用途ではJSONデータはデータベースの中に保存されているデータですが、ここではJSONデータをテキストとして渡しクエリします。

JSONデータにユーザー名とパスワードが保存されていて、それをクエリしてみます。普通はこのような仕様にはしないですが分かり易さを重視した例です。

yohgaki@[local]:5432 ~=# select jsonb_path_query('
{
  "users": [
        {
            "username": "Foo",
            "password": "Some Pass"
        },
        {
            "username": "Bar",
            "password": "Other Pass"
        }
    ]
}',
'$.users[*] ? (@.username == "Foo" && @.password == "Some Pass")' );
               jsonb_path_query               
----------------------------------------------
 {"password": "Some Pass", "username": "Foo"}
(1 行)

時間: 0.563 ミリ秒

username, passwordが一致したデータだけ抽出されました。

username, passwordはユーザー入力です。passwordに

do not care" || 1 == 1

が入力されたとします。その時のクエリは以下のようになります。

$.users[*] ? (@.username == "Foo" && @.password == "do not care" || 1 == 1)

このクエリが実行されるとSQLインジェクションやXPathインジェクションと同じように不正なクエリが実行されます。

yohgaki@[local]:5432 ~=# select jsonb_path_query('
{
  "users": [
        {
            "username": "Foo",
            "password": "Some Pass"
        },
        {
            "username": "Bar",
            "password": "Other Pass"
        }
    ]
}',
'$.users[*] ? (@.username == "Foo" && @.password == "do not care" || 1 == 1)' );
               jsonb_path_query                
-----------------------------------------------
 {"password": "Some Pass", "username": "Foo"}
 {"password": "Other Pass", "username": "Bar"}
(2 行)

時間: 0.597 ミリ秒

全てのJSONデータが取得されてしまいました。

この例では理解り易くユーザーデータベースがJSONである事を想定しました。現実ではあまりあり得ないですが、この例のようにJSONPathクエリでも”エスケープ処理”をしないと様々なインジェクション攻撃が可能になります。ここで紹介したOR条件による全件取得するインジェクション攻撃はほんの一例です。

JSONPathインジェクション対策

JSONPathインジェクション対策は単純です。

  • 文字列はエスケープする
  • 数値/識別子はバリデーションする

だけです。JSON文字列のエスケープはJavaScriptと同じく”\”でエスケープします。数値はエスケープできないので整数/浮動小数点数の形式に合わせてバリデーションします。識別子はエスケープしても意味がないのでバリデーションします。識別子のバリデーションには通常、SQL識別子と同じく、厳格なホワイトリストによるバリデーションが必要です。

SQLコンテクストにJSONPathがある場合、JSONPath表現はSQLパラメーター文字列になります。プリペアードクエリを使うかエスケープが必要になります。プリペアードクエリはJSONPathクエリ等を利用したインジェクションに対して何の役にも立たない点に注意してください。

(XPathインジェクションや正規表現インジェクションも同様に役立なない。SQLデータベースは多機能なのでサポートする全てのコンテクストの安全性を確保しないとインジェクション攻撃に対応できない)

PostgreSQLのようにJSONデータに対してデータを設定するAPIを持つシステムの場合、不正なクエリの他に不正なデータの挿入にも注意が必要です。

まとめ

1文字でも意味のある文字があると大抵の場合でインジェクション攻撃が可能になります。

JSONPathインジェクションが重大なセキュリティ問題となるケースはあまり多くないと思います。しかし、ソフトウェアのセキュリティは全ての動作が確実に正しく動作することを保証することにより確保できます。

不正なJSONPath表現を許してしまうアプリケーションは脆弱なアプリケーションである、と言えます。

JSONPathはPostgreSQL以外の今時のRDBMSでも大抵サポートされています。各言語用にJSONPathクエリ用のライブラリがあります。DBには機密データが含まれることが多いので利用する際には注意が必要です。

未だに「SQLデータベースセキュリティ対策にはプリペアードクエリ/プレイスホルダさえ使っていればOK」と誤解している開発者が稀にいます。JSONPathインジェクションやXPathインジェクション/正規表現インジェクションは勿論、SQL識別子(テーブル/カラム名など)を使ったSQLインジェクション攻撃に対して「プリペアードクエリ/プレイスホルダは何の役にも立ちません」。

SQLデータベースのセキュリティ”も”これだけすればOKという対策はありません。出力セキュリティ対策一つに絞るとするとセキュアコーディング原則7の「他のシステムに送信するデータを無害化」とするのが妥当です。

JSONPathインジェクションでなく、JSONデータを作成する場合にも適切なエスケープ処理をしないと、不正なJSONデータの挿入や不正なJavaScript, HTMLの挿入なども可能になります。全てのデータ作成、データクエリには厳格なデータバリデーションをフェイルセーフ対策としての無害化(出力対策の三原則)が必要になります。

投稿者: yohgaki