--- title: "Google CTF 2017, joe, web" date: "2017-06-19T13:15:12+02:00" --- ## Joe, your intelligent conversation partner For the Joe challenge, you got access to a simple page which looked like a chat. Over a text box you were able to talk with Joe, an "intelligent" conversation partner/bot. Joe had a few functionalities: * Print out Joe's name * Set a new name * Some small talk stuff * Report a bug to an admin ![](/static/ctf/googlectf-2017-joe-1.png) The challenge description already told us to steal the admin cookie, so the last functionality (report a bug) was a good guess how we can interact with the admin. After inspecting the source, I was not able to find any direct vulnerabilities. Sending a message was - on the first sight - not vulnerable to XSS, because the message was inserted as a text element: ``` var row = document.createElement('p'); row.className = peer; row.textContent = message; conversation.appendChild(row); ``` ## Persistent Cross-Site Scripting Vulnerability I played a bit with this and changed the name of Joe to some XSS payload (``). Because of the code above, this payload was not executed. After testing a few other things and refreshing the page, I noticed that this payload was now executed. It was present on the page (like a log): ![](/static/ctf/googlectf-2017-joe-4.png) So we have a persistent xss in our own session. How can this help us? My next focus was on the `/message?msg=foo` endpoint. It was the endpoint called when sending a message. The nice part about this endpoint was the last header of the response: ``` HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Cache-Control: no-cache Content-Encoding: gzip X-Cloud-Trace-Context: 805d41a72f2623ac1728fed38fcac495;o=1 Vary: Accept-Encoding Date: Mon, 19 Jun 2017 10:45:42 GMT Server: Google Frontend Content-Length: 64 Content-Type: `text/html` ``` The response also did not encode the response, so this looks like a perfect way to execute javascript in the context of a different user, doesn't it? So my idea was like this: Report a bug to this endpoint, where *msg* contains my actual payload to steal the cookie. Sadly this was not possible, because there was a very simple check against CSRF. To submit a message (and get a response), you had to add a `CSRF-Protection: 1` header. This is not possible with a `XMLHttpRequest`/`fetch`, because making a GET request with an additional header results in a "preflight"-request (`OPTIONS`), to check for CORS settings. `OPTIONS` was not allowed on the server, so this failed. You can read more about this concept [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS). ## Join me So I kept searching for a way to get the admin to join my session. After taking a deeper look at the requests which were made for logging me in, I found this request to fullfill my login: ![](/static/ctf/googlectf-2017-joe-2.png) As can be seen, the `/login` endpoint is called and a long token is passed. Maybe this allows to fixate a session? > A session fixation allows to make a "victim" use a session an attacker has control over. Read more [here](https://www.owasp.org/index.php/Session_fixation). So if I can make the admin reuse my session, he will also execute the XSS payload I inserted before as the name (described above). Let's test it by copying this exact login url from burp and pasting it into a private browser window: ![](/static/ctf/googlectf-2017-joe-3.png) Success! We executed javascript in the context of a different user. So lets recap what steps we need: * Prepare our session with some XSS payload as the name * Send the admin to a link which logs him into our session. This will the also redirect him to the "start" page and execute our payload. * Our payloads needs to take the cookie and send it to our server. I then used a simple payload as Joe's new name (`let me rename you`) to send the cookie as a parameter to my server: ``` ``` This appends the current cookie to an url. The image does not exist, but the browser will try to fetch this image anyway so I can see it in my server access logs. Lets report our bug, so the admin visits our prepared page: ![](/static/ctf/googlectf-2017-joe-5.png) As you can see here, I can't redirect the admin directly to the `/login?toke=...` url because it was too long. To work around this I made a simple html file on my server and included this url in an iframe: ``` ``` And here we see our "exploit" worked, we got the flag :) ![](/static/ctf/googlectf-2017-joe-6.png)