Hack This Site - Realistic - seculas Ltd.

Tue, Jun 29, 21

Welcome back to my walkthrough of hackthissite.org’s CTF missions. I will be going through my thought process of how I solved these missions, and therefore also giving away the solutions. If you came across this to give you hints, watch out for spoilers! Good luck, have fun. Hopefully this mission is not broken as was the last one.

Our next realistic mission asks us to find out information about some patents that a military technology company has filed. As always, I will begin with an overall investigation of their website. Unlike the previous missions, I will only explain certain things that are interesting and the overall structure of the website as opposed to anything and everything that seems like it could be useful. This is to ensure that these writeups don’t get astronomically long as did the previous mission - which was broken anyways.

One thing that you would notice right away, if inspecting the page source of the landing page, is the metadata that is embedded into the page. There is an interesting author tag that has the following information:

webadmin: Susy Slack, email s.slack@seculas.com

Another point of interest is when submitting an application for a job, there is an image that comes from _backups_/_images/ instead of the usual images/ that the rest of the website uses. This is a point of interest that should be further investigated. Visiting the _backups_/ folder, we see that there is a .zip archive that I suppose has some sort of backup. Trying to extract the zip file, we are prompted with a password field, and thus it is evident that we must try and figure out this password. Overall, the website has the following structure:

I will begin with a quick ‘basic password’ check using JohnTheRipper. I will do so by using the zip2john tool and then trying to crack the hash that it outputs. The following commands are what was done: zip2john backup.zip > backupzip.hash; john backupzip.hash. JohnTheRipper had very quickly defaulted back to incremental ASCII mode, which suggests that the password is not something simple that can be found in my default password list. At this moment on, there is one thing that can be noted about the leaked contents of backup.zip, that being the file index.htm. If you noticed, it has the same name as the landing page, and there doesn’t seem to be any other index.htm page present within the active directory. This would suggest that our backup zip archive contains some version of the index.htm page. This suggests that we could use a known-plaintext attack in order to crack the password that was used. Unfortunately, this would only work if the two files have enough in common for the encryption key to be shown.

What I will be using is the tool pkcrack, which can be found here. It requires 13 bytes of plaintext in order to decrypt the target ZIP archive. In order to use this tool, we are required to - in addition to the ZIP archive we are trying to crack - have a ZIP archive that is unencrypted that contains at least one semi-identical (at least 13 bytes) file that was compressed using the same compression method as the encrypted ZIP archive. Thus, we must download the index.htm file, and create a ZIP folder using the same compression algorithm as the creators of this backup.zip folder. Since I don’t know which method was used, I will start with the basic method. When using the tool, it was indeed able to crack the encryption key, and thus we now have the decrypted backup zip folder available for investigation.

I will first explore the shell.php file to see whether we can find some configuration details about the host target machine. It informs me that its setup using selfSecure, with the root user being root and everyone else under others. Unfortunately, their passwords have been removed. The adminEmail is set to admin@seculas.com. It shows that the shell has access to all directories on the machine.

Moving onto the internal_messages/ folder, it implies that there may be a ‘hidden’ folder with the same name in the website. Starting off with msgshow.php, a comment says that it is called by internal_messages.php. This script includes some showmessages.inc.php, and collects the password and username fields of the incomming POST request. Then includes the other file that is present in this ZIP, msgauth.php, and subsequently calls a function that seems to show messages that are related to the username that was passed. Shifting my attention to msgauth.php, we can see that it maintains a session mapping for all of its users. This script seems to authenticate a POST request in relation to the passed {username, password} fields. It ensures that the passed credentials have at least one character, and escapes the {username, password} fields. Opens a file pointer to files/msgpasswords.txt and reads 199 bytes (fgets() reads length - 1 bytes) or until newline (included) or an EOF is noticed. It uses the contents of this file to match against username:password to try and authenticate a user request. If it doesn’t match against any user, the request is not authenticated.

Trying the endpoint internal_messages/internal_messages.php is a valid address as expected, and internal_messages/files/msgpasswords.txt is forbidden.

We can see that internal_messages/internal_messages.php hosts an authentication form for 3 users - {Dr. Nuts, J. Bardus, admin} - to view their messages. Each login form makes a POST request to msgshow.php with the form fields {username, password} as expected from our cracked source code. Right away, one thing that I can try to do is use that interesting metadata tag that I noticed in the first investigation. Trying the password Susy Slack for the username admin, and changing the hidden value of the username to webadmin with the password Susy Slack, both return a ‘wong username/password!’ message.

Going back to the cracked msgauth.php script, we can see that it is entirely responsible for user authentication by setting a session mapping for the requested username. Which is then used by msgshow.php to check if authentication was successful. PHP, prior to versions 4.2.0, used to have something called register_globals on by default. This allowed any passed cookies, query strings, form fields, etc. to be directly accessed by a variable name if said variable has not been defined elsewise earlier in the script. Meaning, if I were to make a POST request with the form field name msg_password to some script on a PHP server, and this script uses a variable $msg_password that was never properly defined prior to it’s use, then PHP would look for the global scope variable $msg_password which would have been set by the POST request. Hopefully, you - the reader - can see why this could be useful for this situation. I could make a POST request directly to msgauth.php to try and authenticate the user admin to read their messages. My form fields would be {msg_username=admin, msg_password=Susy Slack,, filename=../../index.htm}, and if register_globals is set to on, then the script would default the value of the variables in the script to be my custom form fields. The script would then search through index.htm and match the username:password combo admin: Susy Slack, to the contents of the metadata tag webadmin: Susy Slack,, thus authenticating the admin. Sending this request using Postman, I get the response ‘set admin OK’, implying that the session for admin has been authenticated. Since the admin is already authenticated, we don’t even need to supply the correct password when using internal_messages/internal_messages.php, so we just click the ‘read messages’ button and it’ll show their messages.

The admin only has one message from Jason Bardus. The message states that there is a more secure authentication-check script in the admin_area directory with the source code. We can also reply to his message that makes a POST request to internal_messages/send_message.php with the form fields {from, to}. Following this form, we are then presented with a page that hosts another form that makes a POST request to internal_messages/store_message.php with the form fields {to, messagetext}.

From here, I want to try and find this admin area, trying from the root directory, I find it right away with the path admin_area/. Unfortunately, it says that our access is forbidden. From here, its not clear what to do. Trying admin_area/authentication-check.php doesn’t resolve to a proper location so its a little unclear. From here I have a realization that since we haven’t ‘seen’ the shell.php script yet, then its possible that its hidden in this admin area, since there would be no better place to put it other than here. Going to admin_area/shell.php, we are presented with a Basic Authentication prompt. Looking at the backed-up source code of shell.php, I quickly notice the root username, root, although, the root password is omitted from the backup file. At the top of the script we can see that on an unsuccessful login, the shell sends us back the following:

  $MyShellVersion =  "MyShell 1.1.0 build 20010923 ".$$PHP_AUTH_USER;
  ...
  <em>modified $MyShellVersion</em>

This means that, the user that we are attempting to login as, will be displayed on the 401 page. This variable is dereferenced twice, meaning if our name is a variable, we can see the contents of that variable instead of the name that we used. We want to find the matching password for the root account. This password should be stored in the variable $shellPswd_root, so by using shellPswd_root as the username, we can discover the double MD5 hashed value of the root’s password. Using the browser to make this request, I noticed something interesting. Whenever I attempted to login, it would throw me another login - which is expected - at which I would cancel. It would then make a third susbequent call to the script with empty Basic Authentication thus not displaying the hash as wanted. In order to bypass this, I quickly defaulted to Postman to make the request with the correct Basic Authentication header. This resulted with the following hash for the password: 9e71fc2a99a71b722ead746b776b25ac. JohnTheRipper doesn’t result in a quick answer, so going here and checking with them whilst JohnTheRipper does it’s thing actually gives the result foobar for a double MD5 hash fairly quickly. With this, we now have the username:password combo of the root: root:foobar.

Once we have access to the root shell, we can now explore, but very quickly I notice that we cannot do everything we want - such as a ls mypr0n - and can only use the ls command to show the current directory. From here, we can see that there exists the following structure:

Accessing admin_area/helpdesk/ shows that there is a single ZIP archive. Downloading this archive and reading through the only file in there, it is unrelated to the challenge as it’s just a bunch of funny help-desk scenarios. admin_area/mypr0n/ simply contains some animal images. admin_area/test/ contains a single ZIP archive named chkuserpass.c.zip. This archive contains the file chkuserpass.c which states that it is used in validating username:password in the “‘latest development and patents’ - section” as per the file itself. Looking over the code, it is very simple. It takes as input a username, password and hash. It creates a 200 byte character array - since by default a char in C is 1 byte - and unsafely copies the username in there. Then, if the password provided is less than 4 characters, it unsafely concatenates a fillstring to the array. It then unsafely concatenates the password to the array. Finally, it then hashes the string that was constructed within the array and if it matches to the hash provided it sets the variable is_pass_correct to Y, which otherwise would be set to N. It then returns this variable which is used to figure out whether the hash matched correctly. Instantly, you could tell that this section - the function checkit() - is susceptible to a buffer overflow due to the unsafe string operations. We can use this buffer overflow to set is_pass_correct to Y, even though the computed hash value will not equal the passed hash value.

Accessing admin_area/viewpatents.php, we find a form that makes a login request through POST to viewpatents2.php with the form fields {username, password}. Since we have access to the script that validates our request, and we have an understanding of how to exploit it, I just need to create the payload that will result with a validated request. This part requires, not only some short calculations, but also a little guess and check. We know that the stack would look something like the following:

View of the stack once the concatenated array is created. We hace is_pass_correct at the top, followed down by fillstring, and finally concatenated. There may be some small space in the stack between variables that are added depending on the compiler.
Stack view once concatenated is created.

Whilst the stack grows down, the filling of the concatenated variable grows upwards, and would overflow into the allocated space for other variables if too much is ‘concatenated’ or ‘copied’. There may be small amounts of space - a byte or two - between variables that is added so we should assume that there are gonna be a couple bytes extra that we need to fill. Overall, we need a base - assuming no space between variables - of 207 bytes worth of the character ‘Y’. Thus, starting at 207, I will brute force the login by splitting the amount of bytes in half - equating to amount of characters needed - between the username and password. Eventually, having a total length of 224 B (sizeof(username) + sizeof(password) = 224 B), meaning 112 ‘Y’ characters in the username and 112 ‘Y’ characters in the password worked. Instead of being presented with a message stating that we have the wrong username/password, we were presented with a message stating that we completed the mission!

This will be my last realistic mission as the next one relies on Flash, and since the support for Flash has been dropped, I will ignore that mission.