
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.c의 settings_var_expand_init()에 있었다. Dovecot 설정 시스템은 var-expand용 escape 함수를 두 군데에서 받을 수 있다.
- settings_get_params() 호출자가 명시적으로 넘긴 escape_func (예: passdb_sql_escape)
- 이벤트(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는 아니지만 같은 패턴)
- 6a8f2daf15 — passdb/userdb ldap - Fix escaping ldap filter, base and bind_userdn (CVE-2026-27860, LDAP 버전의 동일 취약점)
- 74a6f1612e — Rewrite 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 절 패턴을 탐지하는 룰을 추가한다