Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
shiro — CVE-2026-49268 — LDAP 인젝션 인증 우회 취약점 분석 및 완화 | Kitploit
도구/GitHubGitHub/sassoftware/shiro
Static Code Analysis (SAST)Vulnerability AnalysisCode AnalysisWeb SecurityAuthenticationLearning & Education
GitHubsassoftware/shiro

shiro

CVE-2026-49268 — LDAP 인젝션 인증 우회 취약점 분석 및 완화

저장소 보기
22926일 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-49268 — LDAP 인젝션 인증 우회 취약점 분석 및 해결

브랜치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에 영향을 미칩니다.


1. 취약점 개요

필드값
CVECVE-2026-49268 — Apache Shiro: DefaultLdapRealm의 LDAP DN 인젝션
CWE IDCWE-90 — LDAP 쿼리에 사용되는 특수 요소의 부적절한 중화('LDAP 인젝션')
CVSS v4.08.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.19.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

2. 요약

Apache Shiro는 LDAP 디렉터리에 대해 사용자를 인증할 수 있습니다. 이를 위해 제출된 사용자 이름을 Distinguished Name(해당 사용자 항목에 대한 디렉터리의 주소)으로 변환하는데, 사용자 이름을 구성된 템플릿에 그대로 넣습니다. 예를 들어 uid={0},ou=users,dc=mycompany,dc=com과 같습니다.

영향받는 버전에서는 사용자 이름이 원시 텍스트로 해당 템플릿에 붙여넣어집니다. 몇 가지 구두점 문자 — 가장 중요한 것은 쉼표 — 는 디렉터리 서버에게 일반 텍스트가 아니라, 주소의 한 부분을 다음 부분과 구분하는 구문입니다. 따라서 이러한 문자를 포함한 사용자 이름은 주소 내부의 값처럼 동작하지 않고 주소 자체의 일부처럼 동작하기 시작합니다.

실질적인 결과는 로그인하는 사람이 자신의 이름을 제공하는 것뿐만 아니라 Shiro가 어느 디렉터리 항목에 대해 인증을 시도할지에 영향을 줄 수 있다는 것입니다. 배포에서 의도한 컨테이너에서 조회되는 대신, 조회가 디렉터리의 다른 곳으로 리디렉션될 수 있습니다. 디렉터리의 구성 방식과 허용 범위에 따라, 이는 잘못된 신원으로 인증되거나, 인증이 성공해서는 안 되는 상황에서 성공하는 결과로 이어질 수 있습니다.

위험 프로필. 위험을 높이는 요소: 사전 접근 권한이나 자격 증명이 필요하지 않습니다 — 입력이 로그인 경계에 도달하며, 이는 애플리케이션에 접근할 수 있는 누구나 도달할 수 있습니다; 그리고 영향받는 클래스는 Shiro를 LDAP에 연결하는 표준적이고 문서화된 방식이므로, 이는 특이한 구성이 아닙니다. 위험을 낮추는 요소: 배포에서 실제로 DefaultLdapRealm(또는 JndiLdapRealm)을 구성된 DN 템플릿과 함께 사용해야 합니다; 다른 방식으로 인증하거나, 전체 DN 또는 인증서와 같은 비텍스트 자격 증명을 전달하는 배포는 영향을 받지 않습니다. 리디렉션된 조회가 사용 가능한 인증을 산출하는지 여부는 대상 디렉터리 자체의 구성과 접근 규칙에 따라 달라지며, 이는 사이트마다 다릅니다.

릴리스 계획을 위해 주목할 만한 두 번째의 비보안적 결과가 있습니다: 정당한 사용자 이름에 백슬래시가 포함되거나 #으로 시작하는 사용자는 현재 전혀 로그인할 수 없습니다. 그들을 위해 생성된 주소가 올바른 형식의 이름이 아니어서 즉시 거부되기 때문입니다. 다른 구두점은 즉시 실패하지 않습니다 — 주소가 참조하는 항목을 조용히 변경하며, 이것이 위의 보안 문제입니다. 동일한 수정이 두 가지 모두를 해결합니다.


3. 근본 원인 분석

3.1 결함

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`로 나뉜다. 인증 시점에는
principal이 그 사이에 연결된다. **어느 시점에도 인코딩은 적용되지 않는다.** `org/apache/shiro/realm/ldap/` 어디에도
이스케이프 헬퍼는 존재하지 않는다.

### 3.2 가정되었지만 결코 강제되지 않은 불변식

주변 코드는 `getUserDn`의 결과를 잘 구성된 Distinguished Name으로 취급한다 —
그것은 곧바로 `LdapContextFactory.getLdapContext(...)`에 전달되고, 거기서부터 JNDI에
바인드 DN으로 전달된다. 이는 대체된 principal이 *단일 속성 값*일 때만 타당하다.

문자열 연결로는 그것을 강제할 수 없다. RFC 2253(그리고 RFC 4514)은
`,` `+` `"` `\` `<` `>` `;` `=`, 선행 `#`, 그리고 선행/후행 공백을 DN 내의 구조적
구문으로 규정한다. 이들 중 어느 것이든 principal에 나타나면 파서는 그것을 내용이 아니라
구조로 읽는다. *"principal은 정확히 하나의 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

3.4 입증된 효과

템플릿 uid={0},ou=users,dc=mycompany,dc=com, principal jsmith,ou=admins:

생성된 DN파싱된 RDN
의도된 결과uid=jsmith\,ou\=admins,ou=users,dc=mycompany,dc=com4
기준선 (수정 전)uid=jsmith,ou=admins,ou=users,dc=mycompany,dc=com5

이름에 구성 요소가 추가된다. ou=users는 더 이상 대상이 되는 컨테이너가 아니며 — ou=admins가 그 사이에 삽입된다. 이는 어떤 이스케이프 구현과도 독립적인 javax.naming.ldap.LdapName으로 파싱하여 검증되었다.

예약 문자 집합에 대한 기준선의 측정된 동작 (JDK 8, LdapName 파싱):

Principal기준선 결과효과
jsmith,ou=admins파싱됨, 5 RDN구조 변경됨 — 컨테이너가 삽입됨
jsmith;ou=admins파싱됨, 5 RDN구조 변경됨 — RFC 1779에서 ;는 RDN 구분자임
jsmith+uid=admin파싱됨, 4 RDN다중값 RDN이 됨; uid가 두 번째 값을 얻음
jsmith=admin파싱됨, 4 RDN값 손상됨
quo"te, angle<br>ackets, leadingSpace, trailingSpace 파싱됨, 4 RDN값 손상됨
back\slashIllegalArgumentException형식 오류 — 바인드를 시도할 수 없음
#leadingNumberSignIllegalArgumentException형식 오류 — 바인드를 시도할 수 없음

두 문자가 이름의 내용뿐 아니라 구조를 변경한다: 쉼표, 그리고 RFC 1779가 대체 RDN 구분자로 허용하는 세미콜론이다. +는 동일한 RDN에 두 번째 속성 값을 도입한다. \와 선행 #만이 이름을 파싱 불가능하게 만든다.

3.5 올바른 인접 코드와 그 이유 — 영향 범위

  • userDnTemplate 미설정. getUserDn은 principal을 그대로 반환한다. 이는 문서화된 "제출된 principal이 곧 DN" 모드이며, 호출자가 완전한 DN을 제공하고 그 정확성에 대한 책임을 진다. 영향을 받지 않음.
  • 비-String principal. getLdapPrincipal은 String principal만 getUserDn으로 라우팅하며, 그 외의 것(X.509 인증서, 사용자 정의 토큰)은 그대로 통과한다. 영향을 받지 않음.
  • setUserDnTemplate 검증 (181–200행)은 null, 공백, 또는 {0}이 없는 템플릿을 올바르게 거부한다. 이는 운영자가 제공한 템플릿을 검증하는데, 그것은 결코 신뢰할 수 없는 입력이 아니었다 — 따라서 이는 올바른 코드이며 단지 이 결함을 다루지 않을 뿐이다.
  • **JndiLdapRealm**은 DefaultLdapRealm을 확장하며 getUserDn을 재정의하지 않으므로 결함을 상속한다. 테스트로 확인됨(§6.1). 범위 내에 있으며, 동일한 변경으로 수정된다.

3.6 관련 경로 — 평가됨, 영향 없음

AbstractLdapRealm.searchFilter (89행)는 두 번째 {0} 치환을 가지며, 기본값은 (&(objectClass=*)(userPrincipalName={0}))이고 인가 경로에서 사용된다. 이는 영향을 받지 않는다.

도구 다운로드