http://나 file://처럼 URL 스키마 형식을 띠고 있지만, 네트워크 통신을 하지 않고 PHP 엔진 내부 메모리에서 동작하는 가상의 입출력 통로(파이프)입니다.
PHP의 파일 입출력 함수(include, require, file_get_contents, fopen 등)는 일반 로컬 파일 경로뿐만 아니라 스트림 주소도 동일하게 받아들입니다.
웹 해킹 및 CTF에서는 주로 LFI(Local File Inclusion) 취약점과 연계하여 소스코드 유출이나 RCE(원격 코드 실행)를 달성하는 핵심 도구로 쓰입니다.
PHP 설계자들이 프로그래머에게 편의를 주기 위해서입니다.
다른 프로그래밍 언어(C, Python, Java 등)에서는 로컬 파일 읽기(open), 네트워크 통신(requests/socket), 메모리 파이프(BytesIO)를 다룰 때 각각 완전히 다른 문법과 라이브러리를 써야 합니다.
하지만 PHP는 file_get_contents()나 fopen() 같은 함수 이름은 그대로 두고, 괄호 안에 전달하는 문자열 앞머리(http://, file://, php://)만 바꿔 끼우면 PHP 엔진이 알아서 네트워크, 디스크, 내부 메모리 파이프로 분기하도록 인터페이스를 하나로 통일해 둔 것입니다.
이것이 공격자에게는 취약점이 됩니다. 개발자가 include($_GET['page']); 처럼 로컬 파일 이름만 들어올 줄 알고 짰더라도, 공격자가 php:// 나 data:// 같은 스트림 주소를 주입하면 함수가 이를 그대로 해석해 버리기 때문입니다.
겉모습만 비슷한 주소 형태(URL 스키마)를 띨 뿐, 내부 동작 방식은 완전히 다릅니다.
| 스키마 | 동작 영역 | 통신 방식 | 헤더/패킷 유무 | 보안 및 공격 관점 |
|---|---|---|---|---|
| http:// | 외부 원격 서버 | TCP 네트워크 소켓 통신 | HTTP 요청/응답 헤더 및 바디 존재 | RFI (외부 서버의 악성 코드 원격 실행) |
| file:// | 로컬 디스크 (OS) | OS 파일 시스템 시스템 콜 | 없음 (원시 바이트 직접 읽기) | LFI (로컬 설정 파일, 시스템 파일 단순 열람) |
| php:// | PHP 프로세스 메모리 | PHP 내부 C 스트림 처리기 | 없음 (메모리 버퍼 및 필터 체인 통과) | LFI 소스코드 유출, 필터 체인 바이트 위조, POST 본문 RCE |
운영체제에서 모든 프로세스는 생성될 때 3개의 표준 입출력 채널(File Descriptor, FD)을 부여받습니다:
PHP는 이 3개 통로를 파일처럼 읽고 쓸 수 있도록 php://stdin, php://stdout, php://stderr 래퍼를 제공합니다.
운영체제의 0번 통로(STDIN)에 직접 접근하는 읽기 전용 스트림입니다.
문법 구조 분해:
주소: php://stdin
실제 PHP 개발 코드 예시:
<?php
// 방법 A: 한 줄씩 인터랙티브하게 읽기
$handle = fopen('php://stdin', 'r');
echo "이름을 입력하세요: ";
$name = trim(fgets($handle));
echo "입력받은 값: " . $name . "\n";
// 방법 B: 버퍼 끝까지 통째로 읽기 (파이프나 파일 리디렉션용)
$all_data = file_get_contents('php://stdin');
echo "전체 전달받은 데이터:\n" . $all_data;
?>
실제로 데이터를 전달하는 3가지 방법:
php test.php 실행 후 키보드로 타이핑하고 엔터 입력echo "test_payload" | php test.phpphp test.php < payload.txt웹 환경 vs CLI 환경: php://stdin 과 php://input 의 결정적 차이
nc target.com 1337 로 특정 포트를 열어두고 사용자의 터미널 입력을 백엔드 PHP 프로세스로 파이프 연결해 둔 환경에서는 원격 사용자가 입력하는 데이터가 그대로 프로세스의 0번(stdin)으로 들어옵니다.운영체제의 1번 통로(STDOUT)와 2번 통로(STDERR)에 직접 데이터를 쓰는 쓰기 전용 스트림입니다.
문법 구조 분해:
주소: php://stdout 또는 php://stderr
실제 PHP 개발 코드 예시:
<?php
// 일반 화면 출력
$out = fopen('php://stdout', 'w');
fwrite($out, "정상 처리 결과입니다.\n");
// 에러 로그 출력 (표준 에러로 분리)
$err = fopen('php://stderr', 'w');
fwrite($err, "경고: 오류가 발생했습니다.\n");
?>
php test.php 1> result.log 2> error.logphp:// 뒤에 오는 여러 기능 중, 데이터 변환(필터링) 전용 파이프라인 기능을 켜겠다는 의미입니다.
쉽게 말해 "데이터를 있는 그대로 주지 말고, 중간에 필터(변환기)를 통과시켜서 가져와라" 하고 명령하는 모드입니다.
예시 주소:
php://filter/read=convert.base64-encode/resource=/etc/passwd
1단계: 접두사 확인
PHP 엔진은 주소 맨 앞의 php:// 를 보고 "외부 인터넷으로 나가지 말고 내장된 PHP 스트림 처리기(C 언어 모듈)로 보내라"고 내부 라우팅합니다.
2단계: 원본 데이터 로드
resource= 뒤에 적힌 로컬 파일(/etc/passwd)을 디스크에서 읽어 PHP 프로세스의 메모리 버퍼에 담습니다. (헤더 같은 부가 정보 없이 순수한 원본 바이트만 담깁니다.)
3단계: 메모리 상에서 필터 적용
지정된 필터(convert.base64-encode 또는 convert.iconv...) 함수를 호출해, 메모리에 올려둔 바이트들을 차례대로 실시간 변환합니다.
4단계: 최종 결과 반환
변환이 끝난 바이트 덩어리를 호출한 함수(예: file_get_contents 또는 mPDF의 이미지 로더)에게 반환합니다.
CTF에서 가장 압도적인 빈도로 등장하는 래퍼입니다.
파일을 읽거나 쓸 때 중간에서 데이터를 실시간으로 변환(필터링)하는 소프트웨어 파이프 역할을 합니다.
문법 구조 분해:
기본 형태: php://filter/read=필터명/resource=대상파일
꿀팁: 필터 이름을 전부 외워야 하나요?
외울 필요 없습니다. 규칙이 매우 직관적입니다:
핵심 활용 1: PHP 소스코드 열람 (LFI)
일반적으로 include 'config.php';를 하면 서버에서 코드가 실행되어 화면에 빈 화면이나 HTML 결과만 보입니다.
이때 base64 인코딩 필터를 거치게 만들면 코드가 실행되지 않고 base64 텍스트로 치환되어 출력됩니다.
페이로드:
php://filter/convert.base64-encode/resource=config.php
출력된 base64 문자열을 디코딩하면 원래 PHP 소스코드를 그대로 볼 수 있습니다.
핵심 활용 2: 기타 문자열 조작 필터
string.rot13 : 텍스트를 ROT13 암복호화 처리
string.toupper / string.tolower : 대소문자 변환
convert.iconv.* : 문자셋 인코딩 변환 (UTF-8, UTF-16 등)
핵심 활용 3: PHP Filter Chain (고급 RCE 기법)
iconv 변환 과정에서 발생하는 바이트 변형 특성을 수십~수백 번 체이닝하여, 임의의 바이트(웹쉘 코드나 가짜 이미지 헤더)를 메모리 상에서 직접 조립해 내는 기법입니다.
클라이언트가 보낸 HTTP 요청 본문(Raw POST Data)을 직접 스트림으로 읽어오는 래퍼입니다.
문법 구조 분해:
요청 주소: http://target.com/index.php?page=php://input
요청 본문:
요구 조건:
php.ini 설정에서 allow_url_include = On 상태여야 함
동작 원리:
include($_GET['page']); 와 같은 취약한 코드에 ?page=php://input 을 넣고,
요청 바디에 PHP 코드를 전송하면 서버가 해당 코드를 그대로 include하여 실행합니다.
엄밀히는 php://가 아닌 별도의 스키마이지만, CTF에서 세트로 묶여서 자주 나오는 스트림 래퍼입니다.
외부 파일을 참조할 필요 없이, URL 자체에 직접 실행할 코드를 담아 보낼 수 있습니다.
문법 구조 분해 (평문 형태):
주소: data://text/plain,
문법 구조 분해 (Base64 인코딩 형태):
주소: data://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOyA/Pg==
요구 조건:
allow_url_include = On 및 allow_url_fopen = On
디스크를 사용하지 않고 프로세스 메모리에 임시로 데이터를 읽고 쓸 수 있는 가상 스트림입니다.
문법 구조 분해:
기본 형태: php://memory
용량 제한 형태: php://temp/maxmemory:2097152
CTF 활용:
디스크에 흔적을 남기지 않는 파일리스(Fileless) 공격이나 메모리 버퍼 조작 관련 문제에서 다뤄집니다.
PHP 아카이브(phar) 파일 내부의 메타데이터를 파싱할 때 발생하는 객체 역직렬화(Unserialize) 취약점을 이용하는 고난도 공격 기법입니다.
문법 구조 분해:
기본 형태: phar://디스크경로/아카이브내부파일
실제 페이로드 예시: phar:///var/www/uploads/evil.phar/test.txt
핵심 원리:
include, require 같은 실행 함수뿐만 아니라, file_exists(), filesize(), md5_file(), is_dir() 같은 단순 파일 검사 함수에 phar:// 주소가 들어가기만 해도, PHP 엔진이 phar 파일의 메타데이터를 읽어들이면서 자동으로 unserialize()를 실행해 버립니다.
공격자가 악성 직렬화 객체(POP Chain)를 심어둔 phar 파일을 업로드한 뒤 파일 검사 함수에 경로를 찔러 넣으면 곧바로 RCE가 터집니다.
서버가 .php 파일 업로드는 막고 .jpg 같은 이미지 파일만 허용할 때, 압축 파일로 우회하여 LFI로 실행시키는 테크닉입니다.
문법 구조 분해:
기본 형태: zip://압축파일절대경로#내부실행파일
URL 페이로드: ?page=zip:///var/www/uploads/avatar.jpg%23shell.php
공격 흐름:
PHP의 PECL expect 확장 모듈이 활성화되어 있을 때 사용 가능한 래퍼로, 스트림 주소 자체가 곧바로 리눅스 쉘 명령어가 됩니다.
문법 구조 분해:
주소: expect://id
또는: expect://whoami
요구 조건:
기본 PHP 내장 기능이 아니며, php.ini에 extension=expect.so 가 활성화되어 있어야 함 (CTF 문제 출제자가 일부러 켜두는 경우가 많음)
공격 효과:
include($_GET['page']); 에 ?page=expect://ls -la 를 넘기면 다른 우회 기법 없이 그 자리에서 쉘 명령어가 실행되고 결과가 화면에 출력됩니다.
| 공격 상황 | 사용 래퍼 | 공격 목적 | 필요 환경 조건 |
|---|---|---|---|
| include() 취약점 존재 | php://filter | 소스코드 유출 (.php 열람) | 기본 설정에서 동작 가능 |
| include() 취약점 존재 | php://input | RCE (POST 본문으로 코드 주입) | allow_url_include = On |
| include() 취약점 존재 | data:// | RCE (URL 내 인라인 코드 주입) | allow_url_include = On |
| 파일 검사 함수(file_exists 등) 존재 | phar:// | RCE (메타데이터 역직렬화 공격) | 파일 업로드 가능 + POP Chain 존재 |
| 이미지 업로드만 허용 + LFI 존재 | zip:// | RCE (jpg 위장 압축 파일 내 웹쉘 실행) | 로컬 파일 포함 취약점 |
| include() 취약점 존재 | expect:// | RCE (직접 쉘 명령어 실행) | PECL expect 확장 설치 필요 |
| 파일 파서/렌더러 악용 | php://filter 체인 | 위조 헤더 주입 및 데이터 유출 | 특정 파서(mPDF 등) 존재 |
php.ini 설정 강화:
allow_url_fopen = Off
allow_url_include = Off
안전한 파일 호출:
사용자 입력값을 include, require, file_get_contents 등의 파일 함수에 직접 대입하지 말고, 화이트리스트 기반으로 지정된 파일만 열 수 있도록 통제해야 합니다.