XSS a Paste Service - Pasteurize (web) Google CTF 2020

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Pasteurize, or pasteurize, was a web  challenge from the Google CTF 2020.   In the end 260 teams solved it.  It was also labeled as easy.  “This doesn't look secure. I wouldn't put even the  littlest secret in here. My source tells me that   third parties might have implanted it with their  little treats already. Can you prove me right?” And we get the link to the challenge  website. So let me tell you how we solved it. <intro> The CTF started around 2am german  time, so I didn’t play from the start.   In the morning when I got up, somebody else  from our CTF team ALLES! Already found the   vulnerability but hadn’t fully solved it yet. Here you can see our internal organisational   system. We used to have CTFPad, and extended it  with some additional features, but some crazy   people, I think mainly Cherry Worm(?) from  our team, basically wrote this from scratch.   It’s AWESOME! You can create tasks for each  challenge and people can assign themselves to it.   When you click on a challenge, like Pasteurize  you get a shared notepad. It supports markdown   and you can look at it with a split view. It’s  crazy. Anyway. When I woke up I already saw this: bodyParser.urlencoded with "extended: true"  in /source, which means, json object can   be supplied, which leads to, breaking the  javascript string. And this payload here.  Content, and in brackets we have dash,  alert, dash, equal, dash, one, dash. Of course I have no clue what that means. Yet.   I obviously need to look at  the challenge myself first. So here we go. We can “create   a new paste”. Blah blah. And maybe we can  already add some Cross-site Scripting tests.   A basic HTML <h1> tag and a script tag with alert. Huh! We get a headline Test HTML! But no alert   popup. If we inspect the HTML element here, we  can see that we were able to inject the <h1>   HTML code, thus format this headline,  but our <script> tag is gone. So it   looks like some sanitization is happening  to the text, only allowing harmless HTML.  If we look at the HTML source code of this  page, so not the currently rendered HTML,   but the raw HTML before the browser  executed any javascript, we can see that   the note is actually empty when we load the page. But further down we can find javascript that seems   to contain our text. “Blah blah” and so forth.  Also still containing the script tags with alert.   Then this note text is passed into  the DOMpurify.sanitize function   and then, once sanitized, it is added  into the innerHTML of our note tag.  DOMpurify is a javascript library that performs  client-side output sanitization and I know this   library. It’s basically the best library you  can get if you need to allow some HTML tags,   but be safe from XSS. That’s very hard to  do, and for many it’s unintuitive, but it’s   actually better to do this sanitization in the  browser on the client, and not on the backend.   I also personally know the maintainer very well.  And I know DOMPurify has rarely vulnerabilities   and bypasses. Mostly weird edge cases. So I  consider this usage here to be completely safe.   I strongly believe the goal is not  to bypass this sanitization check.  Also we can find here a comment, fix the bug  number 1337, in /source, that could lead to XSS.   So we are not only told that the goal is to find a  XSS, but that the bug is not here, but in /source.  And when we go to that site, we find the source  code of the server. It’s written in NodeJS.  Okay. One other important   detail is this “share with TJMike” button. When you press it you get a message that TJMike   will soon look at this paste. This is a typical  XSS challenge setup. XSS means you attack another   user, right? So you need some kind of bot that  visits page, and executes the XSS, so you can   actually pull off an attack. So by clicking this,  we trigger the bot to look at our paste. And if we   have XSS here, we can XSS this bot and somehow  steal the flag. We just don’t know yet how.  With web challenges it also makes sense to use  a web proxy like burp to look at the traffic.   I’m using here the free community edition  and it also has this awesome new feature   with the embedded chrome browser. It’s my new  favorite thing - makes the setup so much easier.  If we create a paste again, we see here this post  request being sent. And looking at the post data   we can find a “content” variable. This looks oddly familiar,   right? In the notes we also had “content”. So let’s send this request to the repeater,   take the example snippet from the notes and  modify the request. Send it, and now we created   a new note. Here is the note id. Copy it into  the browser, and BOOM! We got the XSS alert.  So how did this work? Let’s look into the page source code and   look at the javascript. This looks weird now. Start quote, end quote, so that’s an empty string,   and then we subtract this alert(). And to  calculate this subtraction, we first need   to know the return value of alert(), so we  have to execute alert(). BOOM. triggered XSS. Very briefly. What happened. This payload looks  a bit weird because it’s already doing XSS. but   if I clean that up with simply key and value,  and look at the javascript code this produces,   we can see here the key, colon, value. Now only look at this part and imagine   the first and last quote wasn’t there. This is  basically a JSON key, value pair. The problem is,   that the key and the value use quotes. And if  we simply embed this, without escaping those   quotes into another string, then the first quote  terminates the string, and then our key is raw   javascript. If we look into the console we can  also find a syntax error. Unexpected identifier.   This is not valid javascript anymore, right? But with the trick as seen in this payload,   we can make a mathematical expression out  of it, where we combine these strings with   these identifiers through subtractions. A plus or  other operations would have worked equally well. Let’s also have a peak into the nodejs server  code to see why this happens. In the route   handler for displaying a note, we can see  that it takes the note’s content and calls   escape string on it. This is important because the  note is embedded into the javascript on the page.   We must escape double quotes so you can’t  break out. Also closing script tags have   to be escaped. And escape_string uses a neat  trick to do that. It calls JSON.stringify on   it. If we pass a string into that function, it  will escape the string to go into a JSON string   and that is safe for javascript. Only one caveat,  if we place that into a HTML document we also need   to make sure we don’t get a closing script tag,  because that would break this too. So the output,   for this context, also has to escape the angle  brackets. Which is done here. That’s perfect. As mentioned earlier I didn’t find this XSS,  loknop found it first, so I quickly asked   how. And he said he saw the urlencoded middleware  set to extended. And he googled what that means.   Then you can quickly find the documentation  for body-parser, which says that this enables   extended syntax allowing rich objects. “For more  information, please see the qs library.”. And   here we can find “qs allows you to create nested  objects within your query strings, by surrounding   the name of sub-keys with square brackets []” He simply tried this out, like our key value   example, and saw that it breaks out of  the javascript string, allowing XSS. So you don’t really need to understand the server  code much. Or why exactly the XSS happens despite   the escape_string. You can find it just by a  bit of experimenting or researching the code.  But for completeness sake. The issue is  that with this syntax you can create rich   objects. The server expected that content  is a string, but now content can be an   object. And my key value example would  result in this string after JSON.stringify.   And suddenly this string contains double quotes  breaking out of the javascript string. Anyway. We have the XSS now, how can we solve  it. That was actually the hard part.   We had no clue where the flag was. Checkout the  notes I have written when I tried to solve it.  First guess was that we should leak the  whole HTML code TJMike sees. But “I don’t   see anything in there (full output below)”. Here is the whole code I had leaked. It also   contains how I did that. I simply called  fetch to create an HTTP request to a domain   I can see the request and I added  the HTML code in base64 encoded form.   Anyway. This didn’t give us the flag. The challenge description read   “third parties might have implanted it”. What  could that mean. “Implanted”. So I thought,   maybe another attacker XSSed TJMike and  implanted/installed service workers. So Here   is my XSS payload to look for all registered  service workers, but there was nothing.  Another guess. “Implanted” could also mean like  “embedded” in an “iframe”. Maybe the page we XSS   is an iframe. So let’s see what the top URL is,  maybe that leaks something. But nope. Nothing.  Goddamit. Next guess. The challenge is called  “PASTE”urize. copy&PASTE. Maybe we need to steal   the clipboard of the user. Here you can see  the call to `navigator.clipboard.readText()`.   But nope, no luck. Maybe we should install a serviceWorker to perform   man in the middle and capture something else? But as far as I know I would need a clean   .js script hosted on the same  domain. And we don’t have that.  And then the last guess I had was with TJMike,  because it had this microphone. Maybe we need   to record the mic for a bit and send it over. But  I didn’t try. I was too lazy to implement that. My gosh this cost so much time. In the  end I desperately tried something else.   During the CTF I used burp collaborator from  Burp professional. But you can also use one of   those many request bin websites. Create a bin.  Anything sent to this URL will show up here.   So we can now craft a XSS payload with “fetch”,  to perform a GET request to this URL. and I add   to it the document.cookie. I create this paste  and copy the ID. Here is the paste. And now   we can share it with TJMike, and when TJMike  looks at this post, he should execute this XSS,   and send his cookie to this url. If we refresh  now the request bin, we can see we got a request   from a 104 IP and I know that this is google. So  a google server is the origin of this request.   And when we scroll down we find the leaked  cookie string. And there is the secret flag. Solved. This was overall a very simple challenge. But  there was no hint that the flag is in the cookie.   This website doesn’t use cookies. As you can  see here. So it makes no sense to attempt to   steal the cookie from TJMike. And the wording  of the challenge description lead us down a   very different rabbit hole. So in the end I wish  they had hinted more at the cookie. Maybe set a   cookie with “flag would be here” so that we  know, “ahh we need to steal TJMikes cookie”. Anyway. I also saw that John Hammond did a  writeup about this challenge. In a different   style than my videos, so if you didn’t understand  something, maybe checkout his video about it.  Also Gynvael did a full walkthrough of all  the easy challenges from the google CTF.   So checkout his live stream recordings as well.
Info
Channel: LiveOverflow
Views: 65,544
Rating: undefined out of 5
Keywords: Live Overflow, liveoverflow, hacking tutorial, how to hack, exploit tutorial, google ctf, google capture the flag, pasteurize, paste service, xss, cross site scripting, express.js, nodejs, javascript, json, query parameter, extended parameters, ctf, capture-the-flag, web security, bug bounty, bug hunting, websec, browser security, code review
Id: Tw7ucd2lKBk
Channel Id: undefined
Length: 11min 45sec (705 seconds)
Published: Wed Sep 09 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.