FlawOpen

flawopen.com/경로 탐색/Python os.path.join()

Python의 os.path.join()은 경로 탐색을 막을까요?

CWE-22: Path Traversal참고 페이지
짧은 답변

아니요. 기준 디렉터리를 방이라고 하면 ..은 부모 방을 거쳐 밖으로 나가고, 절대 경로는 새 주소라서 os.path.join()이 시작 방을 버립니다. 파일을 열기 전에 최종 경로를 해석하고 허용된 디렉터리 안인지 확인하세요.

VULNERABLE
import os

base_dir = "/srv/uploads"
user_filename = "../../outside/demo.txt"
path = os.path.join(base_dir, user_filename)
# Relative traversal can leave the base.

path = os.path.join(base_dir, "/outside/demo.txt")
# An absolute segment discards base_dir.
SECURE — resolve and check containment
import os

def resolve_under_base(base_dir, user_filename):
    if not isinstance(user_filename, str) or not user_filename:
        raise ValueError("Invalid filename")

    base = os.path.realpath(base_dir)
    candidate = os.path.realpath(
        os.path.join(base, user_filename)
    )

    try:
        inside = os.path.commonpath((base, candidate)) == base
    except ValueError:  # e.g. different Windows drives
        inside = False

    if not inside or candidate == base:
        raise ValueError("Invalid filename")
    return candidate

고려할 두 가지 경로 결합 동작

..은 상대 경로를 의도한 디렉터리 밖으로 이동시킬 수 있습니다. Python 문서에 따르면 절대 경로 구성 요소는 os.path.join()이 앞선 구성 요소를 버리게 합니다. Windows의 드라이브와 루트 규칙은 POSIX와 다릅니다. pathlib.Path(base) / user_input도 같습니다. 결합과 정규화는 권한 검사가 아닙니다.

commonpath()가 문자열 접두사보다 안전한 이유

비교 전에 신뢰 기준과 후보 경로를 해석하세요. os.path.commonpath()는 문자 접두사가 아니라 경로 구성 요소를 비교하며 루트나 드라이브가 맞지 않으면 ValueError를 발생시킵니다. 해당 입력을 거부하세요. realpath()는 검사 당시 링크를 해석하지만 이후 변경은 막지 못합니다. 다른 사용자가 트리를 수정할 수 있다면 적절한 디스크립터 접근과 no-follow를 사용하세요.

자주 묻는 질문

pathlib도 같은 방식인가요?

네. Path의 / 연산자는 다음 경로가 절대 경로이면 앞선 구성 요소를 버립니다. 결과를 해석해 허용 기준 안인지 확인하세요.

realpath()가 경쟁 조건을 막나요?

아니요. 검증 당시 링크만 해석하며 다른 프로세스가 파일을 열기 전에 변경할 수 있습니다. 쓰기 권한을 제한하거나 지원되는 경우 no-follow 디스크립터 상대 작업을 사용하세요.

참고 자료

함께 보기 경로 조작(CWE-22): 동작 원리와 모든 언어에서의 예방법 — flawopen.com Zip Slip 취약점이란? Node.js path.join()은 Path Traversal을 막지 못합니다 PHP include()와 사용자 제어 경로