flawopen.com/path-traversal/Kotlin

● CWE-918 · 높음
보안 연구 · FlawOpen

취약점 분석: Path Traversal in Kotlin

How unvalidated file paths in Kotlin/Ktor allow directory breakouts, and how to verify boundaries using Path.normalize and Path.startsWith.

💡 알기 쉬운 설명 (ELI5)

호텔 2층 객실만 열 수 있도록 프로그래밍된 도어락을 비유로 들어보겠습니다. 만약 투숙객이 키패드에 '../../master-safe'를 입력하면, 결함이 있는 잠금장치가 복도를 벗어나 지배인의 메인 금고 문을 열어버리는 현상입니다.

핵심 개념 및 용어

Web Application Security
보안 개념 (Web Application Security): Core architecture component affected by CWE-918.
CWE-918
보안 개념 (CWE-918): Standard Common Weakness Enumeration classification for path-traversal-kotlin.
Defense-in-Depth
보안 개념 (Defense-in-Depth): Multi-layered engineering verification and runtime boundary isolation.

단계별 공격 실행 흐름

Step 1

클라이언트 파일 경로 입력 수신

엔드포인트가 HTTP 매개변수를 통해 사용자가 지정한 파일명이나 리소스 경로를 전달받습니다.

Step 2

디렉토리 탐색(Traversal) 시퀀스 삽입

공격자가 파일명에 '../', '..%2f' 등의 상대 경로 또는 절대 경로 우회 문자열을 삽입합니다.

Step 3

루트 디렉토리 경계 우회

백엔드가 표준화(Canonical) 경로 검증 없이 기본 디렉토리와 문자열을 결합하여 경계를 이탈합니다.

Step 4

임의 파일 유출 및 시스템 탈취

서버가 /etc/passwd, 환경 변수 파일 등 민감한 시스템 파일을 읽어 공격자에게 반환합니다.

소스 코드 비교: 취약한 구현 vs 보안 패치

✕ 취약한 구현
// VULNERABLE: Direct File instantiation with user parameter
import java.io.File;
import javax.servlet.http.*;

public class FileServlet extends HttpServlet {
    private static final File BASE_DIR = new File("/var/app/public/files");

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        String filename = req.getParameter("file");
        // Attacker sends: ../../../../etc/passwd
        File target = new File(BASE_DIR, filename);

        // Directly serves arbitrary system file!
        serveFile(target, resp);
    }
}
✓ 보안 강화 패치
// HARDENED: Canonicalize file and enforce directory prefix check
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import javax.servlet.http.*;

public class FileServlet extends HttpServlet {
    private static final File BASE_DIR = new File("/var/app/public/files");

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String filename = req.getParameter("file");
        if (filename == null || filename.isBlank()) {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        // 1. Resolve canonical path (resolves .. and all symlinks)
        File target = new File(BASE_DIR, filename).getCanonicalFile();
        File canonicalBase = BASE_DIR.getCanonicalFile();

        // 2. Strict boundary check using Java NIO Path
        if (!target.toPath().startsWith(canonicalBase.toPath())) {
            resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Path traversal detected");
            return;
        }

        if (!target.isFile()) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        serveFile(target, resp);
    }
}

엔지니어링 및 시스템 보안 강화 체크리스트

References