It’s already time for my first lesson learned. As I mentioned before, even though I have experience here I’m assuming that I’m starting from the bottom. Without further ado, here’s the first thing I discovered:
Reflective XSS
OWASP explains things so much better than I do, so I’m going to borrow from their page for a bit:
Reflected Cross-site Scripting (XSS) occur when an attacker injects browser executable code within a single HTTP response. The injected attack is not stored within the application itself; it is non-persistent and only impacts users who open a maliciously crafted link or third-party web page. The attack string is included as part of the crafted URI or HTTP parameters, improperly processed by the application, and returned to the victim.
tl;dr: You embed the XSS in the URL itself and send everything as one single GET or POST.
Finding it
Obviously, to exploit a bug you have to find it. This flavor of bug is plentiful and easy to find, apparently. Makes sense that it was the first one I discovered on this great big hacker Easter egg hunt, eh?
Browsers these days have a lot of protections built in to keep you safe from the boogey man, so you’ll probably need a special one. I’m using Mantra, a browser build by the OWASP project specially for this purpose (I’ll probably do a review on it later)
After I fired up Mantra, I browsed to the URL in question to start my investigation, but got a redirect: browser not supported. O noes! They’re limiting me to “safe” browsers. Nothing a little user-agent spoofing can’t fix.
Over on the left-hand side of the browser, there is a vertical bar with a few buttons. Third from the bottom is a a little globe. Click on that and you get a choice of user-agent strings to send, but the default list is pretty weak, so I had to craft my own. Here’s the Mozilla one I used:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:18.0) Gecko/20100101 Firefox/31.0
Enable the user-agent, refresh the page, no more rejection. Now, on to finding our first bug.
The page gives me a form: a username and password with a “login” button. Nothing we haven’t seen a thousand times.
This is where I used a bit of prior knowledge: OWASP says, “Hey, you wanna find an XSS, you need to test all the params the site may be using.” So, I fire up my old friend Burp to capture and examine the requests as they come across the wire.
Capturing the request in Burp and looking at it, I see that I sent a POST with parameters like this:
email=foo%40bar.com&rememberme=on&password=baz
Email looks like it’s using URL encoding, so I’ll skip that one. Let’s try rememberme.
Again, back to OWASP to see what they recommend on trying this.
“><script>alert(document.cookie)</script>
as the XSS, and to do it in place of a parameter.
Therefore, our URL will look like so:
https://vulnerable.app.bar/login?email=foo%40bar.com&rememberme=“><script>alert("pwnd")</script>&password=baz
Let’s give that a whirl.
Nope. Didn’t work. Dang.
Oh, wait, there’s a “Params” tab for each request in burp. Let’s log in properly and see what happens.
Nope, nothing new. Let’s try the other two params.
Nope. No dice.
Okay, going to get coffee…
Came back a few minutes later to see that I had been signed out and look! the URL has new parameters in it! Didn’t catch those before!
So I tried the two new params: let’s call them foo
and bar
.
Trying the first one failed, but the second one worked!
https://vulnerable.app.bar/login?foo=true&bar=“><script>alert("pwnd")</script>
Sweet. We have an XSS.
So how do you know what to put in the URL? It’s got a few basic pieces going on here. I think everyone knows how the first bit is constructed, so we’ll focus on the tail end here.
/login
is the route or page we’re trying to hit. This manifests itself to us as clients as a page, but in the webapp, it’s actually a “route”, meaning that URI gets collected and acted on by falling through a series of checks until it hits a matching route handler. Once it hits a matching handler, that request gets dispatched to the corresponding function. So in this case, I’m visiting the /login
page that’s getting handled by a route on the backend.
?
is the start of parameters. It’s a delimiter.
foo=bar
is just the format for parameters. param=value
and to string a few together, it looks like param1=val1¶m2=val2
. Nothing crazy there.
Now, the fun starts when we try to swap those values out for a little bit of JavaScript. An XSS works because the page will accept and return whatever you throw at it without sanitizing or validating or re-encoding. In our case, the bar
parameter was something it was asking the page to do, which meant it was serving that javascript right back to us.
Why did I use the tag I did? Well, at first it was because OWASP told me to. After some digging, I found a great example explaining why:
That ">
at the beginning actually escapes and extends an HTML attribute, which we can now insert anything we want into, arbitrarily. Yes, it doesn’t have to be JS. It can be anything the page will try to run. Another HTML tag, JS, whatever. We tend to just use JS’s alert()
because it’s easy to return and check that way.
So code that may have started like this:
"]Search for a product on our site:
<input type="text" value="search term">
<input type="submit" value="Search">
Now ends up looking like this:
"]Search for a product on our site:
<input type="text" value="" onmouseover="alert('XSS')">
<input type="submit" value="Search">
With an XSS like this: ” onmouseover=”alert(‘XSS’)
that in the case of this example was injected into a text field, rather than a URL.
tl;dr: Sanitize your inputs if you’re on defense. If you’re on the other team, well, look for fields that might want to return data back to the page and start escaping inputs like there’s no tomorrow.