본문으로 건너뛰기

16. 유니코드 정규화로 Path Traversal 우회

경로 검사는 문자열 비교가 아니라 서로 다른 표현을 하나의 파일 이름으로 해석하는 과정의 문제다. 프록시가 한 번 디코딩하고 애플리케이션이 다시 디코딩하거나, 필터는 원문을 보고 라이브러리는 정규화된 값을 사용하면 두 계층이 서로 다른 경로를 검사한다. 이 문서는 승인된 실습 환경에서 그 불일치를 확인하는 방법과, 이를 없애는 설계를 다룬다.

정규화가 경계를 바꾸는 이유

NFKC는 호환 문자를 ASCII에 가까운 형태로 바꾼다. 예를 들어 FULLWIDTH SOLIDUS(U+FF0F, )는 일부 구현에서 /로 정규화될 수 있다. 반면 DIVISION SLASH(U+2215, )는 같은 결과를 보장하지 않는다. 따라서 “비슷하게 생겼다”는 이유만으로 우회 가능성을 주장하면 안 된다. 실제 런타임의 정규화 함수와 파일시스템 동작을 작은 테스트로 확인해야 한다.

검사 순서를 표로 만든다. 원시 바이트, URL 디코딩 결과, 유니코드 정규화 결과, 경로 결합 결과를 각각 저장하고 어느 단계에서 구분자가 생기는지 비교한다. 입력을 실제 파일에 쓰지 않고 임시 디렉터리에서 realpath 결과와 허용 루트의 관계만 관찰하면 된다.

실습에서 확인할 것

필터가 ../ 문자열만 찾는지, 디코딩을 몇 번 하는지, NFC와 NFKC 중 무엇을 적용하는지 확인한다. 프록시와 애플리케이션이 같은 규칙을 공유하지 않으면 취약성은 특정 페이로드보다 “두 번 해석되는 경로”로 재현된다. 테스트 결과에는 운영 시스템의 파일 내용 대신 허용 루트 밖으로 나갔는지와 응답 코드만 기록한다.

방어는 최종 경계를 검사한다

가장 안전한 흐름은 정해진 문자·인코딩 정책으로 입력을 해석한 뒤, 기준 디렉터리와 결합하고 canonical path를 계산하여 그 결과가 기준 디렉터리 내부인지 확인하는 것이다. 단순히 ..를 제거하면 의미가 달라진 파일명이 생기거나 우회 표현을 놓칠 수 있다. 파일명 allowlist, 심볼릭 링크 정책, 압축 해제 정책도 같은 경계에 포함한다.

정규화는 보안 기능이 아니라 해석 규칙이다. 어느 계층이 언제 무엇을 정규화하는지 명시하고, 그 규칙을 프록시·애플리케이션·라이브러리 테스트에 함께 고정할 때만 경로 검사가 설명 가능해진다.