설명
경로 탐색(Path Traversal) 취약점은 애플리케이션이 사용자 입력을 파일 경로로 사용하면서 허용된 디렉터리 경계를 확인하지 않을 때 발생합니다. 공격자는 상대 경로(../ 또는 ..\\)를 이용하여 시스템의 민감한 파일에 접근할 수 있습니다.
잠재적 영향
영향은 애플리케이션 계정의 파일 권한과 읽기·쓰기 기능에 따라 달라집니다.
- 민감한 파일 노출: 시스템 파일이나 다른 사용자의 데이터에서 중요 정보를 읽을 수 있습니다.
- 권한 우회: 다른 사용자의 데이터나 설정 파일을 무단으로 읽거나 수정할 수 있습니다.
- 원격 코드 실행: 애플리케이션이 읽은 파일을 실행하는 경우 악성 코드 실행이 가능할 수 있습니다.
해결 방법
- 사용자 입력값을 직접 파일 경로로 사용하지 않기
- 사용자 입력을 검증하고, 허용된 디렉터리 내에서만 파일을 접근할 수 있도록 제한합니다.
- 파일 경로 검증 수행하기
Path.resolve()로 경로를 결합한 뒤 정규화하고 허용된 기준 경로와 비교하세요.resolve()만으로 디렉터리 경계를 확인할 수는 없습니다.- 정규화된
Path를 구성 요소 단위로 비교하고, 실제 파일을 읽을 때는toRealPath()로 심볼릭 링크까지 해석한 경로가 허용 디렉터리 안에 있는지 다시 확인합니다. - 공격자가 기준 디렉터리를 변경할 수 있다면 검사와 사용 사이의 심볼릭 링크 교체를 막기 위해 디렉터리 권한을 제한하고
SecureDirectoryStream같은 디렉터리 상대 API를 고려합니다.
- 파일 이름을 안전한 값으로 변환하여 사용하기
- UUID 또는 해시 기반의 안전한 파일명을 생성하여 사용합니다.
예시
변경 전
java
private static final String BASE_PATH ="/restricted/directory";
public String readFile(String filePath) throws IOException {
Path file = Paths.get("/restricted/directory/" + filePath);
return new String(Files.readAllBytes(file));
}
File(parent, child) 생성자의 두 번째 인자도 경로의 일부입니다. 기준 디렉터리가 고정되어 있어도 신뢰할 수 없는 child에 .. 경로 구성 요소가 포함되면 정규화 과정에서 기준 디렉터리를 벗어날 수 있습니다.
java
import java.io.File;
import javax.servlet.http.HttpServletRequest;
public final class UnsafeUploadPath {
private static final File UPLOAD_DIR = new File("/var/app/uploads");
public File destination(HttpServletRequest request) {
String child = request.getParameter("name");
// 취약: 두 번째 생성자 인자도 공격자가 제어하는 경로입니다.
return new File(UPLOAD_DIR, child);
}
}
변경 후
다음 Java 11 이상 예시는 공격자가 기준 디렉터리와 그 내부 경로를 바꿀 수 없는 환경을 전제로 합니다. 파일 크기 제한과 잘못된 입력에 대한 오류 응답은 별도로 적용하세요.
java
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
public final class SafeFileReader {
private static final Path BASE_PATH = Path.of("/restricted/directory");
public String readFile(String filePath) throws IOException {
Path base = BASE_PATH.toRealPath();
Path supplied = Path.of(filePath);
if (supplied.isAbsolute()) {
throw new SecurityException("Absolute paths are not allowed.");
}
Path candidate = base.resolve(supplied).normalize();
if (!candidate.startsWith(base)) {
throw new SecurityException("Unauthorized access attempt detected.");
}
// 기존 파일의 심볼릭 링크까지 해석한 실제 대상도 기준 디렉터리 안이어야 합니다.
Path requestedPath = candidate.toRealPath();
if (!requestedPath.startsWith(base)
|| !Files.isRegularFile(requestedPath, LinkOption.NOFOLLOW_LINKS)) {
throw new SecurityException("Unauthorized access attempt detected.");
}
return Files.readString(requestedPath, StandardCharsets.UTF_8);
}
}
설명:
- 변경 전: 신뢰할 수 없는 사용자 입력을 경로로 직접 사용하는 것은 위험합니다. 공격자는
../를 사용하여 상위 디렉터리로 나가 제한된 영역 밖의 파일에 접근할 수 있습니다. 이 위험은new File(parent, child)의child인자에도 동일하게 적용됩니다. - 변경 후: 절대 경로를 거부하고
Path.startsWith(Path)로 정규화된 경로 구성 요소를 비교합니다. 이어서toRealPath()로 심볼릭 링크를 해석한 실제 대상도 정규화된 기준 디렉터리 안에 있는지 확인한 뒤, 검증한requestedPath자체를 읽습니다.
관련 CVE
- CVE-2024-37032: Large language model (LLM) management tool does not validate the format of a digest value (CWE-1287) from a private, untrusted model registry, enabling relative path traversal (CWE-23), a.k.a. Probllama
- CVE-2024-4315: Chain: API for text generation using Large Language Models (LLMs) does not include the "\" Windows folder separator in its denylist (CWE-184) when attempting to prevent Local File Inclusion via path traversal (CWE-22), allowing deletion of arbitrary files on Windows systems.
- CVE-2022-45918: Chain: a learning management tool debugger uses external input to locate previous session logs (CWE-73) and does not properly validate the given path (CWE-20), allowing for filesystem path traversal using "../" sequences (CWE-24)