파일 삽입 취약점이란? LFI와 RFI의 차이점 쉽게 이해하기

파일 삽입 취약점이 발생하는 원리를 살펴보고 LFI와 RFI의 차이, 경로 조작 취약점과의 구분, 안전한 파일 호출 방법을 알아봅니다.

예전 웹사이트에서는 하나의 주소로 여러 화면을 불러오기 위해 URL 파라미터에 페이지 이름을 전달하는 방식을 자주 사용했습니다. 주소 뒤에 page=noticemenu=contact 같은 값이 붙고, 서버는 이 값을 이용해 해당 파일을 불러오는 구조입니다.

문제는 사용자가 전달한 값을 서버에서 그대로 include()require() 같은 함수에 넣을 때 발생합니다. 공격자가 페이지 이름 대신 다른 파일의 경로나 외부 주소를 입력하면 개발자가 의도하지 않은 파일이 프로그램에 포함될 수 있습니다.

이러한 문제를 파일 삽입 취약점(File Inclusion Vulnerability)이라고 합니다. 불러오는 대상에 따라 LFI와 RFI로 나뉘며, 두 방식은 이름은 비슷하지만 공격에 사용되는 파일의 위치와 발생 조건이 다릅니다.

파일 삽입 취약점이란?

파일 삽입 취약점은 외부 입력값으로 프로그램이 포함하거나 실행할 파일을 결정할 때 발생합니다.

정상적인 기능에서는 사용자가 메뉴를 선택하면 서버가 미리 준비된 페이지 파일을 불러옵니다. 하지만 서버가 입력값을 신뢰하면 사용자가 애플리케이션 내부 파일이나 원격 서버의 파일을 임의로 지정할 수 있습니다.

다음과 같은 주소를 사용하는 웹페이지를 생각해 보겠습니다.

/index.php?page=notice

서버는 page 값을 받아 notice.php 파일을 포함할 수 있습니다. 정상적인 요청에서는 문제가 없어 보이지만, page 값이 파일 경로나 URL을 직접 결정한다면 파일 삽입 취약점으로 이어질 가능성이 있습니다.

파일 삽입 취약점의 핵심은 다음과 같습니다.

사용자가 프로그램에 포함될 파일을 직접 선택할 수 있는 것입니다.

취약한 PHP 코드 살펴보기

파일 삽입 취약점은 PHP의 include(), require(), include_once(), require_once() 함수와 관련해 자주 설명됩니다.

<?php
$page = $_GET['page'];

include($page . '.php');
?>

이 코드는 URL의 page 파라미터를 받아 파일명으로 사용합니다. 사용자가 notice를 입력하면 서버는 다음 파일을 불러옵니다.

notice.php

문제는 $page가 외부에서 전달되는 값인데도 허용된 페이지인지 확인하지 않는다는 점입니다.

코드의 흐름을 따라가면 취약한 지점을 쉽게 찾을 수 있습니다.

URL 파라미터
→ $page 변수
→ 문자열 결합
→ include() 함수

외부 입력값이 파일 포함 함수까지 별도의 검증 없이 전달되고 있습니다. 이 구조에서는 사용자가 원래 제공할 예정이 없었던 파일을 지정하거나 경로를 조작할 가능성이 있습니다.

파일 확장자를 코드에서 자동으로 붙인다고 해서 안전해지는 것도 아닙니다. 서버 환경과 입력값 처리 방식, 사용 가능한 경로 표현에 따라 예상하지 못한 파일이 선택될 수 있으므로 허용된 페이지 목록을 별도로 관리해야 합니다.

LFI란?

LFI(Local File Inclusion)는 서버 내부에 존재하는 로컬 파일을 프로그램에 포함시키는 취약점입니다. 우리말로는 로컬 파일 삽입 또는 지역 파일 삽입이라고 표현합니다.

공격 대상 파일은 웹서버 내부에 이미 존재합니다. 애플리케이션 설정파일, 로그파일, 임시파일, 세션파일, 업로드된 파일 등이 대상이 될 수 있습니다.

LFI는 다음과 같은 조건에서 발생할 가능성이 있습니다.

  • 사용자가 파일명이나 경로를 직접 입력할 수 있는 경우
  • 입력값이 include() 또는 require()에 전달되는 경우
  • 상위 디렉터리 이동을 제한하지 않는 경우
  • 기준 디렉터리 검증이 없는 경우
  • 업로드 파일과 실행 가능한 파일이 같은 영역에 있는 경우
  • 웹서비스 계정에 불필요하게 넓은 파일 접근권한이 있는 경우

LFI가 항상 즉시 코드 실행으로 이어지는 것은 아닙니다. 포함되는 파일의 내용, 프로그램이 파일을 처리하는 방식, 서버 설정에 따라 결과가 달라집니다.

단순히 설정정보나 소스 코드가 노출되는 경우도 있고, 특정 조건이 함께 충족되면 서버에서 코드가 실행되는 더 심각한 문제로 이어질 수도 있습니다.

RFI란?

RFI(Remote File Inclusion)는 외부 서버에 존재하는 원격 파일을 웹 애플리케이션에 포함시키는 취약점입니다. 원격 파일 삽입이라고 부릅니다.

LFI가 서버 내부 파일을 대상으로 한다면 RFI는 HTTP나 HTTPS 주소처럼 원격 위치의 파일을 불러온다는 차이가 있습니다.

RFI가 성립하려면 단순히 외부 입력값을 include()에 전달하는 것만으로는 부족합니다. 사용 중인 언어와 서버 설정이 URL을 이용한 원격 파일 포함을 허용해야 합니다.

PHP에서는 원격 URL을 파일처럼 사용할 수 있게 하는 설정과 파일 포함 관련 설정이 영향을 줍니다. 최신 환경에서는 보안을 이유로 원격 파일 포함 기능이 제한돼 있는 경우가 많습니다. 하지만 오래된 서버나 잘못 구성된 환경에서는 여전히 점검이 필요합니다.

RFI가 성공하면 외부에서 준비한 파일이 서버 애플리케이션의 실행 흐름에 포함될 수 있어 위험합니다. 공격자가 서버 내부에 파일을 미리 저장하지 않아도 원격 파일을 호출할 수 있다는 점에서 LFI와 차이가 있습니다.

LFI와 RFI의 차이점

구분LFIRFI
전체 이름Local File InclusionRemote File Inclusion
대상 파일서버 내부의 로컬 파일외부 서버의 원격 파일
주요 입력값파일명, 상대경로, 절대경로외부 URL 또는 원격 경로
주요 발생 조건내부 경로를 외부 입력으로 결정원격 파일 포함 기능이 허용된 환경
주요 피해설정정보·소스·로그 노출, 조건부 코드 실행외부 코드 포함 및 서버 명령 실행 가능성
대응 핵심기준 경로 검증과 허용 목록원격 포함 차단과 허용 목록

두 취약점 모두 외부 입력값으로 포함할 파일을 결정한다는 공통점이 있습니다. 차이는 파일이 어디에 존재하느냐입니다.

  • 서버 내부 파일을 불러오면 LFI
  • 외부 서버의 파일을 불러오면 RFI

실제 점검에서는 입력값만 보고 바로 LFI나 RFI로 단정하기보다, 서버가 어떤 경로 형식을 허용하고 파일을 어떤 방식으로 해석하는지까지 확인해야 합니다.

경로 조작과 파일 삽입은 무엇이 다를까?

파일 삽입 취약점은 경로 조작이나 디렉터리 트래버설과 함께 나타나는 경우가 많아 혼동하기 쉽습니다.

경로 조작은 사용자가 파일 경로를 바꿔 허용되지 않은 위치에 접근하는 문제입니다. 목적은 파일 읽기, 저장, 삭제 또는 복사 등으로 다양합니다.

파일 삽입은 선택된 파일이 프로그램의 실행 흐름에 포함된다는 점에 초점이 있습니다. PHP의 include()처럼 파일 내용을 프로그램 코드의 일부로 처리하는 기능이 대표적입니다.

예를 들어 허용되지 않은 설정파일의 내용을 다운로드했다면 경로 조작에 가깝습니다. 같은 파일이 include() 함수로 전달돼 프로그램에 포함된다면 파일 삽입 취약점으로 볼 수 있습니다.

정리하면 다음과 같습니다.

경로 조작
→ 허용되지 않은 파일 위치에 접근

파일 삽입
→ 선택한 파일을 프로그램에 포함

두 취약점은 서로 별개이지만, 디렉터리 트래버설을 이용해 로컬 파일을 선택한 뒤 LFI로 이어지는 것처럼 함께 발생할 수 있습니다.

LFI로 발생할 수 있는 피해

LFI가 발생하면 웹 애플리케이션 계정이 접근할 수 있는 범위 안에서 서버 내부 파일이 노출될 수 있습니다.

대표적인 피해는 다음과 같습니다.

  • 애플리케이션 설정파일 노출
  • 데이터베이스 접속정보 유출
  • 서버의 로그파일 열람
  • 웹 애플리케이션 소스 코드 노출
  • 다른 사용자의 세션정보 노출
  • 업로드 파일 또는 임시파일 포함
  • 서버 내부 디렉터리 구조 노출
  • 특정 조건에서 서버 코드 실행

서버 설정파일에는 데이터베이스 비밀번호, API 키, 암호화 키처럼 중요한 정보가 포함될 수 있습니다. 따라서 직접적인 코드 실행이 발생하지 않더라도 다른 시스템을 공격하는 데 필요한 정보가 유출될 수 있습니다.

LFI의 위험도는 단순히 “어떤 파일을 읽을 수 있는가”만으로 판단해서는 안 됩니다. 업로드 기능, 로그 기록 방식, 세션 저장 위치와 결합됐을 때 더 큰 피해로 이어질 수 있는지도 함께 확인해야 합니다.

RFI로 발생할 수 있는 피해

RFI는 외부 서버의 파일이 애플리케이션에 포함되는 구조이므로 공격자가 파일 내용을 직접 구성할 수 있다는 점이 위험합니다.

발생할 수 있는 피해는 다음과 같습니다.

  • 외부 악성 코드 실행
  • 웹셸 설치
  • 서버 내부 파일 변경
  • 데이터베이스 정보 탈취
  • 추가 악성파일 다운로드
  • 서버 권한을 이용한 내부망 접근
  • 서비스 장애와 시스템 장악

다만 서버에서 원격 파일 포함 기능이 비활성화돼 있다면 같은 입력값을 전달해도 RFI가 성립하지 않을 수 있습니다.

취약 여부를 판단할 때는 소스 코드뿐 아니라 PHP 설정, 웹서버 구성, 실행 계정의 권한을 함께 살펴봐야 합니다.

가장 안전한 대응은 사용자가 파일을 선택하지 못하게 하는 것

파일 삽입 취약점을 예방하는 가장 좋은 방법은 외부 입력값을 파일 경로에 직접 사용하지 않는 것입니다.

페이지 이름을 전달받아야 한다면 사용자가 입력한 값을 실제 파일명으로 연결하지 말고, 서버가 미리 준비한 목록에서 선택하도록 구성해야 합니다.

<?php
$allowedPages = [
    'home' => __DIR__ . '/pages/home.php',
    'notice' => __DIR__ . '/pages/notice.php',
    'contact' => __DIR__ . '/pages/contact.php'
];

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

if (!isset($allowedPages[$page])) {
    http_response_code(404);
    exit('페이지를 찾을 수 없습니다.');
}

require $allowedPages[$page];
?>

사용자는 home, notice, contact 중 하나를 요청할 수 있지만 실제 파일 경로는 서버 내부에서 결정합니다.

외부 입력값과 파일 경로 사이에 허용 목록이 존재하기 때문에 사용자가 임의의 로컬 경로나 외부 URL을 지정할 수 없습니다.

이 방식에서는 다음 두 가지가 중요합니다.

  1. 허용할 키를 코드나 안전한 설정으로 관리합니다.
  2. 실제 파일 경로는 사용자가 아닌 서버가 결정합니다.

basename만 사용하면 충분할까?

파일 경로에서 디렉터리 부분을 제거하기 위해 basename() 함수를 사용하는 경우가 있습니다.

$page = basename($_GET['page']);
include($page . '.php');

basename()은 입력값에서 파일명 부분을 가져오는 데 도움이 되지만, 이것만으로 파일 삽입을 완전히 막는다고 보기는 어렵습니다.

공격자는 허용된 디렉터리 안에서도 개발자가 의도하지 않은 다른 파일을 선택할 수 있습니다. 입력값 처리 과정이나 운영체제에 따라 예상하지 못한 파일명이 만들어질 가능성도 있습니다.

basename()은 보조적인 처리로 사용할 수 있지만 가장 중요한 방어 방법은 허용 목록입니다. 사용 가능한 페이지 이름을 명확히 정해두고 그 외의 값은 거부해야 합니다.

실제 경로 확인하기

사용자가 특정 하위 파일을 선택해야 하는 기능이라면 realpath()를 이용해 최종 경로를 확인할 수 있습니다.

<?php
$baseDir = realpath(__DIR__ . '/pages');
$fileName = $_GET['file'] ?? '';

$target = realpath(
    $baseDir . DIRECTORY_SEPARATOR . $fileName
);

if ($target === false ||
    strncmp(
        $target,
        $baseDir . DIRECTORY_SEPARATOR,
        strlen($baseDir . DIRECTORY_SEPARATOR)
    ) !== 0) {
    http_response_code(400);
    exit('허용되지 않은 파일입니다.');
}
?>

realpath()는 상대경로와 심볼릭 링크가 반영된 실제 경로를 반환합니다. 이후 최종 경로가 기준 디렉터리 내부에 있는지 확인할 수 있습니다.

다만 realpath()는 존재하는 파일을 대상으로 사용한다는 점을 고려해야 합니다. 또한 실제 경로 검증을 적용했더라도 모든 파일을 include()로 실행해야 하는 것은 아닙니다.

단순히 파일 내용을 읽거나 내려주는 기능이라면 파일 포함 함수 대신 해당 목적에 맞는 안전한 API를 사용하는 것이 좋습니다.

원격 파일 포함 기능 비활성화

PHP 환경에서는 원격 파일 포함 기능을 사용하지 않는다면 관련 설정을 비활성화해야 합니다.

allow_url_include = Off

allow_url_include를 비활성화하면 include()require()에서 URL 형태의 원격 파일을 사용하는 기능을 제한할 수 있습니다.

서비스에서 원격 URL을 파일 처리 함수로 열 필요가 없다면 allow_url_fopen 설정도 함께 검토할 수 있습니다.

allow_url_fopen = Off

다만 서버 설정만 변경했다고 해서 취약한 코드가 안전해지는 것은 아닙니다. 원격 파일 포함은 차단될 수 있어도 로컬 파일 삽입 위험은 남을 수 있습니다.

설정 변경과 함께 외부 입력값을 파일 포함 함수에 전달하는 코드를 수정해야 근본적인 대응이 됩니다.

업로드 디렉터리와 실행영역 분리

파일 업로드 기능이 있는 서비스라면 업로드된 파일이 PHP 코드로 실행될 수 있는 위치에 저장되지 않도록 해야 합니다.

업로드 파일은 웹루트 밖의 별도 디렉터리에 저장하고, 웹서버에서 스크립트 실행 권한을 제거하는 것이 좋습니다. 원본 파일명 대신 서버가 생성한 임의의 이름을 사용하면 경로 조작과 파일 덮어쓰기 위험도 줄일 수 있습니다.

추가로 다음 사항을 확인해야 합니다.

  • 허용된 확장자만 업로드할 수 있는가?
  • 파일의 실제 형식을 확인하는가?
  • 업로드 디렉터리에서 스크립트 실행이 차단됐는가?
  • 파일 저장 이름을 서버에서 생성하는가?
  • 업로드 파일에 직접 접근할 수 있는가?
  • 다운로드 시 접근권한을 확인하는가?

파일 업로드 취약점과 LFI가 함께 존재하면 각각의 취약점만 있을 때보다 피해가 커질 수 있습니다.

최소 권한과 디렉터리 분리

웹 애플리케이션 계정에는 서비스 운영에 필요한 디렉터리만 읽을 수 있도록 권한을 제한해야 합니다.

설정파일, 로그, 세션파일, 업로드 파일을 같은 디렉터리에 저장하지 말고 목적에 따라 분리하는 것이 좋습니다. 특히 애플리케이션이 포함할 코드 파일과 사용자가 업로드한 파일은 명확하게 분리해야 합니다.

운영환경에서 확인할 사항은 다음과 같습니다.

  • 웹서비스 계정에 불필요한 파일 읽기 권한이 있는가?
  • 업로드 디렉터리에 실행 권한이 있는가?
  • 로그와 세션파일을 웹루트 안에 저장하고 있는가?
  • 설정파일을 웹에서 직접 요청할 수 있는가?
  • 코드 디렉터리에 파일 쓰기 권한이 있는가?
  • 임시파일이 다른 사용자에게 노출되는가?

최소 권한은 파일 삽입 자체를 제거하지는 못하지만 취약점이 발생했을 때 접근 가능한 파일과 실행 범위를 줄여줍니다.

오류 메시지 관리

파일을 찾지 못했을 때 PHP 경고나 서버 오류를 사용자 화면에 그대로 출력해서는 안 됩니다.

파일 포함 과정에서 발생하는 오류에는 다음과 같은 정보가 포함될 수 있습니다.

  • 서버의 절대경로
  • 애플리케이션 설치 위치
  • PHP 파일명과 코드 줄 번호
  • 웹서버 계정명
  • include 경로 설정
  • 운영체제의 디렉터리 구조

운영환경에서는 상세 오류 표시를 비활성화하고 사용자에게는 일반적인 오류 메시지만 보여줘야 합니다. 상세한 내용은 접근이 제한된 서버 로그에 기록합니다.

파일 삽입 취약점 점검 방법

소스 코드를 점검할 때는 파일을 프로그램에 포함하는 함수부터 찾습니다.

PHP에서는 다음 함수가 주요 점검 대상입니다.

include()
include_once()
require()
require_once()

함수를 찾은 뒤 외부 입력값이 파일 경로에 포함되는지 확인합니다.

GET·POST 파라미터
→ 변수 저장
→ 파일 경로 결합
→ include 또는 require

다음 사항을 중심으로 점검하면 됩니다.

  • 외부 입력값이 파일 포함 함수에 전달되는가?
  • 사용자가 실제 파일명이나 경로를 지정할 수 있는가?
  • 허용 목록 방식으로 페이지를 선택하는가?
  • 로컬 경로와 원격 URL을 구분해 차단하는가?
  • 최종 경로가 기준 디렉터리 내부인지 확인하는가?
  • 경로 검증이 디코딩 이후의 값에 적용되는가?
  • 원격 파일 포함 관련 설정이 비활성화돼 있는가?
  • 업로드 파일이 실행 가능한 위치에 저장되는가?
  • 로그와 세션파일의 접근권한이 적절한가?
  • 상세한 서버 경로가 오류 화면에 노출되는가?

실제 취약점 테스트는 자신이 소유하거나 명시적인 허가를 받은 시스템에서만 진행해야 합니다.

자주 묻는 질문

LFI와 디렉터리 트래버설은 같은 취약점인가요?

완전히 같지는 않습니다. 디렉터리 트래버설은 허용된 디렉터리를 벗어나 다른 파일 경로에 접근하는 방법이고, LFI는 선택된 로컬 파일이 프로그램에 포함되는 취약점입니다. 디렉터리 트래버설을 이용해 LFI가 발생할 수 있습니다.

RFI는 최신 서버에서도 발생하나요?

최신 환경에서는 원격 파일 포함 기능이 기본적으로 제한된 경우가 많아 과거보다 발생 가능성이 낮습니다. 하지만 오래된 시스템, 잘못된 설정, 자체 제작한 파일 로딩 기능에서는 여전히 점검해야 합니다.

파일 확장자를 자동으로 붙이면 안전한가요?

아닙니다. 확장자를 붙이더라도 사용자가 경로나 파일명의 앞부분을 조작할 수 있다면 다른 파일이 선택될 가능성이 있습니다. 허용된 페이지 목록에서 실제 경로를 선택하는 방식이 더 안전합니다.

realpath만 사용하면 파일 삽입을 막을 수 있나요?

realpath()는 최종 경로를 확인하는 데 도움이 되지만 단독으로 사용해서는 안 됩니다. 기준 디렉터리 검증, 허용 목록, 접근권한, 원격 포함 차단을 함께 적용해야 합니다.

file_get_contents도 파일 삽입 취약점인가요?

file_get_contents()는 파일을 읽는 함수이므로 외부 입력값에 따라 임의의 파일을 읽을 수 있다면 경로 조작이나 파일 읽기 취약점에 해당할 수 있습니다. 읽은 내용을 다시 실행하거나 해석하지 않는다면 일반적인 include() 기반 파일 삽입과는 차이가 있습니다.

마무리

파일 삽입 취약점은 사용자가 전달한 값으로 프로그램에 포함할 파일을 결정할 때 발생합니다. 서버 내부 파일이 대상이면 LFI, 외부 서버의 원격 파일이 대상이면 RFI라고 구분합니다.

가장 안전한 대응은 사용자 입력값을 실제 파일 경로로 사용하지 않는 것입니다. 화면이나 템플릿을 선택해야 한다면 서버에서 허용 목록을 만들고, 사용자가 입력한 키에 대응하는 파일만 불러와야 합니다.

추가로 최종 경로가 기준 디렉터리 내부인지 확인하고, 원격 파일 포함 기능을 비활성화하며, 업로드 디렉터리와 코드 실행 영역을 분리해야 합니다. 웹 애플리케이션 계정의 파일 권한도 필요한 범위로 제한하는 것이 좋습니다.

점검 과정에서는 include()라는 함수 이름만 찾고 끝내지 말아야 합니다. 외부 입력값이 어디에서 들어오고, 어떤 검증 과정을 거쳐, 최종적으로 어떤 파일을 포함하는지 데이터 흐름 전체를 따라가야 정확한 판단이 가능합니다.

핵심 요약: 서버 내부 파일을 포함하면 LFI, 외부 원격 파일을 포함하면 RFI이며, 외부 입력값으로 파일 경로를 직접 만들지 않고 허용 목록으로 실제 파일을 선택해야 합니다.

핵심 키워드: 파일 삽입 취약점, LFI, RFI, Local File Inclusion, Remote File Inclusion, LFI RFI 차이, 파일 삽입 대응

태그: 웹 보안, 웹 취약점, 파일 삽입, LFI, RFI, PHP 보안, 시큐어 코딩

댓글 남기기