Summary

Summary

DAYS ONGOING

DAY 1: Cleartext Storage of Sensitive Information (CWE-312)

The first thing I do when I am attacking a target, Is sending its traffic into Burp suite, So I can see what’s really happening in the background, After sending the grinch network traffic into Burp suite, I was able to catch a robots.txt file.

I checked the file and I found the first flag{48104912-28b0-494a-9995-a203d1e261e7}, The first key to shut the grinch network down.

DAY 2 [s3cr3t-ar3a]: Cleartext Storage of Sensitive Information (CWE-312)

When I found the first flag, I realized there is another directory in the robots.txt file, The directory name was /s3cr3t-ar3a, I saved the directory and come back to it on day two, There was a warning claiming that the page has been moved.

When I saw the warning, I gave in to my habits and directly checked the client-side source, And it was there, I was able to find the second flag{b7ebcb75-9100-4f91-8454-cfb9574459f7} in the client-side source code.

DAY 3 [People Rater]: Insecure Direct Object Reference (IDOR) (CWE-639)

Stupid grinch made a people rater, But he was stupid enough to implement it with the poorest security ever! When I visited the people rater app for the first time I was clicking everywhere to intercept the requests and understand how the app really works, The app has /people-rater/entry?id= endpoint, and I will be able to retrieve people rate via this endpoint.

I also realized all people ids are base64.

So I decoded the first five ids.

At this time I realized that {"id":1} is missing, I encoded it into base64 and I send it to the entry endpoint.

I was able to retrieve the individual rate, Which was grinch himself, And I was able to found the third flag{b705fb11-fb55-442f-847f-0931be82ed9a}, Nine more flags to shut the grinch network down!

DAY 4 [Swag Shop]: Insecure Storage of Sensitive Information (CWE-922) & Insecure Direct Object Reference (IDOR) (CWE-639)

When I visited the swag shop for the first time, As always, I give in to my habits and checked the client-side source, I was able to detect the swag shop purchase management API.

$.getJSON("/swag-shop/api/stock", function(o) {
    $.each(o.products, function(o, t) {
        $(".product-holder").append('<div class="col-md-4 product-box"><div><img class="img-responsive" src="/assets/images/product_image_coming_soon.jpg"></div><div class="text-center product-name">' + t.name + '</div><div class="text-center product-cost">&dollar;' + t.cost + '</div><div class="text-center"><input type="button" data-product-id="' + t.id + '" class="btn btn-success purchase" value="Purchase"></div></div>')
    }), $("input.purchase").click(function() {
        $.post("/swag-shop/api/purchase", {
            id: $(this).attr("data-product-id")
        }, function(o) {
            window.location = "/swag-shop/checkout/" + o.checkoutURL
        }).fail(function() {
            $("#login_modal").modal("show")
        })
    })
}), $(".loginbtn").click(function() {
    $.post("/swag-shop/api/login", {
        username: $('input[name="username"]').val(),
        password: $('input[name="password"]').val()
    }, function(o) {
        document.cookie("token=" + o.token), window.location = "/swag-shop"
    }).fail(function() {
        alert("Login Failed")
    })
});

There were three interesting endpoints in the javascript client-source code:

  • The first endpoint was /api/stock, Which retrieves the swag shop stock content.

  • The second endpoint was /api/purchase, I decided to try to purchase some swag, But I wasn’t able to do it before logging in.

There was also an interesting directory "/swag-shop/checkout/"+o.checkoutURL embedded in the purchase post request.

$.post("/swag-shop/api/purchase", {
        id: $(this).attr("data-product-id")
    }, function(o) {
        window.location = "/swag-shop/checkout/" + o.checkoutURL
    }).fail(function() {
            $("#login_modal").modal("show")
        }

But after reading the javascript code I was sure that I wouldn’t be able to use it before retrieving o.checkoutURL from the purchase endpoint.

  • The third interesting endpoint was /swag-shop/api/login, Which I was redirected to it when I tried to purchase any swag from the shop.

I sent random credentials to the endpoint to make sure if I will be able to brute-force them.

But the response message was well prepared and I wouldn’t be able to brute-force the credentials.

At this moment I was sure that it was fuzzing time, I sent the API to ffuf and I was able to detect two interesting new endpoints.

I sent a GET request to the first new endpoint which was /api/sessions, And I was able to retrieve all user’s sessions and their cookies.

After decoding the sessions, I was able to detect UUID embedded in the third session user JSON parameter.

At this moment I was sure it was time to visit the /api/user endpoint, But I detected an interesting response.

And without any fuzzing, I added the UUID parameter with the value I detected before to the user API GET request.

And I was able to retrieve the user data with the forth flag{972e7072-b1b6-4bf7-b825-a912d3fd38d6}, Eight more flags to shut the grinch network down!

DAY 5 [Secure Login]: Brute Force (CWE-307) & Privilege Escalation (CAPEC-233)

As always when I see any login page, I directly send random credentials to study how the request being send and what messages are retrieved in the response, After submitting random credentials in the secure login form, I was able to detect an interesting response.

The login failed response wasn’t well sanitized, Which lead to detect the right credentials with brute-forcing, I started by brute-forcing the username with ffuf, And I was able to detect it which was access.

With the same concept and after checking the right username response.

I was able to brute-force the password and detecting it which was computer.

I directly logged in with the credentials I retrieved with brute-forcing.

After logging in, There was an interesting message expose that there is a file to download somewhere, I checked the client-side source but nothing was interesting.

At this time I was thinking about how the app held the login cookie, I found that the cookie is a JSON object encoded into base64.

I turned the admin parameter to true, Encoded the cookie JSON object into base64, And updated the cookie with the new one.

After reloading the page with the new cookie, I was able to detect a new zip file appeared in my secure files form.

I directly download it, And it was secured with a zip file password.

I wrote a python script to brute-force the password which was hahahaha.

import zipfile

filename = 'my_secure_files_not_for_you.zip'
dictionary = 'rockyou.txt'

password = None
file_to_open = zipfile.ZipFile(filename)
with open(dictionary, 'r') as f:
    for line in f.readlines():
        password = line.strip('\n')
        try:
            file_to_open.extractall(pwd=password)
            password = 'Password found: %s' % password
            print(password)
        except:
            pass

And I was able to retrieve the fifth flag{2e6f9bf8-fdbd-483b-8c18-bdf371b2b004} with a wired image for grinch!

DAY 6 [My Diary]: Path Traversal (CWE-22)

When I visited the my-diary app for the first time, I was interested in how the app carries and filter the /my-diary/?template= parameter.

At this time an Idea came to my mind, If I need to traverse any file I should make sure it already exists in the my-diary app directory, And the simplest file I can think of was index.php, And after I sent a request for /my-diary/?template=index.php.

I was able to retrieve the index.php file source code.

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();
}

There was an interesting filter in the index.php source code which was working in three parts:

  • The first part was regex /([^a-zA-Z0-9.])/ filter which filters any symbol but Characters, Numbers, and the dot.
  • The second filter was replacing the admin.php string with an empty string.
  • The third filter was replacing the secretadmin.php string with an empty string.

I sent a request for the /my-diary/admin.php file and the response was 404 Not Found.

I also sent a request for /my-diary/secretadmin.php, And after checking the response I was sure that I should find a way to access this file internally.

At this point, I was sure there is a way to bypass those filters and access the secretadmin.php, I started working on the bypass locally.

And after several minutes, I was able to craft a string payload to bypass the secretadmin.php file filter.

I directly sent the bypass into /my-diary/?template= parameter.

And I was able to retrieve the sixth flag{18b130a7-3a79-4c70-b73b-7f23fa95d395}, More six flags to defeat the grinch!

DAY 7 [Hate Mail Generator]: Path Traversal (CWE-22)

Hate-Mail-Generator app implementation was really interesting since it has different functions to consider such as creating and previewing campaigns, At this point and after previewing the first and the only campaign in the list I was sure it’s a template engine.

I directly copied the body of the campaign I previewed and try to create my own campaign, But I wasn’t able to do this.

After reviewing the body of the email, I was interested in the template function which reflects HTML files in the email body, I tried to reflect the simplest file I can think of which is the index.php file, And I get an error message claiming that there is /templates directory in the app.

After checking the /templates directory, I was able to detect three files, The first two files were already known from the campaign body I previewed.

I tried to access the dhs_admins_only_header.html file which was the third one but it has a previewing restriction.

At this point, It was clear I should bypass this restriction, There was another parameter in the email body called name which held and reflect names dynamically, And after checking the preview request, I found that it reflects from JSON object in the preview_data HTML form element.

preview_markup={{template:index.php}}+Hi+{{name}}.....+Guess+what.....+<strong>YOU+SUCK!</strong>{{template:cbdj3_grinch_footer.html}}&preview_data={"name":"Alice","email":"alice@test.com"}

I tried to inject the dhs_admins_only_header.html file in the JSON object.

preview_data={"name":"template:38dhs_admins_only_header.html","email":"alice@test.com"}

But it reflects as a string.

I embedded the whole template object to bypass the string.

preview_data={"name":"{{template:38dhs_admins_only_header.html}}","email":"alice@test.com"}

And I was able to retrieve the seventh flag{5bee8cf2-acf2-4a08-a35f-b48d5e979fdd}!

DAY 8 [Forum]: Use of Hard-coded Credentials (CWE-798)

I start testing Grinch-Forum app by reviewing the existing forums, I was able to detect two usernames in the first and the only post, The first one was grinch which is the post publisher, The second one was max which is the only comment in the post.

At this time I was obviously thinking about brute-forcing the password since I had two usernames, I started by brute-forcing the grinch username, But the password was clearly isn’t brute-forceable.

And the same for the max username.

While I was doing OSINT about the grinch, I knew that max is the grinch’s dog, And it was clear that max wouldn’t be able to speak because it’s a dog, And what approved the hypothesis is max comment in the grinch post.

At this time I was thinking what if max password is woofing but in a different way, I generated two wordlists, The first woof.txt wordlist was generated manually, And I crafted a python script to generate the second woof2.txt wordlist, But non of them works as a password for max username, But I can say it was a good idea!

At this time I knew it was fuzzing time, And after fuzzing the forum I found a PHPMyAdmin panel in the forum directory

PHPMyAdmin always means SQLi and credentials disclosure, I tried to inject the forum and post routes, But it didn’t work.

I also reviewed the client-side source-code but I found nothing, I knew the app credentials would be different than the PHPMyAdmin credentials, I disregard this fact and tried to brute-force the credentials with the existing usernames and with different usernames, But it didn’t work, I tried to log in with the default PHPMyAdmin credentials, But it didn’t work.

At this time I started copying bootstrap hashes from headers and footers and use them to search in GitHub but I also found nothing, I was thinking what if adam which is the CTF creator left the credentials somewhere in his repositories, I copied his username and searched for his account in GitHub, And I found his account with Grinch-Network forum repository.

And after looking in the commits, I was able to identify the PHPMyAdmin credentials!

I directly logged in at the forum PHPMyAdmin with the new credentials, And I was able to detect usernames and passwords in MD5 hashes!

And after decrypting the grinch MD5 password into BahHumbug and log in.

I was able to retrieve the eighth flag{677db3a0-f9e9-4e7e-9ad7-a9f23e47db8b}! Grinch network will be down soon!

DAY 9 [Evil Quiz]: SQL Injection (CWE-89)

After a fast tour in the Evil-Quiz app, It was clear that I wouldn’t be able to brute-force the admin credentials due to the well-sanitized login error, At this time I was interested in how to app working, And what messages reflected in the response.

The app is working in three parts:

  • The first part is the name post request, Which is setting a cookie session if it doesn’t exist.
  • The second part is the quiz query post request.
  • The last part is reflecting the quiz score results.

I tried to use different names such as grinch and max, The interesting part of the response is that it reflects the number of players with the same name, For grinch, it was 14 users, And for max, it was 4 users.

At this point I tried to inject the simplest SQLi payload which is grinch'+OR+1=1--+- and the response was really interesting, The player’s number with the same name was clearly big which is approving the blind SQLi in the name post request.

I tried another request to make sure with 0'+OR+1=0--+- payload and the response was zero which is approving the vulnerability again.

It was clear that I wouldn’t be able to exploit this SQLi manually or with sqlmap, So I did a python script for it, With the zero players with the same name response point of view, And I was able to retrieve the admin password which is S3creT_p4ssw0rd-$.

import requests
import string

secret=[]

var = list(string.printable)
cookies = dict(session='f78c0176d0d02bf6e08cf1c1d1491e91')
headers = {'Content-Type':'application/x-www-form-urlencoded','Connection':'close'}

for v in var:
    for n in range(1, 17):
        data = "name=admin' AND substr((select password from admin limit 0,1),{},1) = '{}' -- -".format(n,v)
        x = requests.post('https://hackyholidays.h1ctf.com/evil-quiz', data=data, cookies=cookies, headers=headers)
        y = requests.get('https://hackyholidays.h1ctf.com/evil-quiz/score', cookies=cookies)
        if y.text.find('There is 0 other player(s) with the same name as you!') != -1:
            continue
        else:
            secret.insert(n, v)
            continue

if len(secret) == 17:
    print('Your admin password is:', ''.join(secret))

I directly logged in as admin, And I was able to retrieve the ninth flag{6e8a2df4-5b14-400f-a85a-08a260b59135}!

DAY 10 [Signup Manager]: Information Disclosure (CWE-200) & Privilege Escalation (CAPEC-233)

The Signup-Manager app was a simple login and signup forms, The first move was obviously trying to sign up my own account and accessing it, But I stumbled with a message, And nothing was interesting in the client-side source code.

I directly started testing the signup form, And while I was testing it, There was an interesting comment in the HTML body claiming that there is a README.md file for assistance.

After downloading the file from the app directory, It was an instructor for the installation of the app.

The file was pointing to another zip file in the app directory which is the app source code.

The source code has four files, But three of them were just reflecting the data, And the index.php file has the functions, So it’s obviously our work ground, And after inspecting the file, I was able to detect three interesting parts:

  • The first one is the buildUsers function, Which building the users with admin=Y in their data line and gives them access permissions.
  • The second one is the addUser function, Which registered all new accounts with admin=N in their data line.
  • The third one is the data filter, Which filters all incoming data by certain selected rules.

I started playing with the app locally by XMAPP, And after signing my own local account up, I was able to detect how the app manages the accounts data in the users.txt file.

I tried to bypass the parameters filters and inject Y char at the end of the user data, But I wouldn’t be able to do this due to the filters, And while I was reviewing the filters source code, There was an interesting function filtering the age data which is intval function, And after reading the PHP manual: The intval function is converting doubles to integers by truncating the fractional component of the number, And for example 1e5 filtered into 100000 .

At this point, My attack components were ready to apply, But I chose to do it locally, and then move to the online app, I started by injecting the age parameter with 1e5 which move my Lastname for more than three characters, This means I need to add Y to the Lastname third character from the back.

And with this concept, I was able to bypass the filters and privilege my account access.

I directly applied my attack concept in the online app, And I was able to retrieve the tenth flag{99309f0f-1752-44a5-af1e-a03e4150757d}!

But wait, The page was pointing to the day 11 app, Which will be private!

DAY 11 [Grinch Recon]: SQL Injection (CWE-89)

When I accessed the Grinch-Recon app for the first time, The developing API message gets my interest.

I simply put /API in the app directory, And I was able to access the API status codes docs.

I tried to fuzz the /API/* endpoints, But I wasn’t able to detect anything due to the IP restriction.

I went back and after accessing the three-album directories I was interested in the hashes on the /album?hash= parameter, But there was no obvious pattern for brute-forcing or decrypting the hashes.

At this moment I tried to access each image on its own, And I found the /picture?data= parameter with base64 data, And after decrypting it, I was able to detect the image directory with its auth hash.

At this point I was clearly thinking about internal SSRF to bypass the API IP restriction, I tried to change the image JSON object URL into the API URL and encoded it again into base64, But I wasn’t able to send the request due to the auth hash, And I wasn’t able to decrypt the auth hash too, And this means I need to find a way to generate my own auth hashes internally.

The simplest thing I can think of was SQLi in the /album?hash= parameter, And it was surprisingly working!

I directly sent the endpoint into sqlmap, And I was able to retrieve the app databases.

The information_schema DB was known as the default database, So I directly retrieved the recon DB tables.

And after dumping the album and the photo tables from the recon DB, I was able to detect that the album table primary key id is used as a foreign key in the photo table (MySQL Foreign Key Reference).

This means I can send routed SQLi payload to the image table via the album table and generate my own auth hashes! And the crafted SQLi payload was 59gro' UNION SELECT "2' UNION SELECT '1',2,'../api/user' --+-",'14',1--+-, And there I was able to generate my own hashes and fuzzing the API endpoints.

And after a long fuzzing process, the result was two endpoints which are /api/user and /api/ping, The second one was obviously for API health checking so I started to fuzz the /API/user endpoint parameters, And the result was /api/user?username= and /api/user?password=parameters.

And from our previous testing process, The Invalid content type detected response always means true, The /api/user parameters map was as:

SELECT username FROM user WHERE username="";
SELECT password FROM user WHERE password ="";

So I need to embed % at the end of the /api/user?username=g% and /api/user?password=s% parameters to make sure I will get a true response If I was sending the right request as:

SELECT username FROM user WHERE username="g%";
SELECT password FROM user WHERE password ="s%";

And with this concept, I was able to fuzz username and password, Which are grinchadmin and s4nt4sucks, And I was able to retrieve the eleventh flag{07a03135-9778-4dee-a83c-7ec330728e72}!

DAY 12 [Grinch Network Attack Server]: Reversible One-Way Hash (CWE-328) & Denial of Service (CWE-400)

The Grinch-Network-Attack-Server is a simple DDOS box for attacking Santa’s servers, The box has an internal script for the DDOS attack, And the script was functioning the attack by a payload in the /attack-box/launch?payload= parameter, And after decrypting the payload there was a JSON object with two parameters, The first parameter was the target IP, And the last was the target hash.

I tried to flood the internal server and functioning internal attack towards 127.0.0.1, But I needed to bypass the hash protection, And the hash salt was surprisingly brute-forceable which is mrgrinch463, And the python script used for hash salt brute-forcing was:

import hashlib

list = open('rockyou.txt','r')
y = '5f2940d65ca4140cc18d0878bc398955'
for f in list:
    x = f.rstrip()+"203.0.113.33"  
    result = hashlib.md5(x.encode())
    result = (result.hexdigest())
    if result == y :
        print(f)
        break

But even after generating a hash for the localhost IP, There was protection against internal DDOS flood.

And this solved by DNS rebinding.

And after generating a hash for the new domain which is 7f000001.cb007121.rbndr.us, The grinch network was finally down! And there is the twelfth flag{ba6586b0-e482-41e6-9a68-caf9941b48a0}.