[Write-Up] PortSwigger - File Path Traversal, Traversal Sequences Stripped With Superfluous URL-Decode
목차
문제 분석
난이도:
PRACTITIONER
Lab: File path traversal, traversal sequences stripped with superfluous URL-decode
상품 이미지 표시 기능에 path traversal 취약점이 존재한다. 애플리케이션이 경로 이탈 시퀀스가 포함된 입력을 차단하지만, 그 입력을 사용하기 전에 URL 디코딩을 한 번 더 수행한다. /etc/passwd 파일의 내용을 가져오면 문제가 해결된다.
Path Traversal 진단
이미지 요청 형태를 확인한다.
GET /image?filename=8.jpg HTTP/2
Host: <lab-id>.web-security-academy.net
앞선 랩들에서 사용한 페이로드를 순서대로 시도한다.
| 시도 | 결과 | 해석 |
|---|---|---|
../8.jpg | 경로 이탈이 반영되지 않음 | ../가 검증에 걸림 |
....// | 동일 | 중첩 시퀀스도 무력화됨 |
%2e%2e%2f | 동일 | 디코딩된 뒤 검증되므로 효과 없음 |
세 번째 결과가 중요하다. 단일 URL 인코딩이 통하지 않는 이유는 필터가 강해서가 아니라, 웹 서버와 프레임워크가 쿼리 문자열을 애플리케이션에 넘기기 전에 이미 디코딩하기 때문이다. 필터가 값을 받아 볼 시점에는 %2e%2e%2f가 이미 ../로 복원되어 있다.
그런데 문제 설명은 애플리케이션이 입력을 사용하기 전에 URL 디코딩을 한 번 더 수행한다고 밝히고 있다. 즉 값이 디코딩되는 지점이 두 곳인데 검증은 그 사이에 한 번만 놓여 있다. 이 구조에서는 검증 시점에 인코딩된 상태로 남아 있다가 검증 이후 디코딩에서 복원되는 값을 만들 수 있다.
| 단계 | 입력값 %252e%252e%252f의 상태 |
|---|---|
| HTTP 계층 디코딩 후 | %2e%2e%2f |
| 필터 검증 시점 | %2e%2e%2f — 경로 이탈 시퀀스가 보이지 않아 통과 |
| 애플리케이션의 추가 디코딩 후 | ../ |
| 파일 API 전달 | ../ 로 경로 이탈 성립 |
익스플로잇
../를 두 번 URL 인코딩해 페이로드를 구성한다. %2e가 ., %2f가 /이므로 각 문자의 %를 다시 %25로 인코딩하면 된다.
GET /image?filename=%252e%252e%252f%252e%252e%252f%252e%252e%252fetc/passwd HTTP/2
Host: <lab-id>.web-security-academy.net
요청을 전송하면 응답 본문에 /etc/passwd의 내용이 포함되며 문제가 해결된다.

정리
이 랩의 결함은 필터의 성능이 아니라 위치에 있다. 필터는 자기가 받은 문자열에 대해 정확히 판단했지만, 그 문자열은 파일 API가 최종적으로 받는 문자열과 같지 않았다. 검증 시점과 해석 시점 사이에 변환이 하나 더 끼어 있는 순간, 검증은 실제로 사용될 값이 아니라 그 값의 중간 표현을 본 것이 된다.
이 구조는 path traversal에 국한되지 않는다. WAF가 검사한 요청과 백엔드가 파싱한 요청이 달라지는 WAF 우회, 프론트엔드와 백엔드가 요청 경계를 다르게 읽는 HTTP request smuggling, 필터가 본 문자열과 브라우저 파서가 본 문자열이 달라지는 XSS 필터 우회가 모두 같은 계열이다. 공통 원인은 하나의 입력을 서로 다른 두 컴포넌트가 서로 다른 규칙으로 해석한다는 데 있다.
같은 계열의 변형 인코딩도 함께 정리해 둘 만하다. 다만 아래 항목들은 대상 컴포넌트가 해당 인코딩을 받아들일 때만 성립하므로, 통하지 않았다고 해서 필터가 안전하다는 뜻은 아니다.
| 형태 | 복원 결과 | 성립 조건 |
|---|---|---|
%2e%2e%2f | ../ | 디코딩이 검증 이후에 일어남 |
%252e%252e%252f | ../ | 디코딩이 검증 전후로 두 번 일어남 |
..%c0%af | ../ | 오버롱 UTF-8을 허용하는 레거시 디코더 |
..%u2215 | ../ | %u 표기를 처리하는 비표준 파서 |
%2e%2e/ | ../ | 부분 인코딩. 문자열 비교식 필터에 유효 |
방어는 인코딩 형태를 하나씩 열거해 막는 방향으로는 닫히지 않는다. 디코딩을 필요한 곳에서 한 번만 수행하고, 검증은 애플리케이션이 실제로 사용할 최종 값에 대해서만 수행해야 한다. 그리고 그 검증은 문자열 패턴 검사가 아니라, 조립된 경로를 정규화한 뒤 기준 디렉터리 하위인지 확인하는 형태여야 한다. 정규화 이후의 경로에는 인코딩이 남아 있지 않으므로 이 계열의 우회가 애초에 성립하지 않는다.
