1948 words
10 minutes
CVE-2026-52886 Analysis

Notepad++ session.xml backupFilePath 경로 순회 취약점

개요#

Notepad++가 세션을 복원할 때 session.xml의 backupFilePath를 정규화하지 않은 채 문자열 접두사만 검사해, 백업 디렉터리 밖의 임의 파일을 읽을 수 있는 취약점을 발견했습니다.

공격자가 session.xml에 백업 디렉터리로 시작하면서 ..\가 포함된 경로를 삽입하면 std::wstring::starts_with() 검사를 통과합니다. 이후 Windows가 실제 파일을 열 때 상위 디렉터리 참조를 해석하면서 사용자 권한으로 읽을 수 있는 파일이 Notepad++ 편집기 탭에 로드됩니다.

항목내용
영향 버전v8.9.7 미만 (CVE 레코드 기준, v8.9.6.2에서 검증)
패치 버전v8.9.7
심각도Medium — CVSS v4.0 5.1
CWECWE-22: Path Traversal
GHSAGHSA-rqfm-pw34-r7j6

공개 GHSA의 패키지 메타데이터와 연구 저장소에는 영향 범위가 < v8.9.6.4, 패치 버전이 v8.9.7로 함께 표기되어 있습니다. 본문은 최종 CVE 레코드의 영향 범위인 < v8.9.7을 기준으로 작성했습니다.


배경: Notepad++의 세션 복원#

Notepad++는 종료 당시 열려 있던 탭과 저장되지 않은 변경 사항을 session.xml에 기록합니다. 스냅샷 모드가 활성화되어 있으면 각 <File> 항목의 backupFilePath를 이용해 백업 파일을 다시 불러옵니다.

정상적인 backupFilePath는 Notepad++ 사용자 경로 아래의 backup 디렉터리를 가리켜야 합니다.

기존 검증 방식#

  1. session.xml에서 backupFilePath 속성을 읽음
  2. getUserPath() + "\\backup\\"으로 허용된 백업 경로를 생성
  3. 입력 경로가 허용 경로로 시작하는지 starts_with()로 검사
  4. 접두사가 일치하지 않으면 파일명만 추출해 백업 디렉터리 아래 경로로 재구성
  5. 검사를 통과한 경로를 세션 복원 정보에 저장하고 파일을 로드

문제는 3번 검사가 문자열에 적힌 경로만 보고, 운영체제가 실제로 해석할 정규화된 경로는 확인하지 않았다는 점입니다.


취약점 분석: starts_with() 접두사 검사 우회#

경로 문자열에는 동일한 위치를 여러 형태로 표현할 수 있습니다. 예를 들어 다음 두 값은 문자열로는 다르지만 Windows가 해석한 최종 위치는 같습니다.

C:\lab\npp\backup\..\..\..\Windows\System32\drivers\etc\hosts
C:\Windows\System32\drivers\etc\hosts

취약한 코드는 첫 번째 문자열이 C:\lab\npp\backup\으로 시작한다는 사실만 확인합니다. 따라서 접두사 검사는 통과하지만, 파일을 여는 시점에는 ..\가 해석되어 백업 디렉터리 밖의 파일에 접근하게 됩니다.

단계사용되는 경로결과
검사 (CHECK)원본 backupFilePath 문자열starts_with(backupDir) → 통과
사용 (USE)Windows가 정규화한 실제 경로..\ 해석 후 백업 디렉터리 밖의 파일을 엶

검사한 경로 표현과 실제 사용한 경로 표현이 다릅니다.

취약한 코드 (Parameters.cpp: 3191–3218)#

const wchar_t* pBackupFilePath = wmc.char2wchar(
NppXml::attribute(childNode, "backupFilePath"), CP_UTF8
);
std::wstring currentBackupFilePath =
NppParameters::getInstance().getUserPath() + L"\\backup\\";
if (pBackupFilePath)
{
std::wstring backupFilePath = pBackupFilePath;
if (!backupFilePath.starts_with(currentBackupFilePath))
{
// reconstruct backupFilePath
wchar_t* fn = ::PathFindFileNameW(pBackupFilePath);
currentBackupFilePath += fn;
pBackupFilePath = currentBackupFilePath.c_str();
}
}
sessionFileInfo sfi(
wstrFileName.c_str(), wstrLangName.c_str(), encoding,
isUserReadOnly, isPinned, isUntitleTabRenamed,
position, pBackupFilePath, fileModifiedTimestamp, mapPosition
);

backupFilePath.starts_with(currentBackupFilePath)가 참이면 경로 재구성 로직은 실행되지 않습니다. 공격자가 넣은 원본 경로가 그대로 sessionFileInfo에 전달되고, 이후 파일 I/O 과정에서 Windows가 ..\를 해석합니다.

패치 커밋을 기준으로 실제 취약 코드와 수정 지점은 PowerEditor/src/Parameters.cpp입니다.


공격 시나리오#

전제 조건#

  • session.xml 쓰기 권한
  • 스냅샷/백업 모드 활성화
  • 대상 파일을 Notepad++ 프로세스 권한으로 읽을 수 있어야 함
  • 세션 복원이 실행되는 Notepad++ 시작 과정

포터블 설치에서는 session.xml과 config.xml이 notepad++.exe와 같은 디렉터리에 있습니다. 스냅샷 모드가 이미 활성화되어 있지 않다면 config.xml을 수정할 권한도 필요합니다.

공격 흐름#

  1. session.xml의 <File> 항목에 조작된 backupFilePath를 삽입
  2. 경로 앞부분에 정상 백업 디렉터리를 배치하고 뒤에 ..\ 시퀀스를 추가
  3. Notepad++ 실행 후 세션 복원 시작
  4. 원본 문자열이 정상 백업 경로로 시작하므로 starts_with() 검사 통과
  5. Windows가 ..\를 해석해 백업 디렉터리 밖의 대상 파일을 엶
  6. 대상 파일 내용이 Notepad++ 편집기 탭에 표시됨

예시 페이로드는 다음과 같은 형태입니다.

<backupDir>\..\..\..\Windows\System32\drivers\etc\hosts

경로 해석 비교#

구분값
저장된 원본<backupDir>\..\..\..\Windows\System32\drivers\etc\hosts
접두사 검사<backupDir>\로 시작 → 통과
OS 정규화 결과C:\Windows\System32\drivers\etc\hosts
최종 결과백업 디렉터리 밖의 파일이 편집기 탭에 로드됨

영향#

항목내용
공격 유형임의 파일 읽기 (Path Traversal)
공격 위치Local
필요 권한Low — session.xml 쓰기 권한
사용자 상호작용Passive — 다음 실행 및 세션 복원 필요
관리자 권한 / UAC불필요 / 없음
노출 범위현재 사용자 권한으로 읽을 수 있는 파일
무결성 / 가용성 영향없음
CVSS v4.05.1 (AV:L/AC:L/AT:N/PR:L/UI:P/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N)

SSH 개인 키, .env, 애플리케이션 설정 파일, 자격 증명 관련 파일 등 Notepad++ 실행 사용자에게 읽기 권한이 있는 데이터가 편집기 탭에 노출될 수 있습니다.


PoC#

재현 절차와 PowerShell PoC는 다음 자료에 공개되어 있습니다.

실행 환경#

  • Notepad++ v8.9.6.2 portable x64
  • Windows 11 Pro x64
  • 포터블 모드를 위한 doLocalConf.xml
  • session.xml 쓰기 권한
  • 스냅샷 모드와 backup 디렉터리

검증 결과#

PoC에서 C:\Windows\System32\drivers\etc\hosts가 경로 순회를 통해 Notepad++ 편집기 탭에 로드되는 것을 확인했습니다.


패치 분석#

v8.9.7에 포함된 패치 커밋 7e66f36은 접두사를 검사하기 전에 PathCchCanonicalize()로 입력 경로를 정규화합니다.

const wchar_t* pBackupFilePath = wmc.char2wchar(
NppXml::attribute(childNode, "backupFilePath"), CP_UTF8
);
wchar_t normalizedBackupFilePath[MAX_PATH]{};
if (pBackupFilePath)
{
::PathCchCanonicalize(
normalizedBackupFilePath, MAX_PATH, pBackupFilePath
);
}
std::wstring currentBackupFilePath =
NppParameters::getInstance().getUserPath() + L"\\backup\\";
if (normalizedBackupFilePath[0])
{
std::wstring backupFilePath = normalizedBackupFilePath;
if (!backupFilePath.starts_with(currentBackupFilePath))
{
wchar_t* fn = ::PathFindFileNameW(normalizedBackupFilePath);
currentBackupFilePath += fn;
StringCchCopyW(
normalizedBackupFilePath,
MAX_PATH,
currentBackupFilePath.c_str()
);
}
}

정규화 과정에서 ..\가 먼저 제거되므로, 백업 디렉터리 밖을 가리키는 실제 경로는 더 이상 허용된 접두사로 시작하지 않습니다. 검사를 통과하지 못한 경로는 파일명만 유지한 채 정상 백업 디렉터리 아래로 재구성됩니다.

항목취약 버전v8.9.7 패치
검사 대상원본 경로 문자열PathCchCanonicalize() 결과
..\ 처리 시점파일 사용 시 OS가 처리접두사 검사 전에 처리
세션에 전달되는 값원본 pBackupFilePathnormalizedBackupFilePath
디렉터리 이탈 경로접두사가 맞으면 허용정규화 후 감지해 재구성

검사와 사용이 동일한 정규화 경로를 기준으로 동작하면서 접두사 검사 우회가 차단됩니다.


공개 타임라인#

날짜내용
2026-06-05연구 저장소에 기재된 취약점 공개일
2026-06-05 (UTC)패치 커밋 7e66f36 작성
2026-07-14Notepad++ v8.9.7 릴리즈 및 GHSA 공개
2026-08-17CVE-2026-52886 레코드 공개

마무리#

이번 취약점은 경로의 문자열 형태가 아니라 정규화된 최종 위치를 검증해야 한다는 원칙을 보여줍니다. starts_with() 같은 접두사 비교는 입력 경로에 ..\, 중복 구분자, 상대 경로 표현이 남아 있다면 보안 경계로 사용할 수 없습니다.

경로 검증과 실제 파일 접근이 같은 정규화 결과를 사용하도록 수정하면서 취약점이 해결됐습니다. 개발팀의 대응에 감사드립니다.

참고#

CVE-2026-52886 Analysis
https://blog.v3s9er.com/posts/cve-2026-52886-analysis/
Author
v3s9er
Published at
2026-09-18
License
CC BY-NC-SA 4.0