LDAPインジェクション

LDAPインジェクション

説明

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式を組み立てる場合は、その文脈に応じたエスケープを使用してください。

参考資料