Skip to content
KitploitKITPLOIT
도구블로그
Log in
제출
도구블로그
제출

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

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

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
supernote-obsidian-plugin-PoC — PoC — Supernote Obsidian 플러그인의 악성 기기 동기화를 통한 경로 순회 (GHSA-3gx3-r874-5pp4, CVE-2026-86999, CVSS 5.6). | Kitploit
도구/GitHubGitHub/squeeze440/supernote-obsidian-plugin-poc
Vulnerability AnalysisExploitationWeb Application ExploitationSecurity VirtualizationPenetration TestingPapers & Research
GitHubsqueeze440/supernote-obsidian-plugin-poc

supernote-obsidian-plugin-PoC

PoC — Supernote Obsidian 플러그인의 악성 기기 동기화를 통한 경로 순회 (GHSA-3gx3-r874-5pp4, CVE-2026-86999, CVSS 5.6).

저장소 보기
1619일 전아직 검토되지 않음

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

요약

CVE 상태: 요청됨, 할당 대기 중. 이 취약점은 GHSA-3gx3-r874-5pp4로 공개되었습니다. CVE가 할당되면 이 저장소는 CVE-YYYY-NNNNN-supernote-obsidian-plugin-PoC로 이름이 변경되고 이 배너는 CVE 링크로 교체됩니다.

연구자Dostxodjayev Abdullox (@squeeze440)
권고GHSA-3gx3-r874-5pp4
CVSS 3.15.6 (Medium)
취약점 유형CWE-22, CWE-73

요약

Supernote (Unofficial) Obsidian 플러그인(philips/supernote-obsidian-plugin) v2.9.1의 기기 자동 동기화 기능에서 발생하는 경로 순회(Path Traversal) 취약점은 악의적이거나 변조된 "Supernote" 기기(또는 페어링된 기기의 IP를 사칭하는 경로상/근거리 네트워크 공격자)가 기기의 디렉터리 목록 응답에 조작된 uri 필드를 통해 피해자의 Obsidian 클라이언트가 데스크톱 프로세스가 쓸 수 있는 임의의 위치 — 설정된 동기화 폴더 외부 및 볼트 자체 외부를 포함하여 — 에 임의의 새 파일을 쓰도록 만들 수 있게 합니다.

제품

philips/supernote-obsidian-plugin("Supernote (Unofficial)")은 물리적 Supernote 전자잉크 기기의 노트를 기기의 로컬 "Browse and Access" HTTP 서버(http://<device-ip>:8089, 기기 기능 설계상 인증 없음)를 통해 볼트로 동기화하는 Obsidian.md 커뮤니티 플러그인입니다.

테스트 버전

  • 플러그인: v2.9.1, 커밋 48db5bf4dcfb100632c84699c830c1309da1abb9
  • 서브모듈 supernote-typescript: 커밋 195415b3a1f74147... (자체적으로는 관련 없음 — 버그는 전적으로 플러그인 자체의 동기화 계획 코드에 있음)
  • 호스트 앱: Obsidian 데스크톱 1.13.4 (실제 바이너리, 동적 테스트)

추정 CVSS v3.1

5.6 Medium — CVSS:3.1/AV:A/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N

  • AV:A — 기기의 HTTP 서버는 평문이고 인증이 없으며 단순 IPv4 주소로 구성됩니다(IP_VALIDATION_PATTERN, src/settings.ts:6). "기기"로서 이에 접근하려면 근거리 네트워크에 인접한 위치(로그 AP, ARP 스푸핑, 또는 IP 점유)가 필요하며, 임의의 원격 접근은 아닙니다.
  • AC:H — 익스플로잇은 피해자의 플러그인이 directConnectIP:8089와 통신할 때 공격자가 이미 해당 네트워크 위치를 점유하고 있어야 합니다. 일회성 원격 트리거가 아닙니다.
  • UI:R — 피해자가 공격자가 제어하는 엔드포인트를 향한 상태에서 "Sync supernote notes now"를 실행하거나(또는 자동 동기화 토글이 이미 활성화되어 있어야) 합니다.
  • S:C — 취약한 구성 요소는 볼트 범위의 Obsidian 플러그인이며, 쓰기는 볼트 외부의 기반 호스트 파일시스템에 발생하는데, 이는 다른 보안 범위입니다.
  • C:N — 쓰기 전용 프리미티브로, 공격자에게 읽혀 돌아오는 것은 없습니다.
  • I:H — 공격자가 제어하는 바이트가 공격자가 선택한 경로에 공격자가 선택한 파일명으로 기록됩니다. 제한적 단서: writeBinaryAt()(src/syncEngine.ts:40-47)은 플러그인 자체 동기화 매니페스트에 이미 추적되지 않은 경로에 대해서만 생성 분기를 취하며, Obsidian의 Vault.createBinary() 자체도 해석된 경로에 이미 물리적으로 존재하는 파일을 조용히 덮어쓰기를 거부합니다(경험적으로 확인됨 — PoC 참조) — 따라서 이는 "임의의 위치에 새 파일 심기"이지 "기존 파일 덮어쓰기"가 아닙니다.
  • A:N — 가용성 영향은 입증되지 않았습니다.

세부 사항

runDeviceSync()(src/syncEngine.ts:100-196)는 scanDeviceSupernoteTree()(src/FileListModal.ts:53-68)를 통해 페어링된 기기의 파일을 나열하는데, 이 함수는 기기 자체의 HTTP 디렉터리 목록을 재귀적으로 탐색하며 **name**이 /\.(note|spd)$/i와 일치하는 항목을 유지합니다(src/FileListModal.ts:46,63). 각 항목의 uri 필드 — 기기가 반환한 동일한 JSON 객체 내의 별도로 독립적으로 제어되는 문자열 — 는 name이나 다른 어떤 것과도 검증되지 않습니다.

그 원시 uri는 그대로 deviceUriToVaultPath()(src/deviceSync.ts:153-161)로 전달됩니다:

const INVALID_FILENAME_CHARS = /[\\:*?"<>|]/g;   // deviceSync.ts:144

export function deviceUriToVaultPath(syncFolder: string, deviceUri: string): string {
    const segments = deviceUri
        .split('/')
        .filter((s) => s.length > 0)
        .map((s) => s.replace(INVALID_FILENAME_CHARS, '_'));

    const cleanRoot = syncFolder.replace(/^\/+|\/+$/g, '');
    return cleanRoot ? `${cleanRoot}/${segments.join('/')}` : segments.join('/');
}

INVALID_FILENAME_CHARS는 \ : * ? " < > |를 제거하지만 .. 경로 세그먼트는 제거하거나 거부하지 않습니다. /../../PWNED.txt라는 기기 uri는 그대로 살아남아 설정된 동기화 폴더(기본값 "Supernote sync")에 결합되어 vaultPath = "Supernote sync/../../PWNED.txt"를 생성합니다.

syncEngine.ts:128은 목록의 원시 uri로부터 이 vaultPath를 계산한 다음, ensureFolder()/writeBinaryAt()(syncEngine.ts:146-148, 21-47)이 이를 순회 검사 없이 그대로 app.vault.getAbstractFileByPath() / createBinary() / modifyBinary()에 전달합니다. Obsidian 자체의 경로 해석은 그런 다음 디스크상의 실제 볼트 디렉터리에 대해 .. 세그먼트를 정규화하여, 쓰기가 동기화 폴더 위에 — 그리고 ../ 세그먼트가 충분하면 볼트 루트 전체 위에 — 호스트 파일시스템의 OS 사용자 권한 범위에 위치하게 됩니다.

형제 검사: 이 코드베이스의 다른 모든 볼트 쓰기 싱크(src/main.ts — 화면 미러 캡처, PDF/마크다운 가져오기, "attach to note", src/FileListModal.ts:245-246의 DownloadListModal)는 Obsidian 자체의 app.fileManager.getAvailablePathForAttachment(file.name)를 통해 대상 경로를 구성하며, 이는 기기의 name 필드만 소비하므로 이런 방식으로는 익스플로잇할 수 없습니다. 더 새로운 자동 동기화 기능의 deviceUriToVaultPath()는 대신 기기의 uri 필드로부터 경로를 직접 만들어내는 유일한 싱크이며, .. 정리(sanitization)를 건너뛴 바로 그 싱크입니다 — "모든 형제에서 검사되었지만 이것만 빠진" 명확한 사례입니다.

플러그인 자체 설정 UI에는 다음과 같이 명시되어 있습니다: "The sync command never writes anywhere outside this folder" (src/settings.ts, Sync folder 설명) — 이 PoC는 그 보장을 직접 반증합니다.

개념 증명

실제 수정되지 않은 Obsidian 1.13.4 데스크톱 바이너리(Xvfb + fluxbox + xdotool + scrot)에 대해 플러그인의 실제 컴파일된 main.js를 실행하여 종단 간 동적으로 확인했습니다 — 플러그인 코드의 모킹은 없습니다.

  1. 소스에서 플러그인을 빌드하고(./scripts/build) 새 테스트 볼트에 로드했습니다(Supernote (Unofficial) v2.9.1, "Trust author and enable plugins"를 통해 활성화).
  2. 플러그인 설정 Supernote IP address를 127.0.0.1로 설정하고, Sync folder는 기본값 Supernote sync로 두었습니다.
  3. 127.0.0.1:8089에서 기기의 "Browse and Access" 서버를 모방한 단일 파일 Node 목 서버를 세워 악의적/변조된 기기를 시뮬레이션했습니다. 이 서버의 디렉터리 목록은 다음을 반환합니다:
    {"name":"Quick notes.note","size":61,"date":"2026-07-31 00:00:00",
     "uri":"/../../PWNED_BY_DEVICE_SYNC.txt","extension":"note","isDirectory":false}
    
    (name은 .note 확장자 필터를 통과하고, uri는 순회를 담고 있습니다.) 모든 GET은 파일 바이트와 함께 200으로 응답됩니다 — 실제 HTTP 클라이언트는 나가는 요청 경로에서 ..를 정규화한 후 전송하지만(RFC 3986), 취약한 코드가 정규화된 요청 URL이 아니라 원본 JSON 문자열로부터 쓰기 경로를 계산하므로 여기서는 무관합니다.
  4. 명령 팔레트 작업 **"Supernote (Unofficial): Sync supernote notes now"**를 실행했습니다.
  5. 결과: 공격자의 바이트를 담은 새 파일 PWNED_BY_DEVICE_SYNC.txt가 볼트 루트 한 디렉터리 위(testvault/의 형제, 볼트 외부 전체)에 생성되었습니다. 플러그인 자체의 data.json은 계산된 경로를 그대로 기록했습니다: "vaultPath": "Supernote sync/../../PWNED_BY_DEVICE_SYNC.txt".

증거(실제 캡처, ~/engagements/supernote-obsidian-plugin/evidence/):

  • 01-vault-escape-file-write.png — 실제 터미널: ls -la testvault/ PWNED_BY_DEVICE_SYNC.txt로 파일이 볼트 디렉터리의 형제임을 보여주고, cat으로 공격자가 제어하는 내용을 보여줍니다.
  • 02-plugin-data-json-vaultpath.png — 플러그인 자체의 영속화된 동기화 상태 파일이 "vaultPath": "Supernote sync/../../PWNED_BY_DEVICE_SYNC.txt"를 기록한 모습.
  • 03-plugin-settings-security-claim.png — 동기화 명령이 "never writes anywhere outside this folder"라고 주장하는 플러그인 설정 UI.

영향

피해자가 구성한 Supernote 기기로서 응답할 수 있는 공격자(근거리 네트워크 인접: 로그 AP, ARP 스푸핑, 또는 기기의 IP 점유)는 피해자의 Obsidian 클라이언트가 데스크톱 프로세스가 쓸 수 있는 임의의 파일시스템 경로에 공격자가 제어하는 새 파일을 생성하도록 만들 수 있습니다 — 볼트 내부(예: .obsidian/plugins/<new-id>/ 아래의 완전히 새로운 파일로, 추가적인 플러그인 신뢰 악용의 발판 마련) 또는 완전히 외부(예: ~/.config/autostart/*.desktop, 새 crontab 드롭인, 또는 새 파일 — 덮어쓰기가 아닌 — 만으로 실행이나 지속성을 얻기에 충분한 다른 위치)에 생성할 수 있습니다. 해석된 경로에 이미 존재하는 파일을 조용히 덮어쓸 수는 없습니다(이 경우 Obsidian의 createBinary는 "File already exists"를 던지며, 경험적으로 확인됨). 이는 프리미티브를 보편적 덮어쓰기가 아닌 순수 신규 파일 심기로 제한합니다.

취약점 유형

  • CWE-22: 경로명의 제한된 디렉터리 제한 부적절 ('경로 순회')
  • CWE-73: 파일명 또는 경로의 외부 제어 (기기가 제공한 uri가 디스크상의 대상을 직접 결정)

완화 방안

deviceUriToVaultPath()(src/deviceSync.ts:153-161)에서 deviceUri를 분할한 후 ..(및 비어 있거나 .만 있는) 경로 세그먼트를 거부하거나 제거하십시오. 예:

const segments = deviceUri
    .split('/')
    .filter((s) => s.length > 0 && s !== '.' && s !== '..')
    .map((s) => s.replace(INVALID_FILENAME_CHARS, '_'));

추가로, scanDeviceSupernoteTree()(src/FileListModal.ts:63)는 목록 항목의 uri가 name과 일치하는지(예: uri가 동일한 파일명으로 끝나는지) 검증해야 하며, 두 필드를 독립적으로 신뢰해서는 안 됩니다 — 이는 코드베이스의 다른 모든 곳에서 이미 암묵적으로 의존하고 있는 동일한 종류의 방어로, getAvailablePathForAttachment()를 통해 name/basename으로만 경로를 구성합니다.

크레딧

Dostxodjayev Abdullox

신고 채널

이 저장소에는 SECURITY.md가 없습니다. GitHub 비공개 취약점 신고는 philips/supernote-obsidian-plugin에 대해 활성화되어 있음이 확인되었습니다(gh api repos/philips/supernote-obsidian-plugin/private-vulnerability-reporting --jq .enabled → true; 이전에 공개된 보안 권고 0건). 표준 GHSA 흐름이 적용됩니다: https://github.com/philips/supernote-obsidian-plugin/security/advisories/new.

도구 다운로드