説明
LDAPフィルターインジェクションは、信頼できないデータをRFC 4515に従ってエスケープせずに検索フィルターへ挿入すると発生します。攻撃者はフィルター演算子やワイルドカードを追加して条件を変え、認証を回避したり、アプリケーションが公開する予定のないディレクトリエントリーを取得したりする可能性があります。
想定される影響
- ユーザー検索や認証の条件を回避されるおそれがあります。
- ユーザー、グループ、メールアドレスなど、ディレクトリ内の機密情報が漏えいする可能性があります。
- 広範囲の検索や計算コストの高いフィルターによって、LDAPサーバーの負荷が高まるおそれがあります。実際の影響は、ディレクトリのアクセス制御や検索制限によって異なります。
対処方法
- フィルターの構造と属性名を固定し、信頼できないデータはアサーション値(assertion value)の位置にのみ挿入してください。
- python-ldapでは、各アサーション値を
ldap.filter.escape_filter_charsでエスケープするか、信頼できる固定テンプレートと値のリストを分けてldap.filter.filter_formatに渡してください。第1引数のテンプレートを外部入力から組み立てないでください。 - ldap3では、各アサーション値を
ldap3.utils.conv.escape_filter_charsでエスケープしてから検索フィルターに挿入してください。 - 検索フィルターとDNではエスケープ方法が異なります。DNを組み立てる場合は、RFC 4514に対応する値に
ldap.dn.escape_dn_charsまたはldap3.utils.dn.escape_rdnを使用してください。 - 必要に応じて業務上の許可リストや長さ制限を適用し、最小権限のアカウントでバインドしてください。これらは文脈に応じたエスケープを補完する対策であり、代わりにはなりません。
例
変更前
python
import ldap
from flask import request
def bad_search():
conn = ldap.initialize("ldaps://directory.example.com")
username = request.args.get("user")
query = "(uid=%s)" % username
return conn.search_s("ou=users,dc=example,dc=com", ldap.SCOPE_SUBTREE, query)
変更後
python
import ldap
from ldap.filter import filter_format
from flask import request
def safe_search():
conn = ldap.initialize("ldaps://directory.example.com")
username = request.args.get("user")
query = filter_format("(uid=%s)", [username])
return conn.search_s("ou=users,dc=example,dc=com", ldap.SCOPE_SUBTREE, query)
説明:
- 変更前: ユーザー入力をLDAPフィルター文字列に直接挿入します。
- 変更後: アプリケーションがフィルターテンプレートを固定し、
filter_formatが別途渡されたアサーション値にRFC 4515のエスケープを適用します。
適用時の注意点
フィルターのエスケープは、接続のTLS検証、バインドするアカウントの権限、必須入力の検証の代わりにはなりません。この例は検索フィルターの組み立てに絞った抜粋です。これらの設定は別途用意してください。DNや他のLDAP式を組み立てる場合は、その文脈に応じたエスケープを使用してください。