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:
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.