While this is a somewhat unconventional box with a bit of a CTF feel. It has a lot of very real-world GitHub teaching points for organizations running the community edition. While privilege escalation didn’t technically result from the reverse engineering portion, it was a good exercise in deriving credentials from a Windows binary which leverages obfuscation techniques for credentials. Ultimately the user portion of this box was incredibly realistic, teaching GitHub security concepts, and the privesc portion of the box was a great review in basic reverse engineering.
TTPs and Tools of Note
This box used a few common tools, and also had us pulling some old ones out of the archives, specifically in regards to reverse engineering (RE).
- [application misconfiguration] Improperly configured GitLab webhooks, exposing key areas of the server’s filesystem to attackers.
- [database administration malpractice] Plaintext Storage of user credentials in a MySQL database resulting in the ability for an attacker to dump the entire database and acquire all user credentials.
- [programming malpractice] Use of a plaintext password in a Windows binary, exposing credentials to an attacker in memory during runtime.
|Release||07 September 2019|
Initial Access / Unprivileged Shell
Standard starting point on this one; nmap enumeration with the typical flags. As seen in figure 1.1, we kick off with a nmap query of the box using flags -sC for default scripts, -sV for version detection, -Pn to assume alive, and -oA to output the results in all formats. We get two open ports back.
TCP22/ssh is likely going to be leveraged to reconnect once we obtain a set of credentials for our target user, but it’s useless for us now given its presentation of a public key. Also, its version is current so we can’t use any sort of exploit on it at the time of writing this. TCP/80 http requires some further investigation. Let’s try investigating a few pages.
Further Enumeration of TCP/80
Going back to figure 1.1, we notice that nmap picks up on the existence of a robots.txt file on the webserver. Let’s pull it with curl. Sorting the output into 2 columns with a pipe to pr, we get some good information.
We can now take that curl and transform it into a wordlist, quickly scanning all of those URLs with dirb to see what we can access. This is much more efficient than manually visiting each page. Dirb just reports the status of the request back to us, and then we only have to visit the pages that gave us a success code (status 200). In figure 1.3 you see the dirb syntax including -r for non-recursive, so that we don’t run wordlist again under each find. Out of the whole robots.txt file, it picks up on two pages that are accessible. This is ultimately due to a current lack of authentication, but its a good starting place nonetheless, and it saved us an enormous amount of enumeration.
We start by navigating to https://10.10.10.114:80/ and are redirected to the sign-in page for GitLab community. At this point, I’m thinking that this is probably a development page for a business or some other community, but regardless, I may be able to get or put code here. In figure 1.4, also note that there is a link to the help page, which we will access later. Further, is also the URL that came back status 200 (success) in our dirb scan.
Before we go to the help page, lets first check out the /profile. We find a users page named, “clave,” and now just need to try to locate the other half of the credential set; the password. Nothing interesting in the source, unfortunately.
Exploiting a WebHook to Pop the Shell
Unfortunately, this doesn’t give us a SSH login, but it does allow us into the GitLab community website, where we can further poke around and look for some way to trigger code execution on the local machine. We see two projects which we have access to.
The first appears to be the profile page which we accessed at /profile before, and which we can match up simply by the page syntax. See Figure 1.9.
…and the second appears to be deployer code which will make the changes to the local server through a pull. Fortunately, while we can’t change the deployer, we can execute it to make the local change. This is standard GitLab code, and a normal concept in GitLab called webhooks. As you see in figure 1.10, the server is using a PHP shell_exec to perform a cd and git pull.
This means that we can probably get code execution through adding a page or changing index.php to a reverse shell, making the deployer change the local source, and then navigating to the page. We start by replacing index.php in the Profile project with PHP reverse TCP shellcode. As usual, I used pentestmonkey’s PHP script and modified the IP and port for my box. We then confirm the merge request and navigate to /deployer/index.php to execute the webhook, changing the code locally. It appears to succeed.
Now the last thing to do is trigger and receive the shell. We set up a listener with $nc -lvnp 13339 as we had set up the reverse shell to kick back on TCP/13339. We use flags -l for listen, -v for verbose (so we see the connection drop in the case that we don’t get line headers), -n to prevent DNS lookup, and -p to specify the port. We get a sh shell as www-data!!
Lateral Movement to Target User
Less conventional lateral techniques with this box, instead we continue our relationship with GitLab Community.
As none of the typical endeavors such as those in LinEnum come up with anything, we go back to GitLab and continue to poke around. We end up finding a PHP script called Postgresql in the snippets tab.
It appears to execute a database connection and then through an SQL query, provide results. Let’s modify this code to dump the database contents. For the sake of our sanity, we are going to create the file locally and then drop it to the machine and execute it. We still don’t have a fully formed shell, and it’s not worth the effort to establish one that works just like a normal BASH shell on the remote box, so in this case, this is just the easier method of altering the code. As you can see in figure 2.2, we have appended lines 5 and 6, feeding the results of the query into the variable $arr, and then printing that array to the screen.
We then run it locally on the remote box with $php postgresql.php, and receive back all the database records (of which there is just one) in array format. We see just the user clave and the associated password.
I wasted a lot of time at this point in the box trying to use the record after decoding what appears to be base64, but in traditional bullshit HackTheBox fashion, the credentials were just the “base64” itself this time, a total troll. When we ssh with email@example.com, we successfully login and become our target user.
Privilege Escalation to Root
There were a couple methods to get root on this box, but I went the intended way, which was essentially reverse engineering a Windows executable. While it wasn’t a typical Linux privesc technique, it was still a valuable exercise as I shook off some rust in x64dbg.
Right off the bat, we find a file in Clave’s home directory that appears to be a Windows executable. I then pull this file back onto my kali virtual machine, and export it through a password protected zip into my Windows Reverse Engineering environment. The “password-protected zip,” is an important measure in the event that the exe is malicious. We don’t want to accidentally infect our host box through some hook we don’t know about.
The last thing we will do prior to starting the Reverse Engineering (RE) process, is to see if we can just pull the password out using $strings, the binary that converts an exe’s Hex to ASCII and looks for human readable patterns. Unfortunately, we don’t get much here for passwords, but we do get some interesting strings. The “Access Denied !!” and “GetUserNameW” both give us a starting point, but at this point we can assume that the credentials are assembled through the registers and pushed onto the stack during runtime rather than just being read in. Fortunately for us, we found some starting points for breakpoints in the strings output, and we also aren’t working with a massive executable, so we can likely just step through it manually.
Before we drop it into x64dbg (x32dbg for 32 bit), and start working through the machine code and memory, let’s take a high-level look at it using CFF Explorer. We examine the headers, imports, resources, and find just two items of note. In the configuration files, we see that there is markup text asking to be run with the privileges of the invoker. This is essentially the same idea as SUDO. Additionally, in the imports, we find ShellExecuteW. The MSDN documentation tells us that this will be used to run a local command. Lastly, we are able to identify that it is not packed.
Now for the fun part, let’s get cracking with x32dbg. Before we run it, let’s look for that ShellExecuteW we identified in the imports earlier. As I mentioned earlier, the password is not available in the file itself. But instead of figuring out the algorithm used to obfuscate the password, let’s see if we can just pull it from memory during runtime. We start with the forward run, and finding the entry point, as well as a call to another function.
Scrolling up a bit we find a reference to clave, and see that PuTTy is being executed through the ShellExecuteW we found in the imports.
We can now add a breakpoint on the ShellExecuteW to see if we can pull the credentials out of memory, however, it still just exits. For some reason we aren’t reaching that point in the execution, ie: for some reason, that code is being stepped over. Let’s add the breakpoint prior to the check and jump on 0x00261647.
Success! We find that both the comments on the assembly in x64dbg and the register output, as well as runtime memory, contain the ssh input code being passed as a parameter to PuTTY during execution. In this case, we fortunately didn’t have to truly reverse engineer to de-obfuscate, we were simply able to extract it with a breakpoint because it written into the application in plaintext. This is a common and major programming mistake.
SSH in to Get Our Flag
As identified in figures 3.6 and 3.7 we see that the credential set for our ssh connection will be (clave:Qf7]8YSV.wDNF*[7d?j&eD4^). This works for us, and boom, we are root! Bitlab pwned!
This had a second but unintended avenue to root. Once we had the unprivileged shell, we identified SUDO rights to run /usr/bin/git pull as root. This meant that once we were in, we could create a second PHP script and then post-merge and pull it with SUDO, resulting in the hook from the pull triggering a shell as root. We can then receive it locally on a different port.
(1) A quick Hack The Box / Capture the Flag competition point. Don’t assume anything when it comes to credentials or general strings. Something might look like base64 but really just be a string because the author of the challenge is an asshole. In this case, we had just that: a password that looked like a base64 string. From the forms, it looks like many people suffered here.
(2) This is such a huge issue that I did a whole separate blog post on a dirty little trick to exploit it with basic Linux executables, but as we saw with the reverse engineering, it’s absolutely essential to hash credentials and to NEVER, EVER program critical plaintext information into an application. You must always either pull it from a server during runtime or hash it. As I’ve proven, it is incredibly easy to pull this information out during runtime.
(3) In addition to teaching point 2, never store plaintext credentials in a database, as if an attacker gets access to the API they can dump all the user credentials and access the server very easily.
(4) GitLab webhooks can be incredibly useful, but it is critical to protect them with the appropriate permissions, or someone with malicious intent can very easily leverage them to perform malicious actions, such as arbitrary code execution resultant of a reverse shell on the server.