LDAP 인젝션 취약점이 발생하는 원인과 검색 필터 조작을 통한 인증 우회 원리, 취약한 Java 코드 및 안전한 LDAP 인증 방법을 알아봅니다.
기업이나 기관에서는 사용자 계정과 조직 정보를 한곳에서 관리하기 위해 LDAP을 사용하는 경우가 많다. 사내 시스템에 로그인할 때 입력한 아이디를 LDAP 디렉터리에서 검색하고, 계정 정보와 비밀번호를 확인하는 방식이다.
LDAP 검색 조건을 만들 때 사용자가 입력한 값을 문자열로 직접 연결하면 공격자가 검색 필터의 구조를 변경할 수 있다. 이로 인해 다른 계정 정보가 조회되거나 인증 조건이 무력화되는 문제를 LDAP 인젝션 취약점이라고 한다.
LDAP이란?
LDAP은 ‘Lightweight Directory Access Protocol’의 약자로, 디렉터리 서비스의 정보를 조회하고 관리하기 위한 프로토콜이다. 일반적인 데이터베이스가 주문이나 게시물처럼 자주 변경되는 데이터를 처리한다면 LDAP은 사용자 계정, 부서, 이메일 주소와 권한 정보처럼 계층적으로 구성된 데이터를 관리하는 데 주로 사용된다.
LDAP 디렉터리의 항목은 DN이라는 고유한 이름으로 구분된다. 예를 들어 특정 사용자는 다음과 같은 구조로 표현될 수 있다.
uid=user01,ou=people,dc=example,dc=com
애플리케이션은 LDAP 필터를 이용해 원하는 사용자를 검색한다. 정상적인 사용자 ID가 입력되면 해당 계정만 조회해야 하지만, 입력값이 필터 문자열에 그대로 결합되면 검색 조건 자체가 변경될 수 있다.
LDAP 인젝션 취약점이란?
LDAP 인젝션은 외부 입력값이 LDAP 검색 필터나 DN 문자열의 일부로 사용되면서 원래 검색 조건의 구조를 변경하는 취약점이다.
SQL 인젝션이 사용자 입력을 통해 SQL 쿼리를 조작하는 공격이라면 LDAP 인젝션은 디렉터리 검색 필터를 조작한다는 차이가 있다. 두 취약점 모두 데이터와 명령 구조를 분리하지 않았을 때 발생한다는 공통점이 있다.
LDAP 필터에는 검색 조건을 표현하기 위한 괄호, 별표, 역슬래시 등의 특수문자가 사용된다. 입력값을 안전하게 처리하지 않으면 이러한 문자가 일반적인 사용자 이름이 아니라 검색 연산자로 해석될 수 있다.
LDAP 인젝션의 핵심은 외부 입력값이 LDAP 검색 필터의 구조와 조건에 영향을 준다는 점이다.
LDAP 인젝션이 발생하는 원인
가장 흔한 원인은 로그인 화면에서 받은 아이디와 비밀번호를 LDAP 필터 문자열에 직접 연결하는 것이다. 개발자는 입력된 값과 일치하는 사용자만 찾으려고 작성하지만, 공격자는 특수문자를 이용해 조건의 의미를 바꾸려고 시도할 수 있다.
특정 문자 몇 개만 제거하는 블랙리스트 방식도 충분하지 않다. LDAP 검색 필터에 사용하는 값과 DN에 사용하는 값은 이스케이프 처리 기준이 서로 다르기 때문이다. 값이 사용되는 위치를 구분하지 않고 동일한 필터를 적용하면 예상하지 못한 우회가 발생할 수 있다.
사용자 ID를 이용해 DN 문자열을 직접 만드는 코드도 주의해야 한다. 입력값이 DN 구성 요소로 사용된다면 LDAP 필터가 아닌 DN 문법에 맞는 별도의 인코딩이 필요하다.
애플리케이션이 사용하는 LDAP 계정에 과도한 조회 및 수정 권한이 부여된 경우에는 피해가 더 커질 수 있다. 검색 필터가 조작됐을 때 업무에 필요하지 않은 계정과 중요 속성까지 노출될 가능성이 있기 때문이다.
LDAP 인젝션에 취약한 Java 코드
다음은 사용자가 입력한 아이디와 비밀번호를 LDAP 검색 필터에 직접 연결하는 코드다.
public boolean login(
String username,
String password) throws Exception {
String filter =
"(&(uid=" + username + ")" +
"(userPassword=" + password + "))";
SearchControls controls =
new SearchControls();
controls.setSearchScope(
SearchControls.SUBTREE_SCOPE
);
NamingEnumeration<SearchResult> results =
context.search(
"ou=people,dc=example,dc=com",
filter,
controls
);
return results.hasMore();
}
코드에서는 username과 password를 문자열 결합으로 LDAP 필터에 넣는다. 정상적인 값이 입력되면 아이디와 비밀번호가 모두 일치하는 계정을 검색한다.
하지만 입력값에 LDAP 필터 문법으로 해석될 수 있는 문자가 포함되면 검색 조건의 의미가 달라질 가능성이 있다. 공격자는 비밀번호 비교 조건을 약화하거나 여러 계정이 검색되도록 필터를 조작하려고 시도할 수 있다.
검색 결과가 한 건이라도 있으면 로그인에 성공하도록 구현돼 있으므로 의도하지 않은 계정이 검색될 경우 인증 우회로 이어질 수 있다.
비밀번호를 LDAP 검색 속성과 직접 비교하는 방식 자체도 권장되지 않는다. 안전한 LDAP 인증은 사용자 계정을 찾은 뒤 해당 사용자의 DN과 비밀번호로 LDAP 바인드를 수행해 서버가 인증 결과를 판단하도록 구성하는 것이 좋다.
LDAP 인증 우회가 발생하는 원리
정상적인 로그인 요청에서는 서버가 사용자가 입력한 아이디와 일치하는 계정 하나를 검색한다. 이후 해당 계정의 DN을 기준으로 비밀번호 인증을 수행해야 한다.
취약한 코드에서는 사용자 입력값이 검색 필터의 일부가 된다. 공격자가 LDAP 연산자로 해석될 수 있는 값을 입력하면 필터에 새로운 조건이 추가되거나 기존 조건의 범위가 달라질 수 있다.
애플리케이션이 검색 결과의 첫 번째 계정을 로그인 사용자로 처리하거나 결과가 존재한다는 이유만으로 인증 성공을 반환하면 비밀번호를 정상적으로 확인하지 않고도 로그인될 가능성이 생긴다.
LDAP 인젝션이 항상 인증 우회로 이어지는 것은 아니다. 계정 검색에만 사용되는 기능에서는 사용자 목록이나 조직 정보가 과도하게 조회될 수 있다. LDAP 수정 기능과 연결된 경우에는 디렉터리 정보의 변조로 피해가 확대될 가능성도 있다.
LDAP 인젝션으로 발생할 수 있는 피해
로그인 필터가 조작되면 일반 사용자가 다른 계정이나 관리자 계정으로 인증을 우회할 수 있다. 내부 업무 시스템에서 LDAP 인증을 공통으로 사용한다면 하나의 취약점이 여러 기능의 접근 권한에 영향을 줄 수 있다.
검색 기능에서 LDAP 인젝션이 발생하면 사용자 이름, 이메일, 부서와 전화번호 같은 조직 정보가 노출될 수 있다. 오류 메시지나 응답 내용의 차이를 이용해 특정 계정의 존재 여부를 확인하는 데 악용될 수도 있다.
애플리케이션 LDAP 계정에 수정 권한이 있다면 계정 속성이나 그룹 정보가 변경될 위험도 있다. 따라서 검색만 필요한 애플리케이션에는 읽기 전용 권한만 부여하고, 조회할 수 있는 조직 단위와 속성도 최소화해야 한다.
안전한 Spring LDAP 인증 코드
Spring LDAP에서는 검색 필터를 문자열로 직접 만들기보다 LdapQueryBuilder와 같은 기능을 사용해 입력값을 안전하게 처리할 수 있다.
public boolean login(
String username,
String password) {
if (username == null ||
!username.matches(
"^[A-Za-z0-9._-]{3,50}$")) {
return false;
}
if (password == null ||
password.isBlank()) {
return false;
}
LdapQuery query =
LdapQueryBuilder.query()
.base("ou=people")
.where("uid")
.is(username);
return ldapTemplate.authenticate(
query,
password
);
}
이 코드는 먼저 사용자 ID의 형식과 길이를 제한한다. 비밀번호가 비어 있는 경우에도 LDAP 요청을 보내지 않고 인증을 거부한다. 일부 LDAP 환경에서는 빈 비밀번호가 익명 바인드로 처리될 가능성이 있으므로 애플리케이션에서 명확하게 차단해야 한다.
검색 필터는 문자열 연결이 아니라 LdapQueryBuilder를 이용해 구성한다. 사용자 입력값은 검색 조건의 데이터로 처리되며, 필터 문법에 영향을 주는 문자는 라이브러리의 규칙에 따라 인코딩된다.
비밀번호를 LDAP 검색 필터에 포함하지 않고 authenticate()를 통해 LDAP 바인드 인증을 수행하는 점도 중요하다. 애플리케이션이 비밀번호 값을 직접 비교하기보다 LDAP 서버가 해당 계정의 인증 결과를 판단하도록 한다.
입력값 검증과 이스케이프 처리를 함께 적용해야 한다
사용자 ID에 허용할 문자와 길이가 명확하다면 허용 목록 방식으로 검증하는 것이 좋다. 사번이 숫자로만 구성된다면 숫자와 정해진 길이만 허용하고, 일반 계정명이라면 영문자, 숫자와 필요한 일부 기호만 허용한다.
입력값 검증만으로 LDAP 인젝션 대응을 끝내서는 안 된다. 업무상 특수문자가 포함된 이름이나 검색어를 처리해야 할 수도 있기 때문이다. LDAP 필터에 들어가는 값은 필터 문법에 맞게 인코딩하고, DN에 들어가는 값은 DN 문법에 맞게 별도로 처리해야 한다.
필터 문자열을 직접 만들기보다 검증된 LDAP 라이브러리의 쿼리 작성 기능을 사용하는 편이 안전하다. 불가피하게 직접 처리해야 한다면 필터 값과 DN 값의 인코딩 기준을 구분해야 한다.
LDAP 계정에 최소 권한 적용
웹 애플리케이션이 사용하는 LDAP 서비스 계정에는 업무에 필요한 최소 권한만 부여해야 한다. 로그인 사용자 검색만 필요하다면 읽기 전용 권한을 사용하고 계정 생성, 수정과 삭제 권한은 부여하지 않는다.
검색 기준이 되는 Base DN도 필요한 조직 범위로 제한해야 한다. 전체 디렉터리를 검색할 수 있도록 설정하면 필터가 잘못됐을 때 다른 부서나 시스템 계정 정보까지 노출될 수 있다.
조회 결과에는 업무에 필요한 속성만 포함하도록 제한한다. 비밀번호 관련 속성이나 관리용 정보처럼 애플리케이션에서 사용하지 않는 값은 반환되지 않도록 설정하는 것이 좋다.
LDAP 서버와 애플리케이션 사이의 통신에는 TLS를 적용해야 한다. 암호화되지 않은 LDAP 통신에서는 계정 정보와 인증 데이터가 네트워크 구간에 노출될 가능성이 있다.
오류 메시지와 로그인 시도 관리
로그인 실패 시 “사용자가 존재하지 않습니다”와 “비밀번호가 틀렸습니다”를 구분해 보여주면 공격자가 계정 존재 여부를 확인할 수 있다. 외부 응답은 “아이디 또는 비밀번호가 올바르지 않습니다”처럼 동일한 메시지로 처리하는 것이 좋다.
반복적인 LDAP 필터 오류, 특수문자가 포함된 계정 검색과 짧은 시간에 발생한 대량 로그인 실패를 로그로 기록해야 한다. 동일한 계정이나 IP에서 로그인 시도가 반복되면 Rate Limiting이나 일시적인 지연을 적용할 수 있다.
단순히 계정을 장시간 잠그는 정책만 적용하면 공격자가 다른 사용자의 계정을 의도적으로 잠그는 서비스 거부 공격에 이용할 수 있다. 접속 IP, 계정, 기기와 시간대를 함께 고려해 제한 정책을 설계해야 한다.
LDAP 인젝션 취약점 점검 항목
소스코드 점검에서는 DirContext.search(), LdapTemplate.search(), authenticate()와 같은 LDAP 관련 함수를 먼저 확인한다. 외부 입력값이 문자열 연결을 통해 검색 필터나 DN에 포함되는지 추적해야 한다.
필터를 구성할 때 검증된 쿼리 작성 기능을 사용하는지, 필터 값과 DN 값에 각각 올바른 인코딩이 적용되는지도 확인한다. 로그인 기능에서는 비밀번호를 검색 조건으로 비교하지 않고 LDAP 바인드 인증을 사용하는지 살펴봐야 한다.
빈 사용자 이름과 빈 비밀번호가 명확하게 거부되는지도 중요하다. 검색 결과가 여러 건일 때 첫 번째 계정을 임의로 사용하는 코드가 없는지, 반드시 하나의 계정만 선택되는지 확인해야 한다.
운영 환경에서는 LDAP 서비스 계정의 권한, Base DN의 범위와 TLS 적용 여부를 점검한다. 인증 실패 기록과 비정상적인 필터 오류를 모니터링하는지도 함께 확인해야 한다.
자주 묻는 질문
LDAP 인젝션과 SQL 인젝션은 같은 공격인가요?
공격 원리는 비슷하지만 조작하는 대상이 다르다. SQL 인젝션은 데이터베이스의 SQL 쿼리를 조작하고, LDAP 인젝션은 디렉터리 검색 필터나 DN을 조작한다. 두 취약점 모두 외부 입력값을 명령 구조에 직접 연결할 때 발생한다.
특수문자를 모두 차단하면 안전한가요?
업무에 필요한 정상적인 문자까지 차단할 수 있으며, 사용 위치에 따라 인코딩 기준도 다르다. 허용 가능한 입력 형식을 검증하고 LDAP 라이브러리를 이용해 필터 값과 DN 값을 각각 안전하게 처리해야 한다.
비밀번호를 LDAP 필터에서 검색하면 안 되나요?
권장되지 않는다. 먼저 사용자 ID로 계정을 안전하게 찾은 뒤 해당 계정의 DN과 비밀번호로 LDAP 바인드 인증을 수행하는 것이 좋다. 애플리케이션이 비밀번호 속성을 직접 검색하거나 비교하지 않도록 구성해야 한다.
PreparedStatement로 LDAP 인젝션을 막을 수 있나요?
PreparedStatement는 SQL 쿼리를 위한 기능이므로 LDAP 필터에는 사용할 수 없다. LDAP 전용 쿼리 빌더나 안전한 필터 생성 기능을 사용해야 한다.
마무리
LDAP 인젝션은 사용자 입력값이 LDAP 검색 필터나 DN의 구조에 영향을 줄 때 발생한다. 로그인 기능에서 취약점이 발생하면 검색 조건이 변조돼 다른 계정 조회나 인증 우회로 이어질 수 있다.
안전한 대응을 위해서는 입력값을 문자열로 직접 연결하지 않고 검증된 LDAP 쿼리 작성 기능을 사용해야 한다. 사용자 ID의 형식과 길이를 제한하고, 비밀번호는 검색 필터가 아닌 LDAP 바인드 방식으로 인증해야 한다.
LDAP 인젝션 방어의 핵심은 외부 입력값과 LDAP 필터 구조를 분리하는 것이다. 필터와 DN에 맞는 인코딩, 최소 권한, TLS 통신과 로그인 모니터링을 함께 적용해야 인증 우회와 정보 노출 위험을 줄일 수 있다.
핵심 키워드: LDAP 인젝션, LDAP Injection, 인증 우회, LDAP 필터 조작, LDAP 보안, LDAP 바인드 인증, Spring LDAP, LDAP 인젝션 대응 방법