BsidesSF CTF 2025 Writeup
Hello, This is my writeup for BsidesSF CTF, In this writeup I will walk you through most web challenges that I solved. I will also show you how I solved another categories, so let’s get started.
Challenge 1 (detector - web)
We can see the challenge provides us with the flag path, the website link with source code.
The website is simple as it takes an IP address and ping on it.
So, let’s check the source code and examine how it works.
We can see above that it checks for the range of each octet in an IP address (this is a client side validation), but in the php
code it passes the the IP directly to system()
function without validation, so an attacker can inject a vaild IP range then execute system commands.
As we have the flag path already, let’s read it.
Challenge 2 (detector-2 - web)
This the improved version of the pervious challenge, so let’s access the website.
We can see above the challenge has the same website like previous challenge, so let’s check the source code.
The challenge has an small validation on server side which puts the IP address into double quotes and prevents from escaping by checking if the user-input contains double quotes, it returns an error.
As the IP will be injected into system()
function and executed by bash
, we can bypass the validation using 127.0.0.1$(Command)
.
The bash will run the command inside $(Command)
and Replace $(Command)
with the output of the command.
The flag path is provided, so let’s read the flag.
Challenge 3 (go-git-it - web)
We can notice from the challenge name that our goal is to access .git
directory.
We can see it’s a static website, so let’s try to access /.git
We can see the directory is not found, weird!, let’s back to the home page and inspect the page source.
We can see a comment which tells us don't forget to unlist the git directory
, so let’s try /git
.
And we can access it successfully.
Now let’s download it using GitTools especially git-dumper
.
Let’s explore the history of commits using git log
.
As the first commit has a note of remove sensitive information
, let’s see it using git show <commit>
.
Challenge 4 (hangman-one - web)
We can see a login page and signup pages, so let’s create an account and login.
We can see that the our goal is to guess flag and we have 4 times to wrong then gameover.
Acutually I tried to find something to make the guess process easier but I couldn’t, so my approach is:
- Create an account and login
- Try characters and note the wrong one
- Repeat until I git the flag.
The image below from admin account as I tried to login as admin:admin
and found that his guesses are saved.
Challenge 5 (your-browser-hates-you - web)
The challenge tells us that the URL will not work as it has something wrong in SSL certificate.
If we try it in the browser, it’s not working.
But we can try to open it using curl -k <url>
and get the flag.
The
-k
(or--insecure
) flag tellscurl
ignore certificate validation.
Challenge 6 (web-tutorial-1 - web)
In this challenge, we need to exploit XSS steal the flag from admin
We can a 3 hints, let’s see them
Let’s try to trigger an XSS for testing the vulnerability.
Now our approach is:
- Use JavaScript (
XMLHttpRequest
orfetch
) to make a request to/xss-one-flag
. This fetches the flag from the Admin’s session. - Send the response from the request to attacker machine (I will use
webhook
).
Challenge 7 (hidden-reports - web)
If we visit a website, we can see that we need to enter a valid password (Authorization Code) to get the flag. But there is an interesting note tells us to avoid using '
which indicates to SQL Injection
.
We can see that the single quote '
breaks the SQL query. So, we can bypass the query using ' OR 1=1 --
'
The end of the query.
OR 1=1
which always true.
--
to comment the reset of query.
Challenge 8 (sighting - web)
The website has an upload feature, but when I tried to upload an image, I can’t see where it uploaded. So, I opened one of the above images (already exists) in new tab.
We can see images will be in uploads/
directory, but if we try to access our uploaded image, it gives us not found.
So, the file upload is a rabbit hole, but file
parameter is interesting and may indicate to Local File Inclusion (LFI)
.
Let’s try to access ../../../../etc/passwd
.
Now let’s read the flag from the provided path /flag.txt
.
Challenge 9 (taxonomy - web)
The website shows us a list of users and some data belong to them. This belongs to SQL Injection
. Why I said that? because I suppose that the data returned from database. It also maybe a static and written by developer.
At first let’s try to inject test'
, and as expected it returned a SQL error.
We need to bypass the query to read the flag as we do before.
But the above query will not work correctly, so we need to track the SQL query and close each part using %' OR 1=1 ) --
.
%
Complete the LIKE.
'
Close the ‘ string.
OR 1=1
Always true.
)
Close the ( group.
--
Comment out everything else.
Now the WHERE
clause is always true because of OR 1=1
. We can see all data from the database.
Challenge 10 (evidence - web)
The challenge gives us a small hint about XML entities which is interesting and indicate to XXE Injection
.
The website shows us an upload feature and the allowed extension is XML
Let’s try to upload a normal XML file.
Everything looks normal, so let’s read the source code.
The above code is vulnerable to XXE Injection
because:
-
libxml_disable_entity_loader(false);
Allows us to load external entities.We re-enable entity loading, which allows the processing of external entities in the XML input.
-
- Expand entities (
LIBXML_NOENT
). - Load external DTDs (
LIBXML_DTDLOAD
).
- Expand entities (
- User-Controlled Input:
You take a file uploaded by the user and directly load it into the
DOMDocument
without any validation or sanitization.
So, as it loads external entities we can read internal files us. Let’s try to read /etc/passwd
to confirm that it works.
Now we can read /flag.txt
.
Challenge 11 (dating - web)
In this challenge the goal is to gain RCE and read the flag.
The website takes some parameters to create a dragon profile.
Let’s navigate to the source code directly to have a better understanding.
Wikipedia
A Jakarta Servlet, formerly Java Servlet is a Java software component that extends the capabilities of a server. Although servlets can respond to many types of requests, they most commonly implement web containers for hosting web applications on web servers and thus qualify as a server-side servlet web API. Such web servlets are the Java counterpart to other dynamic web content technologies such as PHP and ASP.NET.
The code above sends a POST request to /ProfileServlet
:
- It reads the raw POST body as a binary input stream.
- It deserializes that input using
XMLDecoder
. XMLDecoder
expects XML formatted Java objects.- It reads an object (
dragonData
) from the XML. - It responds by printing
Profile received for: {dragonData}
.
The issue here is that If an attacker sends malicious XML, they can create dangerous objects that:
- Execute arbitrary code
- Read files
- Write files
- Run system commands (if gadget chains exist)
After searching for an exploit for this situation, I found this exploit from Exploit-DB.
Let’s test it first by sending a request to webhook.
Now we need to get a shell and for this, we can use ngrok as an IP and port to listen on and use nc
to get a reverse shell.
Finally let’s read the flag.
Challenge 12 (pathing - web)
The website tells us the flag is in the following path ../../../../../../../../flag.txt
which indicates to Path Traversal
.
If we go to the website, it shows us the path will be inserted into the URL directly without parameters.
But we can’t access it using a browser because the browser automatically normalize the URL path before sending it.
So, we can bypass it by sending the request using burp suite and get the flag.
Challenge 13 (hoard - web)
The website shows us some user inputs, so let’s check the source code directly.
It just sends a POST request with some data but the interesting part is shell_exec()
which tells us if the hoardType
is equal to artifact
, it passes the reset of parameters into shell_exec()
.
So, we need to escape from single quotes and execute system commands to read the flag using '; cat /flag.txt ; #
.
Note: In the first time I used //
but it’s not working, so I replace it with #
.
Challenge 14 (meow - terminal)
This challenge is like a warm up to the terminal category which our goal is to read the flag using the provided flag path.
Just navigate to the website and read the flag. Piece of Cake!
Challenge 15 (toothless - forensics)
Move to another category which is forensics
, and the challenge provide us with .pcap
file
Let’s open it in wireshark
and analyze the packets.
We can see that the packets are ICMP
, so we need to check all packets and capture the one that contains Data
as we see above.
Let’s decode the value of Data
and get the flag.
Challenge 16 (dragon-name - mobile)
The last challenge is a mobile challenge and it provides us with the .apk
file, so, let’s download and insert it into Jadx-GUI
.
We can see in AndroidManifest.xml
that the app contains 3 activities. Let’s start by analyzing MainActivity
The below function is the most interesting part in MainActivity
.
We can see the flag consists of 5 parts:
- Part #1: The
rot13
ofPGS
string - Part #2: The
bas64
decoding ofdzNhaw==
string - Part #3:
T0
- Part #4: This part will be found in
res/values/strings.xml
file - Part #5:
Typ3
The final flag will be: