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

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

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

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-97332-PoC — CVE-2026-97332에 대한 독립적인 PoC 및 재현 가능한 실습 환경 — WordPress Multisite에서 User Private Files < 2.2.0의 비인증 비공개 파일 노출 취약점. | Kitploit
도구/GitHubGitHub/kolya080808/cve-2026-97332-poc
Defensive ToolsVulnerability AnalysisExploitationWeb Application ExploitationInformation GatheringWeb SecurityPenetration TestingLabs & Practice
GitHubkolya080808/cve-2026-97332-poc

CVE-2026-97332-PoC

CVE-2026-97332에 대한 독립적인 PoC 및 재현 가능한 실습 환경 — WordPress Multisite에서 User Private Files < 2.2.0의 비인증 비공개 파일 노출 취약점.

429일 전아직 검토되지 않음

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
저장소 보기
웹사이트

CVE-2026-97332 — WordPress User Private Files < 2.2.0 인증되지 않은 비공개 파일 노출 (멀티사이트)

CVE Severity Affected Fixed Requires PoC Ethics

[!NOTE] 이 저장소는 CVE-2026-97332에 대한 **독립적인 재현 및 개념 증명(Proof of Concept)**입니다.

최초 취약점 발견: Akshat Parikh (SN1PER). CNA / 권고문: WPScan. 독립 PoC 및 재현: Kolya080808.

멀티사이트 설치 환경에서 WordPress 플러그인 User Private Files(wp.org 슬러그 user-private-files) 2.2.0 이전 버전의 다른 사용자 비공개 파일에 대한 인증되지 않은 노출 취약점입니다. 이 플러그인은 모든 사용자의 비공개 문서를 평범한 업로드 디렉터리 트리 안에 저장하고, 루트 .htaccess에 추가된 단일 RewriteRule로 보호합니다. 이 규칙은 파일 요청을 PHP 접근 검사로 라우팅합니다. 멀티사이트 네트워크에서는 이 규칙이 결코 적용되지 않습니다: 쿠키도, 헤더도, 로그인도 없이 단순한 GET 요청 하나로 다른 사용자의 비공개 파일이 정적 콘텐츠로 반환됩니다. 접근 제어 미흡(Broken Access Control), CWE-284, CVSS 3.1 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N).

이 저장소에는 공격을 처음부터 끝까지 재현할 수 있는, 의존성이 없는 동작하는 Python 익스플로잇과 두 개의 컨테이너로 구성된 docker 랩(취약 버전 2.1.9 및 패치 버전 2.2.0, 둘 다 WordPress 멀티사이트)이 포함되어 있습니다.

[!CAUTION] 이 자료는 승인된 보안 연구 및 방어적 테스트 전용으로 제공됩니다(하단의 면책 조항 참조). 소유하지 않았거나 운영하지 않는 시스템을 서면 허가 없이 공격하는 것은 불법입니다.

취약점

이 플러그인은 비공개 파일을 평면적인 사이트별 업로드 하위 디렉터리 — wp-content/uploads/upf-docs/(메인 사이트) 및 wp-content/uploads/sites/N/upf-docs/(서브사이트 N) — 에 일반 첨부 파일로 보관하며, 사용자별 격리는 전혀 없습니다. 접근 제어는 디렉터리에 있는 것이 아니라 PHP(dl-file.php: 익명 사용자에게 403, 그다음 작성자/허용 목록 검사)에 있으며, 플러그인 활성화 시 루트 .htaccess에 출력되는 rewrite를 통해서만 도달할 수 있습니다:

# appended by 2.1.9 (upvf_actdeact.php), AFTER the WordPress block
RewriteRule ^wp-content/uploads/upf-docs/(.*)$ https://site/?file=$1 [QSA,L]

멀티사이트에서는 두 가지 독립적인 이유로 이 훅이 절대 실행되지 않습니다:

  1. 네트워크 .htaccess가 기존 파일을 먼저 단락 처리합니다. WordPress 멀티사이트는 자체 규칙 세트를 루트 .htaccess에 기록합니다:

    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^ - [L]
    

    물리적으로 존재하는 파일에 매핑되는 요청은 그 지점에서 재작성을 종료합니다 — Apache는 이를 정적 콘텐츠로 제공하며, mod_rewrite는 파일 끝에 있는 플러그인의 블록에 도달하지 않습니다. (단일 사이트 WordPress는 부정 조건 !-f/!-d를 사용하기 때문에 동일한 플러그인이 그곳에서는 작동합니다.) 이는 이미 네트워크의 메인 사이트 파일들을 노출시킵니다.

  2. 서브사이트 경로는 패턴과 전혀 일치하지 않습니다. 멀티사이트에서 각 사이트는 자체 업로드를 가지며 — wp-content/uploads/sites/N/…, 그리고 서브디렉터리 네트워크에서는 /siteN/ URL 접두사 — 규칙은 메인 사이트의 기본 URL에서 가져온 ^wp-content/uploads/upf-docs/로 하드코딩되어 있습니다. 네트워크의 내부 재작성이 /siteN/ 접두사를 제거한 후에도 sites/N/ 세그먼트는 여전히 일치하지 않습니다.

따라서 모든 멀티사이트 네트워크(서브디렉터리 및 서브도메인 모두)에서 인증되지 않은 공격자는 비공개 파일을 직접 가져올 수 있습니다:

GET /wp-content/uploads/upf-docs/victim.pdf HTTP/1.1            (main site)
GET /site2/wp-content/uploads/sites/2/upf-docs/victim.pdf HTTP/1.1  (subsite)

응답은 파일 본문과 함께 200이며, Apache가 uploads/에서 직접 제공하므로 플러그인의 접근 검사는 실행되지 않는다.

파일 이름은 예측 가능하다

URL에 비밀스러운 부분은 없다. 플러그인은 소유자의 숫자 사용자 ID와 업로드 시점의 유닉스 초를 기반으로 이름을 생성한다:

생성기 (2.1.9)디스크상 이름
최신 UI, inc/functions-file.php:73-77usr{ID}_{unixtime}_{original}
클래식 템플릿, inc/classic-user-functions.phpuser_{ID}_doc_{unixtime}.{ext}

멀티사이트 네트워크의 사용자 ID는 하나의 공유 wp_users 테이블에서 나오는 작은 순차 정수이므로, 공격자는 대략적인 업로드 시각(±2분 범위는 약 240회 요청)과 원본 파일 이름 또는 그 확장자만 있으면 된다. 충돌 접미사(-1, -2)도 존재하지만 드물다.

수정 사항 (2.2.0)

버전 2.2.0은 루트 파일 재작성을 폴더 수준의 upf-docs/.htaccess로 대체했다 — RewriteRule ^(.*)$ {home}/?file=$1 [R=302,QSA,L], 그리고 mod_rewrite가 없을 때는 Require all denied — 이는 매 admin_init마다 (자가 복구 방식으로, 서브사이트별로) 재생성된다. 디렉터리별 규칙은 URL 접두사와 관계없이 upf-docs/ 아래의 모든 물리적 경로에 대해 작동하므로, 이제 직접 GET은 접근 검사로의 302가 되고, 익명 사용자의 경우 403이 된다. 파일은 이동되지 않으며, 오직 가로채기 메커니즘만 변경된다. 이 302 대 정적 응답의 구분이 바로 익스플로잇이 행위 기반 탐지기로 사용하는 것이다.

영향을 받는 버전

  • User Private Files < 2.2.0 (2.1.9 소스로 검증됨; SVN 태그 1.0.1–2.1.9는 동일한 루트 재작성 설계를 가짐)
  • 멀티사이트 배포만 해당 (서브디렉터리 및 서브도메인). 단일 사이트 설치는 Apache가 .htaccess를 읽고 활성화 시점에 파일이 쓰기 가능했다면 루트 규칙으로 보호된다 (nginx는 벤더의 수동 재작성이 필요 — 플러그인 readme 참조).
  • 참조: WPScan 권고 d6a144b3-84e3-43eb-92d3-fd4505dd0e1c (2026-10-02 게시, 연구원 Akshat Parikh / SN1PER).

익스플로잇 작동 방식

exploit.py는 순수 Python 3 표준 라이브러리(http.client)만 사용하며, 리디렉션을 따르지 않고 쿠키를 전송하지 않는다:

  1. 버전 확인 (1단계, 공격 전): GET /wp-content/plugins/user-private-files/readme.txt를 요청하고 Stable tag:를 파싱한다. < 2.2.0 → 계속 진행; >= 2.2.0 → 빨간색 NOT VULNERABLE - plugin 2.2.0+ (patched) 박스를 표시하고 종료 코드 6. readme.txt를 읽을 수 없으면, 존재하지 않는 upf-docs/ 이름에 대한 행위 기반 프로브(302 = 재작성 적용 중, 404 = 정적 제공)를 표시하고 실행을 계속한다 (--skip-check는 이 단계 전체를 비활성화한다).
  2. URL 구성 (2단계): 메인 사이트 {base}/wp-content/uploads/upf-docs/{file} 또는 --site site2 --site-id 2를 사용하면 {base}/site2/wp-content/uploads/sites/2/upf-docs/{file}.
  3. 가져오기 (3단계): 단일 일반 GET, Cookie 없음, Authorization 없음.
  4. 판정 (4단계):
    • 200 → 파일의 첫 줄들을 증거로 * LEAKED 표시, 종료 0;
    • 302 → "접근 검사로 리디렉션됨 - 취약하지 않음 / 패치됨", 종료 1;
    • 404 → "파일을 찾을 수 없음 (이름이 잘못되었나?)" — --guess 시도, 종료 1.

--guess 모드는 하나의 고정된 이름 대신 예측 가능한 이름 패턴을 열거한다: --user-id 2 --orig-name "Invoice 2026.pdf" --around <unixtime> --window 120은 범위 내 매초에 대해 usr2_{t}_Invoice-2026.pdf와 user_2_doc_{t}.pdf를 생성하고, 200으로 응답하는 모든 이름을 읽는다.

종료 코드: 0 파일 노출됨 · 1 노출되지 않음 (404 / 302 / 403) · 6 취약하지 않음 (버전 확인) · 4 네트워크 오류.

저장소 구조

exploit.py     Python exploit (stdlib only, zero dependencies)
demo.sh        end-to-end demo: leak on 2.1.9, refusal on 2.2.0
README.md      this file
logs/          actual runs of demo.sh (vulnerable and patched)
lab/   docker lab: 2 x WordPress multisite + MariaDB
               (wp-vuln: port 8080, plugin 2.1.9; wp-control: port 8081, plugin 2.2.0)

재현

cd stand-wp && docker compose up -d     # vulnerable 8080 + control 8081
cd ../poc-upf && ./demo.sh              # full run, logs into logs/

데모는 순서대로 다음을 출력합니다: readme.txt에서 가져온 두 스탠드의 Stable tag; 자격 증명 없이 user1의 개인 파일을 유출하는 2.1.9(포트 8080)에 대한 공격; 버전 검사가 거부하는(종료 코드 6) 2.2.0(포트 8081)에 대한 동일한 공격; 그리고 --skip-check를 사용했을 때 접근 검사가 경로에 다시 적용되었음을 증명하는 원시 302 -> /?file=... 리다이렉트입니다.

예상 출력 — 취약한 2.1.9 (포트 8080)

  [1/4] Fingerprinting the plugin via readme.txt ........ VULNERABLE (2.1.9)
  > user-private-files readme.txt reports Stable tag: 2.1.9 (< 2.2.0) - affected on multisite
  [2/4] Building the direct-download URL ................................ OK
  > http://localhost:8080/wp-content/uploads/upf-docs/victim-secret.txt
  [3/4] Fetching the file with no authentication .................... 200 OK
  [4/4] Verdict ..................................................... LEAKED

  * LEAKED  victim-secret.txt (56 bytes, text/plain)
  Proof - first lines of the private file:
    TOPSECRET: private file of user1 (CVE-2026-97332 stand)
exit code 0

예상 출력 — 패치된 2.2.0 (포트 8081)

  [1/4] Fingerprinting the plugin via readme.txt ............ NOT VULNERABLE
  > user-private-files readme.txt reports Stable tag: 2.2.0 (patched, >= 2.2.0)
  > 2.2.0 moved the rewrite into upf-docs/.htaccess - direct GETs can no longer bypass the access check

  NOT VULNERABLE - plugin 2.2.0+ (patched)
exit code 6

--skip-check 사용 시(행위 탐지기):

  [3/4] Fetching the file with no authentication .............. 302 redirect
  [4/4] Verdict .................................................... REFUSED
  > direct GET was rewritten to http://localhost:8081/?file=victim-secret.txt (access check)
exit code 1

Lab 자격 증명 (로컬 스탠드 전용)

도구 다운로드