一覧SD-10-4 · 解釈系への入力

NoSQL・LDAPインジェクション対策

検索式そのものを入力として受け取らず、アプリが定めた条件へ値だけを入れる方法を説明します。

説明

検索APIへJSONを渡す場合でも、値の中のオブジェクトが演算子として扱われることがあります。LDAPでは文字列の特殊文字が検索条件の構造を変えます。それぞれの構文に合わせた対処が必要です。

二人の固定レコードに対し、文書検索とLDAP検索を比較します。mongomockとldap3の固定ディレクトリを使い、型検証とフィルター値のエスケープによって返る対象がどう変わるかを調べます。

検索式そのものを入力として受け取らず、アプリが定めた条件へ値だけを入れる方法を説明します。

1同じ二人のレコードを準備する

nameまたはuidを使ってaliceを検索します。

from sd10 import * lab = Search() print(lab.query文書のnameを検索する("alice")) print(lab.directoryLDAPのuidフィルターを評価する("alice"))

どちらもaliceだけです。以後も同じ二人の保存資料を使い、検索に渡す値を変えます。

2文字列の代わりに演算子を渡す

文書検索のnameへ、文字列ではなく$neを持つ辞書を渡します。

value = {"$ne":None}不一致の演算子を値の位置へ渡す print({"name":value}) print(lab.query(value))

nameがnullでないという条件となり、aliceとbobが返ります。外側のフィールド名を固定しただけでは、内側へ渡した演算子までデータ扱いになるとは限りません。

3許可された型へ限定する

この機能の入力は利用者名の文字列だけです。

print(lab.typed入力型を検査してから検索する(value)) print(lab.typed("alice")) print(lab.typed('{"$ne":null}'))

辞書はtype_rejected、aliceは1件、JSONに見える文字列は0件です。文字列をさらにJSONへ再解釈していません。

4LDAPの星印が構文になる例を見る

uidへ*を直接連結します。

print(lab.directory("*")特殊文字を直接フィルターへ連結する)

(uid=*)はuid属性の存在を調べるフィルターで、二人が返ります。入力文字の星印を名前の一部として探した結果ではありません。

5LDAPの値用エスケープを適用する

同じ星印をescape_filter_charsへ通します。

print(escape_filter_charsLDAPフィルター値の特殊文字を符号化する("*")) print(lab.directory("*",escaped=True符号化した値を同じ検索へ渡す))

星印は\2aとなり、(uid=\2a)は文字としての*を探すため0件です。HTMLやSQL用のエスケープではなく、LDAPフィルター値の規則を使います。

6括弧・バックスラッシュ・NULも処理する

フィルター値で意味を持つ文字をまとめて確認します。

print(escape_filter_chars特殊文字の変換結果を確認する("a(b)*"+chr(92)+chr(0))) print(lab.directory("alice",escaped=True通常名の検索が保たれることを確認する))

括弧、星印、バックスラッシュ、NULが16進のescapeになり、通常のaliceはそのまま検索できます。DNのエスケープとは規則が異なります。

7保存データを変更していないことを確認する

検索条件の変更によって返る対象が広がっても、ここまでの操作は読取りです。

print(list(lab.docs.find元の文書を照合する({}, {"_id":0}).sort("name"))) print(lab.directory("*")固定ディレクトリの全対象を照合する)

元の二人とroleが保たれています。検索結果の漏えいと保存データの改変は別の影響として評価します。

まとめ

JSONの値位置にも演算子が入る可能性を調べます。

入力型を契約で固定し、文字列を再解釈しません。

LDAPフィルターの値には専用のエスケープを使います。

操作履歴と解説を振り返れます。

Python 3.15 / mongomock 4.3.0 / ldap3 2.9.1 MOCK_SYNC

1
from sd10 import * lab = Search() print(lab.query("alice")) print(lab.directory("alice"))
['alice']
('(uid=alice)', ['alice'])
2
value = {"$ne":None} print({"name":value}) print(lab.query(value))
{'name': {'$ne': None}}
['alice', 'bob']
3
print(lab.typed(value)) print(lab.typed("alice")) print(lab.typed('{"$ne":null}'))
type_rejected
['alice']
[]
4
print(lab.directory("*"))
('(uid=*)', ['alice', 'bob'])
5
print(escape_filter_chars("*")) print(lab.directory("*",escaped=True))
\2a
('(uid=\\2a)', [])
6
print(escape_filter_chars("a(b)*"+chr(92)+chr(0))) print(lab.directory("alice",escaped=True))
a\28b\29\2a\5c\00
('(uid=alice)', ['alice'])
7
print(list(lab.docs.find({}, {"_id":0}).sort("name"))) print(lab.directory("*"))
[{'name': 'alice', 'role': 'viewer'}, {'name': 'bob', 'role': 'editor'}]
('(uid=*)', ['alice', 'bob'])

演習完了