
| ブランチ | 1.13-CVE-2026-49268 |
| 作者 | Jinwoo Hwang (https://JinwooHwang.com) |
このブランチ(1.13-CVE-2026-49268)には、Apache Shiro 1.13リリースにおけるLDAPインジェクション認証バイパス脆弱性に対する包括的なセキュリティ修復が含まれています。
NVD公式説明
リモート攻撃者は、DefaultLdapRealmクラスのDistinguished Name(DN)構築にLDAP特殊文字を インジェクションすることができます。ユーザーが指定したユーザー名入力は、RFC 2253特殊文字の エスケープ処理なしに、LDAP DNテンプレートに直接連結されます。これにより、攻撃者はLDAPバインド 認証に使用されるDN構造を操作し、認証をバイパスしたり、他のユーザーになりすましたりする 可能性があります。この問題は、DefaultLdapRealmを使用している場合、Apache Shiroの 2.2.0までのすべてのバージョン、および3.0.0-alpha-1に影響します。
| フィールド | 値 |
|---|---|
| CVE | CVE-2026-49268 — Apache Shiro: DefaultLdapRealmにおけるLDAP DNインジェクション |
| CWE ID | CWE-90 — LDAPクエリで使用される特殊要素の不適切な中和(「LDAPインジェクション」) |
| CVSS v4.0 | 8.8 HIGH — CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N/S:P/AU:Y/R:A/RE:L/U:Red |
| CVSS v3.1 | 9.1 CRITICAL — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
| 影響を受けるバージョン | org.apache.shiro:shiro-core 0から2.2.0まで(両端を含む)、3.0.0-alpha-0から3.0.0-alpha-1まで(両端を含む) |
| アップストリームでの修正バージョン | 2.2.1および3.0.0-alpha-2 |
| 参照 | https://lists.apache.org/thread/svszql3od8td7hn6conyj2oq70v53b5s |
Apache Shiroは、LDAPディレクトリに対してユーザーを認証できます。そのために、送信された
ユーザー名をDistinguished Name(そのユーザーエントリのディレクトリアドレス)に変換します。
これは、ユーザー名を設定済みのテンプレート(例えば
uid={0},ou=users,dc=mycompany,dc=com)に埋め込むことで行います。
影響を受けるバージョンでは、ユーザー名は生のテキストとしてそのテンプレートに貼り付けられます。 いくつかの句読点文字 — 最も重要なのはカンマ — は、ディレクトリサーバーにとって通常のテキストでは ありません。それらは、アドレスのある部分を次の部分から区切る構文です。したがって、これらの文字を 含むユーザー名は、アドレス内の値として振る舞うことをやめ、アドレス自体の一部として振る舞い始めます。
実際的な結果として、ログインする人物が、単に自分の名前を提供するだけでなく、Shiroがどの ディレクトリエントリに対して認証を試みるかに影響を与えることができます。デプロイメントが意図した コンテナ内で検索される代わりに、検索はディレクトリ内の別の場所にリダイレクトされる可能性があります。 ディレクトリの構成方法や許可内容によっては、これが誤ったIDとしての認証につながったり、本来成功 すべきでない認証が成功したりする可能性があります。
リスクプロファイル。 リスクを高める要因:事前のアクセスや認証情報は不要です — 入力はログイン
境界に到達し、アプリケーションに到達できる誰もがアクセスできます。また、影響を受けるクラスは、
ShiroをLDAPに接続するための標準的で文書化された方法であるため、これは特殊な設定ではありません。
リスクを低減する要因:デプロイメントが実際にDefaultLdapRealm(またはJndiLdapRealm)を
設定済みのDNテンプレートとともに使用している必要があります。他の手段で認証するデプロイメント、
または完全なDNや証明書などの非テキスト認証情報を渡すデプロイメントは影響を受けません。リダイレクト
された検索が使用可能な認証をもたらすかどうかは、対象ディレクトリ自体の構成とアクセスルールにも
依存し、これはサイトごとに異なります。
リリース計画のために注目すべき、セキュリティ以外の二次的な結果もあります。正当なユーザー名に
バックスラッシュが含まれる、または#で始まるユーザーは、現在まったくログインできません。
なぜなら、それらのユーザー用に構築されたアドレスが整形式の名前ではなく、即座に拒否されるからです。
他の句読点は即座に失敗することはありません — それはアドレスが参照するエントリを暗黙的に変更し、
これが上記のセキュリティ問題です。同じ修正が両方を解決します。
core/src/main/java/org/apache/shiro/realm/ldap/DefaultLdapRealm.java、getUserDn(String)、
未修正のベースラインでは227〜250行目:```java
protected String getUserDn(String principal) throws IllegalArgumentException, IllegalStateException {
if (!StringUtils.hasText(principal)) {
throw new IllegalArgumentException("User principal cannot be null or empty for User DN construction.");
}
String prefix = getUserDnPrefix();
String suffix = getUserDnSuffix();
if (prefix == null && suffix == null) {
log.debug("userDnTemplate property has not been configured, indicating the submitted "
+ "AuthenticationToken's principal is the same as the User DN. Returning the method argument "
+ "as is.");
return principal;
}
int prefixLength = prefix != null ? prefix.length() : 0;
int suffixLength = suffix != null ? suffix.length() : 0;
StringBuilder sb = new StringBuilder(prefixLength + principal.length() + suffixLength);
if (prefixLength > 0) {
sb.append(prefix);
}
sb.append(principal); // <-- inserted verbatim
if (suffixLength > 0) {
sb.append(suffix);
}
return sb.toString();
}
テンプレートは設定時に一度だけ `{0}` トークンの周囲で分割され(`setUserDnTemplate`、181~200行目)、`prefix` と `suffix` になります。認証時には、プリンシパルがそれらの間に連結されます。**どの時点でもエンコーディングは適用されません。** `org/apache/shiro/realm/ldap/` のどこにもエスケープ用のヘルパーは存在しません。
### 3.2 想定されていたが決して強制されなかった不変条件
周囲のコードは `getUserDn` の結果を整形式の識別名(Distinguished Name)として扱います — それはそのまま `LdapContextFactory.getLdapContext(...)` に渡され、そこから JNDI へバインド DN として渡されます。これが妥当であるのは、置換されるプリンシパルが*単一の属性値*である場合に限られます。
文字列連結ではそれを強制できません。RFC 2253(および RFC 4514)は、`,` `+` `"` `\` `<` `>` `;` `=`、先頭の `#`、および先頭/末尾の空白を DN 内の構造構文として予約しています。これらのいずれかがプリンシパルに現れると、パーサーはそれらを内容ではなく構造として読み取ります。不変条件 — *「プリンシパルは正確に1つの RDN 値を占める」* — は、すべての下流のコンシューマーによって想定されていましたが、誰によっても強制されていませんでした。
### 3.3 実行フロー、エントリポイントから欠陥まで```
submitted credentials (username, password)
│
▼
DefaultLdapRealm.doGetAuthenticationInfo(AuthenticationToken) [line 292]
│
▼
DefaultLdapRealm.getLdapPrincipal(AuthenticationToken) [line 338]
│ principal instanceof String ?
│ ├── no ──► return principal unchanged ── NOT AFFECTED (e.g. X.509)
│ └── yes ──┐
▼ │
DefaultLdapRealm.getUserDn(String) [line 227]
│ prefix == null && suffix == null ?
│ ├── yes ──► return principal unchanged ── NOT AFFECTED ("principal IS the DN")
│ └── no ──┐
▼ │
prefix + principal + suffix ◄── DEFECT: unencoded concatenation [line 246]
│
▼
LdapContextFactory.getLdapContext(userDn, credentials)
│
▼
JNDI bind against the directory using the constructed DN
テンプレート uid={0},ou=users,dc=mycompany,dc=com、プリンシパル jsmith,ou=admins:
| 構築された DN | 解析された RDN | |
|---|---|---|
| 意図されたもの | uid=jsmith\,ou\=admins,ou=users,dc=mycompany,dc=com | 4 |
| ベースライン(未修正) | uid=jsmith,ou=admins,ou=users,dc=mycompany,dc=com | 5 |
名前がコンポーネントを獲得する。ou=users はもはや対象とされているコンテナではない —
ou=admins が挿入される。これは javax.naming.ldap.LdapName による解析で検証されており、
これはいかなるエスケープ実装からも独立している。
予約セット全体にわたるベースラインの測定された挙動(JDK 8、LdapName 解析):
| プリンシパル | ベースライン結果 | 影響 |
|---|---|---|
jsmith,ou=admins | 解析成功、5 RDN | 構造が改変 — コンテナが挿入される |
jsmith;ou=admins | 解析成功、5 RDN | 構造が改変 — RFC 1779 では ; は RDN 区切り文字 |
jsmith+uid=admin | 解析成功、4 RDN | 多値 RDN になり、uid が2つ目の値を獲得する |
jsmith=admin | 解析成功、4 RDN | 値が破損 |
quo"te、angle<br>ackets、 leadingSpace、trailingSpace | 解析成功、4 RDN | 値が破損 |
back\slash | IllegalArgumentException | 不正形式 — バインドを試行できない |
#leadingNumberSign | IllegalArgumentException | 不正形式 — バインドを試行できない |
2つの文字が名前の内容だけでなく構造を改変する:カンマ、そしてセミコロン — 後者は RFC 1779 が代替の RDN 区切り文字として受け入れる。+ は同じ RDN に2つ目の属性値を導入する。\ と先頭の # のみが名前を解析不能にする。
userDnTemplate 未設定。 getUserDn はプリンシパルをそのまま返す。これは文書化された「送信されたプリンシパルが そのまま DN である」モードであり、呼び出し側が完全な DN を提供し、その正確性に責任を持つ。影響なし。String プリンシパル。 getLdapPrincipal は String プリンシパルのみを getUserDn にルーティングし、それ以外(X.509 証明書、カスタムトークン)は通過する。影響なし。setUserDnTemplate の検証(181–200行目)は、null、空白、または {0} を含まないテンプレートを正しく拒否する。これは オペレータが提供する テンプレートを検証しており、それは信頼できない入力ではなかった — したがって、この欠陥に対処していないだけの正しいコードである。JndiLdapRealm は DefaultLdapRealm を継承し、getUserDn をオーバーライドしないため、この欠陥を継承する。テスト(§6.1)で確認済み。対象範囲内であり、同じ変更で修正される。AbstractLdapRealm.searchFilter(89行目)は2つ目の {0} 置換を持ち、デフォルトは (&(objectClass=*)(userPrincipalName={0})) で、認可パスで使用される。これは影響を受けない。