HTB Writeups
  • HTB Writeups
  • Boxes: Very Easy
    • Academy
    • Archetype
    • Arctic
    • Base
    • Bike
    • Blue
    • Explosion
    • Included
    • Markup
    • Oopsie
    • Redeemer
    • Responder
    • Shield
    • Unified
    • Vaccine
  • Boxes: Easy
    • Analytics
    • Armageddon
    • Bashed
    • Beep
    • Blocky
    • Bounty Hunter
    • Buff
    • Cap
    • CozyHosting
    • Devel
    • Explore
    • Forest
    • Grandpa
    • Granny
    • Horizontall
    • Jerry
    • Keeper
    • Knife
    • Lame
    • Late
    • Legacy
    • Mirai
    • Netmon
    • Nibbles
    • Optimum
    • Paper
    • Photobomb
    • Precious
    • RedPanda
    • Return
    • Sau
    • ScriptKiddie
    • Sense
    • Servmon
    • Shocker
    • Shoppy
    • Squashed
    • Trick
  • Boxes: Medium
    • Poison
  • Challenges
    • Behind the Scenes
    • Canvas
    • Debugging Interface
    • Digital Cube
    • Easy Phish
    • Find the Easy Pass
    • Forest
    • Infiltration
    • misDIRection
    • Pusheen Loves Graphs
    • Retro
    • Signals
    • The Secret of a Queen
    • Wrong Spooky Season
  • Fortresses
  • Cyber Apocalypse 2023: The Cursed Mission
    • The Cursed Mission
    • Alien Cradle
    • Critical Flight
    • Debug
    • Extraterrestrial Persistence
    • Getting Started
    • Needle in the Haystack
    • Orbital
    • Packet Cyclone
    • Passman
    • Perfect Sync
    • Persistence
    • Plaintext Tleasure
    • Questionnaire
    • Reconfiguration
    • Relic Maps
    • Roten
    • Secret Code
    • Shattered Tablet
    • Small StEps
  • Hack the Boo 2023
    • Hauntmart
    • Spellbrewery
    • Trick or Treat
    • Valhalloween
  • Cyber Apocalypse 2024: Hacker Royale
    • Hacker Royale
    • An Unusual Sighting
    • BoxCutter
    • BunnyPass
    • Character
    • Data Siege
    • Delulu
    • Dynastic
    • Fake Boost
    • Flag Command
    • Game Invitation
    • It has begun
    • KORP Terminal
    • Labyrinth Linguist
    • LockTalk
    • Lucky Faucet
    • Makeshift
    • Maze
    • Packed Away
    • Phreaky
    • Primary Knowledge
    • Pursue the Tracks
    • Rids
    • Russian Roulette
    • Stop Drop and Roll
    • Testimonial
    • TimeKORP
    • Unbreakable
    • Urgent
  • CYBER APOCALYPSE 2025: Tales from Eldoria
    • Tales from Eldoria
    • A New Hire
    • Cave Expedition
    • Echoes in Stone
    • Eldorion
    • Embassy
    • EncryptedScroll
    • HeliosDEX
    • Quack Quack
    • Silent Trap
    • Stealth Invasion
    • Tales for the Brave
    • The Ancient Citadel
    • The Hillside Haven
    • The Stone That Whispers
    • Thorins Amulet
    • ToolPie
    • Traces
    • Trial by Fire
    • Whispers of the Moonbeam
Powered by GitBook
On this page
  1. Hack the Boo 2023

Hauntmart

Personal Rating: Medium

This challenge was about a SSRF with a filter bypass to create a privileged user at an api endpoint.

  • At first glance I could see that the flag is copied to /flag in the dockerfile. The file entrypoint.sh is loaded, which sets up a database like so:

DROP DATABASE IF EXISTS hauntmart;
CREATE DATABASE hauntmart;
CREATE TABLE hauntmart.users (
    id INTEGER PRIMARY KEY AUTO_INCREMENT,
    username varchar(255) NOT NULL UNIQUE,
    password varchar(255) NOT NULL,
    role varchar(255) NOT NULL DEFAULT 'user'
);

CREATE TABLE hauntmart.products (
    id INTEGER PRIMARY KEY AUTO_INCREMENT,
    name varchar(255) NOT NULL,
    price varchar(255) NOT NULL,
    description TEXT NOT NULL
);

CREATE USER 'xclow3n'@'localhost' IDENTIFIED BY 'xclow3n';
GRANT SELECT, INSERT, UPDATE ON hauntmart.users TO 'xclow3n'@'localhost';
GRANT SELECT, INSERT, UPDATE ON hauntmart.products TO 'xclow3n'@'localhost';

FLUSH PRIVILEGES;
  • The file web_hauntmart/challenge/application/blueprints/routes.py is interesting. It contains route definitions for web and api endpoints. At the web endpoint /home the flag is defined as flag=current_app.config['FLAG'] with the template index.html

  • For /home and /product it seems like you need to be authenticated

  • The /register API seems interesting too. You can possibly register a user from an unauthenticated standpoint:

  • On the live website I could register an account immediately and access the site.

  • Under Sell Product it seems like you can send data to the server. This site really stands out, it says that you can send a manual url which will be visited by the admins. My first Idea is to host a javascript that triggers another request to me, containing the admin cookie.

  • The idea of reflected blind XSS did not work as I could not find a working injection. But what we state in the url field is indeed accessed by the server, which is interesting.

  • This is written in the index.html:

{% if user['role'] == 'admin' %}
{{flag}}
{% endif %}
  • This code in util.py might also be interesting as it seems to download what we give it in the manual url field under certain circumstances:

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
  • This might be the way in. I found out before that /api is prepended to the url of api calls. so we should be able to directly access /api/addAdmin. Here is the code from routes.py that shows that:

@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
  • Trying to access it shows a 403, which is expected because of @isFromLocalhost:

  • Lets check the isFromLocalhost function; Maybe we can bypass that. It is defined in util.py:

def isFromLocalhost(func):
    @wraps(func)
    def check_ip(*args, **kwargs):
        if request.remote_addr != "127.0.0.1":
            return abort(403)
        return func(*args, **kwargs)

    return check_ip

Well, we have a chance to perform a localhost request. Here:

blocked_host = ["127.0.0.1", "localhost", "0.0.0.0"]

def isSafeUrl(url):
    for hosts in blocked_host:
        if hosts in url:
            return False
    
    return True
  • Using 0 as the IP did work to bypass the filter and get the flag

PreviousSmall StEpsNextSpellbrewery

Last updated 1 year ago

According to the addAdmin code, the payload should work. But no, we receive “Invalid URL”. We see that there is a blacklist for manual url requests defined in util.py:

There are many ways to bypass this as you can see in this reference:

http://127.0.0.1:12345/api/addAdmin?username=hacker
https://book.hacktricks.xyz/pentesting-web/ssrf-server-side-request-forgery/url-format-bypass