경로 탐색(Path Traversal)은 디렉터리 탐색(Directory Traversal)이라고도 불리며, 공격자가 웹 애플리케이션을 통해 서버 내부의 임의 파일을 읽거나 쓸 수 있게 만드는 취약점입니다.
경로 탐색이란?
경로 탐색 취약점이 존재하면 공격자는 서버의 파일 시스템에서 의도하지 않은 파일에 접근할 수 있습니다. 접근 가능한 대상에는 애플리케이션 소스 코드 및 설정 파일, 데이터베이스 자격 증명과 같은 백엔드 시스템 정보, 그리고 /etc/passwd와 같은 민감한 운영 체제 파일이 포함됩니다.
더 나아가 일부 환경에서는 서버의 임의 파일에 쓰기까지 가능한 경우도 있는데, 이를 통해 애플리케이션 데이터나 동작을 변조하고, 최종적으로 서버 전체를 장악할 수도 있습니다.
기본 원리: 임의 파일 읽기
쇼핑 애플리케이션이 상품 이미지를 다음과 같은 HTML로 로드한다고 가정해 봅시다.
<img src="/loadImage?filename=218.png">
서버는 filename 파라미터의 값을 받아 기본 디렉터리인 /var/www/images/에 결합한 뒤 해당 파일을 읽어 반환합니다. 즉, 위 요청이 처리되면 서버는 아래 경로의 파일을 읽게 됩니다.
/var/www/images/218.png
여기서 문제는 경로 탐색에 대한 아무런 검증이 없다는 것입니다. 공격자가 filename 파라미터에 ../ 시퀀스를 삽입하면 디렉터리 구조를 상위로 이동할 수 있습니다.
https://insecure-website.com/loadImage?filename=../../../etc/passwd
이 요청이 서버에서 처리되면 다음과 같은 경로가 완성됩니다.
/var/www/images/../../../etc/passwd
../는 파일 시스템에서 한 단계 상위 디렉터리로 이동하는 것을 의미합니다. /var/www/images/에서 세 번 상위로 이동하면 파일 시스템 루트(/)에 도달하므로, 최종적으로 서버는 /etc/passwd 파일의 내용을 반환하게 됩니다.
참고로 Windows 환경에서는 ../와 ..\ 모두 유효한 디렉터리 탐색 시퀀스입니다. Windows 서버를 대상으로 하는 동등한 공격은 다음과 같은 형태가 됩니다.
https://insecure-website.com/loadImage?filename=..\..\..\windows\win.ini
실습 1: 기본적인 경로 탐색
이 랩에는 제품 이미지 표시 부분에 경로 탐색 취약점이 포함되어 있습니다. 랩을 해결하려면
/etc/passwd파일의 내용을 가져오세요.

메인 페이지로 이동한 뒤 임의의 상품 이미지를 클릭하면, 이미지가 다음과 같은 방식으로 로드되고 있음을 확인할 수 있습니다.

filename 파라미터의 값이 20.jpg로 설정되어 이미지를 가져오고 있습니다. 이 파라미터에 경로 탐색 시퀀스를 삽입해 보겠습니다.

filename 파라미터의 값을 ../../../etc/passwd로 설정한 뒤 전송하면, 서버 내부의 /etc/passwd 파일 내용이 응답으로 반환됩니다. 아무런 방어 기제가 없기 때문에 가장 기본적인 형태의 공격이 바로 성공한 것입니다.
경로 탐색 우회 기법
실제 환경에서는 많은 애플리케이션이 경로 탐색 공격에 대한 방어를 구현하고 있습니다. 그러나 이러한 방어 기제는 다양한 기법으로 우회할 수 있는 경우가 많습니다. 아래에서 대표적인 우회 기법들을 하나씩 살펴보겠습니다.
기법 1: 절대 경로 사용
일부 애플리케이션은 ../와 같은 상대 경로 탐색 시퀀스를 차단하지만, 파일 시스템 루트에서 시작하는 절대 경로는 허용하는 경우가 있습니다.
이 랩의 애플리케이션은 탐색 시퀀스를 차단하지만, 제공된 파일명을 기본 작업 디렉터리에 대한 상대 경로로 처리합니다. 랩을 해결하려면
/etc/passwd파일의 내용을 가져오세요.

메인 페이지에서 임의의 이미지 파일을 클릭하면, 이전 랩과 동일하게 filename 파라미터로 이미지를 불러오고 있습니다.

filename 파라미터의 값이 72.jpg로 설정되어 있습니다. 이번에는 ../ 탐색 시퀀스가 차단되어 있으므로, 절대 경로를 직접 입력합니다.

filename 파라미터 값을 /etc/passwd로 설정하면, 탐색 시퀀스 없이도 서버 내 파일의 내용이 그대로 반환됩니다. 애플리케이션이 상대 경로에서의 ../만 필터링하고 절대 경로 입력은 허용하고 있기 때문입니다.
기법 2: 중첩 탐색 시퀀스
일부 애플리케이션은 입력값에서 ../ 시퀀스를 탐지해 제거합니다. 하지만 이 제거가 비재귀적(한 번만 수행)으로 이루어진다면, 중첩된 시퀀스를 사용해 우회할 수 있습니다. 예를 들어 ....//에서 내부의 ../가 제거되면 다시 ../가 완성되는 원리입니다.
이 랩의 애플리케이션은 사용자가 제공한 파일명에서 경로 탐색 시퀀스를 제거한 뒤 처리합니다. 랩을 해결하려면
/etc/passwd파일의 내용을 가져오세요.
먼저 기본적인 ../../../etc/passwd를 전송해 보면, No such file이라는 응답이 돌아옵니다. 서버가 ../ 시퀀스를 제거하고 있다는 의미입니다.

이번에는 중첩 시퀀스인 ....//....//....//etc/passwd를 전송합니다. 서버가 내부의 ../를 한 번 제거하면 ....// → ../로 변환되어, 최종적으로 ../../../etc/passwd가 완성됩니다.

정상적으로 /etc/passwd 파일의 내용이 반환됩니다.
기법 3: URL 인코딩 및 이중 URL 인코딩
일부 환경에서는 웹 서버가 입력을 애플리케이션에 전달하기 전에 디렉터리 탐색 시퀀스를 제거합니다. 이는 URL 경로나 multipart/form-data 요청의 filename 매개변수에서 특히 흔합니다. 이 경우 ../ 문자를 URL 인코딩하면 정제 과정을 우회할 수 있습니다.
주요 인코딩 변환 방식은 다음과 같습니다.
| 원본 | 1차 URL 인코딩 | 2차 URL 인코딩(이중 인코딩) |
|---|---|---|
../ |
%2e%2e%2f |
%252e%252e%252f |
이 외에도 ..%c0%af나 ..%ef%bc%8f와 같은 비표준 인코딩이 동작하는 경우도 있습니다. Burp Suite Professional을 사용 중이라면, Burp Intruder의 사전 정의 페이로드 목록 중 Fuzzing - path traversal 항목에 다양한 인코딩 패턴이 준비되어 있으니 활용해 볼 수 있습니다.
이 랩의 애플리케이션은 경로 탐색 시퀀스가 포함된 입력을 차단한 뒤, URL 디코드를 수행합니다. 랩을 해결하려면
/etc/passwd파일의 내용을 가져오세요.
먼저 중첩 시퀀스인 ....//....//....//....//etc/passwd를 시도하면 No such file 응답이 나옵니다.

다음으로 1차 URL 인코딩인 %2e%2e%2f를 반복적으로 사용해 봐도 마찬가지로 No such file이 반환됩니다. 서버가 1차 URL 디코딩 후 탐색 시퀀스를 탐지해 차단하는 것으로 보입니다.

하지만 이중 인코딩인 %252e%252e%252f를 사용하면 결과가 달라집니다. 서버의 첫 번째 디코딩에서 %252e → %2e로 변환되고, 이 시점에서 탐색 시퀀스 필터링을 통과합니다. 이후 애플리케이션의 두 번째 디코딩에서 %2e → .로 변환되어 최종적으로 ../가 완성됩니다.

/etc/passwd 파일의 내용이 정상적으로 반환됩니다.
기법 4: 경로 시작 부분 검증 우회
일부 애플리케이션은 사용자가 제공한 파일명이 특정 기본 디렉터리로 시작하는지 검증합니다. 예를 들어 /var/www/images로 시작해야만 요청을 허용하는 식입니다. 이 경우, 필요한 기본 경로를 먼저 포함시킨 뒤 그 뒤에 탐색 시퀀스를 이어 붙이면 검증을 통과할 수 있습니다.
이 랩의 애플리케이션은 전체 파일 경로를 요청 매개변수로 전송하며, 제공된 경로가 예상되는 폴더로 시작하는지 검증합니다. 랩을 해결하려면
/etc/passwd파일의 내용을 가져오세요.
이미지 로드 요청을 확인하면, filename 파라미터에 /var/www/images/47.jpg처럼 서버 내부의 절대 경로 전체가 포함되어 있는 것을 볼 수 있습니다.

애플리케이션이 경로의 시작 부분(/var/www/images/)만 검증하고 이후의 탐색 시퀀스는 처리하지 않는다면, 다음과 같은 페이로드로 우회할 수 있습니다.
/var/www/images/../../../etc/passwd

경로가 /var/www/images/로 시작하므로 검증을 통과하고, 이후 ../../../에 의해 파일 시스템 루트로 이동하여 최종적으로 /etc/passwd가 읽히게 됩니다.
기법 5: 널 바이트를 이용한 파일 확장자 검증 우회
일부 애플리케이션은 파일명이 .png, .jpg와 같은 특정 확장자로 끝나는지 검증합니다. 이 경우 널 바이트(%00)를 사용하면 검증 로직에서는 확장자가 정상으로 보이지만, 파일 시스템 API에서는 널 바이트 이후의 문자열이 무시되어 파일 경로가 사실상 잘리는 효과를 얻을 수 있습니다.
이 랩의 애플리케이션은 제공된 파일명이 예상되는 파일 확장자로 끝나는지 검증합니다. 랩을 해결하려면
/etc/passwd파일의 내용을 가져오세요.
이중 URL 인코딩(%252e%252e%252f)을 시도하면 No such file 응답이 나옵니다. 이 랩에서는 해당 기법이 통하지 않습니다.

대신 널 바이트를 활용합니다. ../../../etc/passwd%0035.jpg를 전송하면, 검증 로직에서는 파일명이 .jpg로 끝나는 것으로 판단하지만, 파일 시스템에서는 %00(널 바이트) 이후의 35.jpg가 무시되어 실제로는 /etc/passwd만 읽히게 됩니다.

/etc/passwd 파일의 내용이 성공적으로 반환됩니다.
우회 기법 요약
| 방어 기제 | 우회 기법 | 페이로드 예시 |
|---|---|---|
| 탐색 시퀀스 차단 (상대 경로) | 절대 경로 사용 | /etc/passwd |
비재귀적 ../ 제거 |
중첩 시퀀스 | ....//....//....//etc/passwd |
| URL 디코딩 후 필터링 | 이중 URL 인코딩 | %252e%252e%252f 반복 + etc/passwd |
| 경로 시작 부분 검증 | 기본 경로 포함 후 탐색 | /var/www/images/../../../etc/passwd |
| 파일 확장자 검증 | 널 바이트 삽입 | ../../../etc/passwd%00.png |
경로 탐색 공격 방지 방법
경로 탐색 취약점을 방지하는 가장 근본적인 방법은 사용자 입력을 파일 시스템 API에 직접 전달하지 않는 것입니다. 기존에 사용자 입력을 기반으로 파일을 참조하던 기능은 더 안전한 대안으로 재설계할 수 있는 경우가 많습니다.
만약 사용자 입력을 파일 시스템 API에 전달하는 것이 불가피하다면, 다음 두 가지 계층의 방어를 함께 적용하는 것을 권장합니다.
첫째, 입력 검증을 수행합니다. 가장 이상적인 방법은 허용된 파일명의 화이트리스트를 정의하고, 사용자 입력이 이 목록에 포함되는지 확인하는 것입니다. 화이트리스트 방식이 불가능하다면, 최소한 입력값이 영숫자와 같은 안전한 문자만 포함하는지 검증해야 합니다.
둘째, 경로 정규화 후 검증합니다. 사용자 입력을 기본 디렉터리에 결합한 뒤, 플랫폼의 파일 시스템 API를 사용하여 경로를 정규화(canonicalize)하고, 정규화된 결과가 기대하는 기본 디렉터리로 시작하는지 확인합니다.
아래는 Java에서 이 두 가지 방어를 결합한 코드 예시입니다.
File file = new File(BASE_DIRECTORY, userInput);
if (file.getCanonicalPath().startsWith(BASE_DIRECTORY)) {
// 파일 처리
}
getCanonicalPath()는 ../와 같은 탐색 시퀀스를 모두 해석하여 실제 절대 경로를 반환합니다. 따라서 공격자가 어떤 우회 기법을 사용하더라도, 정규화된 경로가 지정된 기본 디렉터리 내에 있는지 최종적으로 확인할 수 있습니다.
Comments
Sign in with GitHub to leave a comment.