본문 바로가기
CVE 취약점

Dovecot SQL 기반 인증 우회 (auth_username_chars 회귀 취약점) CVE-2026-24031

by WhiteGuidance 2026. 9. 2.
반응형

 

1. 개요

항목 내용
CVE ID CVE-2026-24031
대상 Dovecot Core (SQL passdb/userdb 인증)
취약점 유형 CWE-89 (SQL Injection)
CVSS 3.1 7.7 (High) — AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:L
영향 버전 OX Dovecot CE core 2.4.0 ~ 2.4.2, OX Dovecot Pro core 3.1.0 ~ 3.1.3
패치 버전 CE core 2.4.3, Pro core 3.1.4
내부 추적 ID DOV-8781

 

"설정 실수 문제"처럼 보이지만, 실제로는 2.4 계열로 넘어오면서 진행된 설정(settings) 시스템 리팩터링 과정에서 SQL 이스케이프 함수가 조용히 무시되는 회귀 버그가 핵심 원인이다. auth_username_chars는 이 버그가 실제로 트리거되기 위한 전제조건일 뿐이다.

 

이 글에서는 dovecot/core 저장소를 직접 클론해서 취약 버전(2.4.2)과 패치 버전(2.4.3)의 실제 소스코드를 비교하며 근본 원인을 추적한다.


2. 배경 지식 — auth_username_chars는 원래 뭘 하는 설정인가

Dovecot은 로그인 시 사용자가 입력한 username을 SQL/LDAP 쿼리, 파일 경로(%u, %n, %d 등 변수 치환) 등 여러 곳에 그대로 재사용한다. 이때 사용자명에 위험한 문자가 섞여 들어가는 것을 막기 위한 1차 방어선이 auth_username_chars다.

 

src/auth/auth-settings.c 기본값:

 

기본값은 영문 대소문자, 숫자, . - _ @ 만 허용하는 화이트리스트다. 작은따옴표(')나 세미콜론 같은 SQL 메타문자는 애초에 포함되어 있지 않다.

 

이 문자열은 실제로는 256바이트짜리 bitmap(username_chars_map)으로 변환되어 사용되는데, 그 변환 로직에 다음과 같은 특수 케이스가 존재한다.

 

src/auth/auth-settings.c (약 623~630번째 줄):

 

즉 auth_username_chars를 빈 문자열("")로 설정하면 "허용 문자 없음"이 아니라 정반대로 "모든 바이트(0x00~0xFF) 허용" 이 된다. 관리자가 넓은 유니코드 사용자명 등을 쓰기 위해 이 값을 비워버리는 경우가 실무에서 종종 있는데, 이 순간 SQL 메타문자에 대한 1차 필터가 완전히 사라진다.

 

실제 필터링은 src/auth/auth-request-fields.c의 auth_request_fix_username() 에서 이 map을 검사하는 방식으로 이루어진다.

 

username_chars_map이 전부 1로 채워져 있으면 이 검사는 사실상 아무 문자도 걸러내지 못한다. 여기까지는 "관리자 설정 실수"의 영역이다. 문제는 그 다음 단계다.


3. SQL 쿼리는 어떻게 만들어지는가 — passdb-sql.c

SQL passdb는 passdb_sql_query 설정(예: SELECT username, password FROM users WHERE username = '%u')에 %u(username) 같은 변수를 실제 값으로 치환(var-expand)해서 쿼리 문자열을 만든다. 이때 SQL 인젝션을 막기 위해 반드시 값에 SQL 이스케이프를 적용해야 한다.

 

src/auth/passdb-sql.c:

 

코드만 보면 문제가 없어 보인다. settings_get_params()를 호출하면서 escape_func = passdb_sql_escape를 명시적으로 넘기고 있고, 이 함수는 실제 DB 드라이버(MySQL/PostgreSQL/SQLite)의 sql_escape_string()을 호출해 따옴표 등을 제대로 이스케이프한다. settings.h의 주석도 이렇게 계약을 명시한다.

"non-NULL이면 반드시 이 함수로 이스케이프된다" — 그런데 실제로는 이 계약이 지켜지지 않았다.


4. 진짜 원인 — settings.c의 escape_func 덮어쓰기 버그

문제는 passdb-sql.c가 아니라, 그보다 더 아래 레이어인 src/lib-settings/settings.csettings_var_expand_init()에 있었다. Dovecot 설정 시스템은 var-expand용 escape 함수를 두 군데에서 받을 수 있다.

  1. settings_get_params() 호출자가 명시적으로 넘긴 escape_func (예: passdb_sql_escape)
  2. 이벤트(event) 계층을 타고 올라가며 등록된 전역/컨텍스트 기본 escape 콜백 (init_ctx.escape_func)

취약한 2.4.0~2.4.2 코드는 이 둘 중 무조건 2번(전역 값)을 사용했다.

 

패치 커밋 34fbd3956d diff (src/lib-settings/settings.c):

 

즉 취약한 버전에서는 passdb-sql.c가 아무리 escape_func = passdb_sql_escape를 명시적으로 지정해도, 실제 변수 치환 단계에서는 그 값이 조용히 버려지고 이벤트 체인에서 발견된 다른(대개 SQL 이스케이프와 무관한, 혹은 존재하지 않는) escape 함수가 대신 사용됐다. 결과적으로 %u로 치환되는 username 값에 대해 SQL 이스케이프가 전혀 적용되지 않거나 잘못된 이스케이프가 적용되는 상황이 발생한다.

 

이 버그는 2024년 커밋 ef0c63b6 ("auth: passdb/userdb sql - Convert to new settings")로 SQL passdb/userdb를 신규 settings 프레임워크로 전환하는 대규모 리팩터링 과정에서 도입된 회귀(regression)다. Dovecot 개발자도 커밋 로그에 "v2.4 regression"이라고 명시하고 있다.

같은 원리로 LDAP passdb에서도 동일한 클래스의 문제(CVE-2026-27860, LDAP 필터 인젝션)가 함께 발견·수정되었다 — 두 CVE 모두 원인이 같은 settings_var_expand_init() 버그다.


5. 전체 공격 체인

두 가지 조건이 겹쳐야 실제 익스플로잇이 가능하다.

  • 필요 조건 A: 관리자가 auth_username_chars를 빈 문자열로 설정 (문자 화이트리스트 비활성화)
  • 근본 원인 B: settings_var_expand_init()passdb_sql_escape 지정을 무시하는 버그 (모든 2.4.0~2.4.2 / 3.1.0~3.1.3 설치본에 항상 존재)

CVSS 벡터의 AC:H(공격 복잡도 높음)는 조건 A가 성립해야 한다는 점을 반영한 것이다.

 

 


6. 실제 수정 코드 (v2.4.3)

핵심 패치는 앞서 본 settings_var_expand_init()의 우선순위 재정렬이다.

 

이제 settings_get_params() 호출자가 escape_func(예: passdb_sql_escape)를 명시적으로 넘기면 그 값이 최우선으로 적용되고, 넘기지 않은 경우에만 이벤트 체인의 전역 escape 함수가 fallback으로 사용된다. passdb-sql.c, userdb-sql.c, db-ldap.c가 넘기는 이스케이프 함수가 이제야 실제로 동작하게 된 것이다.

함께 배포된 관련 커밋들도 같은 클래스의 문제를 정리한다.

  • 25c34e5084 passdb sql - Fix escaping for set_credentials() (OTP 갱신 경로의 이스케이프 누락, 별도 CVE는 아니지만 같은 패턴)
  • 6a8f2daf15passdb/userdb ldap - Fix escaping ldap filter, base and bind_userdn (CVE-2026-27860, LDAP 버전의 동일 취약점)
  • 74a6f1612eRewrite ldap_escape() with a unit test (회귀 재발 방지를 위한 테스트 보강)

7. 탐지 및 완화

설정 점검

  • 값이 비어 있으면(auth_username_chars = ) 즉시 위험 신호다. 최소한 기본 화이트리스트(abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@) 수준으로 되돌린다.
  • 유니코드 사용자명이 반드시 필요하다면, 문자 자체를 무제한 허용하는 대신 SQL 인젝션에 사용되는 문자(', ", ;, \, 공백 등)만 명시적으로 제외한 화이트리스트를 구성한다.

패치 적용

  • CE core → 2.4.3 이상
  • Pro core → 3.1.4 이상

로그 기반 탐지

auth_debug = yes 상태에서 passdb-sql.c가 남기는 query: ... 디버그 로그에 비정상적인 따옴표/주석(--, #, /*) 패턴이 포함된 사용자명이 나타나는지 점검한다.

e_debug(authdb_event(sql_request->auth_request), "query: %s", set->query);
 

네트워크/운영 통제

  • 인증 실패 응답 시간·에러 메시지 차이를 이용한 사용자 열거(enumeration) 가능성에 대비해 인증 엔드포인트에 rate limiting을 적용한다.
  • SQL 쿼리 로깅이 활성화된 DB 서버 측에서도 password_query/user_query에 대해 비정상 WHERE 절 패턴을 탐지하는 룰을 추가한다
반응형