FlawOpen

flawopen.com/파일 포함/PHP include()

사용자 입력을 사용하는 PHP include()는 안전한가요?

CWE-98: PHP Remote File Inclusion참고 페이지
간단한 답변

아니요. include()는 선택된 파일을 PHP로 파싱하지만 영향은 도달 가능한 파일과 내용에 따라 달라집니다. 취약점은 wrapper를 통해 소스 코드를 노출하거나 의도하지 않은 로컬 파일을 포함할 수 있습니다. 공격자가 PHP 태그가 포함된 내용을 제어하고 애플리케이션이 해당 파일을 포함할 때만 공격자 제어 PHP가 실행됩니다. 원격 URL 포함도 PHP 설정에 달려 있습니다. 사용자가 선택한 키를 고정 허용 목록에 매핑하고 요청 데이터로 include 경로를 만들지 마세요.

취약
<?php
// User-controlled path: unintended local file
include $_GET['page'] . '.php';

// Example selector: ../../private/sample
// Impact depends on the resolved file and
// whether it contains PHP code to interpret.

// Remote URL inclusion requires the relevant
// URL wrapper and allow_url_include setting.

// Wrappers can change which resource is read;
// php://filter is a read filter, not a path
// containment check.
수정됨
<?php
// Allowlist: input selects a KEY,
// never a path or wrapper.
$pages = [
    'home'    => __DIR__ . '/pages/home.php',
    'about'   => __DIR__ . '/pages/about.php',
    'contact' => __DIR__ . '/pages/contact.php',
];

$key = $_GET['page'] ?? 'home';

if (!is_string($key) || !array_key_exists($key, $pages)) {
    http_response_code(404);
    exit;
}

include $pages[$key];

로컬 포함이 자동으로 코드 실행을 뜻하지 않는 이유

로컬 포함이 자동으로 코드를 실행하지는 않습니다. PHP는 포함된 파일을 파싱합니다. 유효한 PHP 태그 안의 문장은 포함한 컨텍스트에서 실행되고, 그 외 바이트는 출력될 수 있습니다. 코드 실행에는 공격자가 제어하는 PHP가 든 도달 가능한 파일 등 조건이 필요합니다. 업로드나 로그 쓰기 취약점이 통제된 PHP 내용을 나중에 포함될 파일에 기록하면 공격 체인이 될 수 있습니다.

php://filter는 스트림을 열 때 필터를 적용합니다. 취약한 읽기 또는 포함 흐름에서 적절한 필터는 소스 텍스트를 실행하지 않고 노출할 수 있습니다. 읽기 가능 여부는 wrapper, 권한, 설정에 달려 있습니다.

경로 정제가 해결책이 아닌 이유

../ 제거, 고정 확장자 추가, 디렉터리 접두사 추가만으로 최종 리소스가 허용됐다고 보장할 수 없습니다. 인코딩, 절대 경로, 플랫폼 규칙, wrapper가 해석을 바꿀 수 있습니다. 리소스 이름에 대한 사용자 제어를 없애고 애플리케이션이 정의한 고정 키만 허용하세요.

경로를 해석해야 한다면 realpath()로 정규화하고 Path Traversal 가이드처럼 디렉터리 경계를 확인하세요. 승인된 파일만 허용하고 심볼릭 링크와 검사-사용 경쟁 상태를 고려하세요. 고정 대상 허용 목록이 더 안전합니다.

자주 묻는 질문

allow_url_include=Off면 안전한가요?

이 설정은 include/require의 URL wrapper를 끕니다. PHP에서 기본값은 꺼짐이며 PHP 7.4부터 deprecated입니다. 원격 포함 위험을 줄이지만 로컬 traversal이나 안전하지 않은 로컬 include는 막지 못합니다. 끈 상태를 유지하고 대상에 대한 사용자 제어를 없애세요.

널 바이트 기법은 아직 가능한가요?

PHP 5.3.4에서 경로 처리의 널 바이트 종료 문제가 수정됐습니다. 오래된 기법이 통하지 않는다는 사실에 의존하지 마세요. 신뢰할 수 없는 include 대상은 여전히 위험합니다.

require, include_once, readfile은 어떤가요?

require와 _once 변형도 대상을 포함해 파싱합니다. 오류와 중복 처리 방식은 다르지만 신뢰할 수 없는 대상을 안전하게 만들지는 않습니다. readfile()과 file_get_contents()는 PHP로 파싱하지 않고 데이터를 반환하므로 신뢰할 수 없는 경로는 traversal이나 정보 노출을 일으킬 수 있습니다.

참고 자료

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