Ducks, dinosaurs, and XSS: A little knowledge is a dangerous thing in security

Security vulnerabilities are often misunderstood and underestimated. Based on superficial application security knowledge, you might say that cross-site scripting is people putting script tags in form fields. And that’s basically true—but only in the same way as saying ducks are basically dinosaurs. Allow me to explain.

Ducks, dinosaurs, and XSS: A little knowledge is a dangerous thing in security

We all sometimes work with incomplete information and get by quite well. In fact, having the full picture is rarely required in day-to-day life. It only becomes important when dealing with problems that require extensive knowledge about the subject to get everything exactly right, especially in areas where you can’t immediately check if what you’re doing is effective. Application security is an excellent example of this—but before we get to dissecting XSS, let me start with a little anecdote about my past experiences.

Everything starts with incomplete information

I’m a father, and if you don’t have kids, you may be surprised to learn that the most important perk of parenthood is you have an excuse to eat bubblegum ice cream with rainbow sprinkles when your daughter doesn’t finish her helping. And all without a single judgmental look from the ice cream guy. “Yes, I am sure she wants six scoops. Of course she will eat all of it.” 

A close second is getting to think deeply about topics you are so used to that you don’t question them anymore. To kids, everything is new and fascinating because they haven’t yet made all the mental connections you have. And so, they think about a whole bunch of things and fill in the blanks using their current limited understanding of the world. 

In practice, this means that whenever my daughter thinks about something and can’t quite figure it out by herself, she comes to me and asks for the missing clue, expecting a well-thought-out, factual answer. To her mind, I’m probably the smartest man on the planet. In her defense, the poor child just doesn’t know any better. 

The hard truth behind duck factoids

Her questions start with simple topics, such as “Why can’t I just pet any dog that walks by?” After all, our golden retriever loves nothing more than being petted all day. That one is easy: “Because some dogs don’t enjoy being petted by strangers and they may bark or even bite you.” But sometimes, we have more difficult questions, like “Are there any dinosaurs living today?” To which I confidently replied: “Well yes, of course. Birds are dinosaurs!” And I know that for a fact because I learned it from a show about dinosaurs that we once watched together, back when she was more interested in feeding ducks in the park than in determining their detailed taxonomy. 

However, something did not sit right with me after I said it. At a surface level, my answer was correct. You can state “Birds are dinosaurs” and, on the basis of modern science, nobody can disagree. (I double-checked on Wikipedia, so it must be true.) But here’s the problem: based on my off-the-cuff answer, my daughter now thinks that when dinosaurs roamed the Earth, there were already ducks swimming in a pond somewhere. In reality, the first ducks didn’t appear until like 40 million years after the (obviously inferior) non-bird dinosaur varieties went extinct. So even though they are technically considered dinosaurs, they weren’t around back when all the T-Rex action happened.

Quite honestly, I could do well in life without knowing that particular distinction. Unless someone comes out of the woodwork demanding my money if I can’t tell him everything about the evolutionary history of birds (which has happened to me twice before), it’s not information I would ever really need. But in my parental haze of rejuvenated mental connections, I suddenly realized I know how it feels to work with information that superficially looks correct but is, in fact, fundamentally incomplete—and it’s how one young developer felt when faced with his first security vulnerabilities.

Facing my first “real” vulnerability

Back before my interest in application security was sparked, I was “developing” PHP applications, mostly for personal projects and, by today’s standards, nothing to write home about. After finishing one of them, I remember scanning it with a free open-source scanner because I didn’t know too much about security and was a bit freaked out about just having my project exposed to the whole web. What if a hacker came across my site, exploited a flaw, and defaced my very reputable… Well, actually, it was just a subdomain on a free hoster, but it mattered to me. 

So I ran the tool and, surprisingly, only one security issue popped up: “Directory Listing enabled.” Oh no! And I had no clue what that meant, by the way. I was still just developing sites for fun and having that scary message in front of me was enough to convince me I should immediately do something about it. I quickly googled something along the lines of “how to disable directory listing” and just did what it said. Problem solved, crisis averted, everything is fine. Phew!

The key to security wisdom: It all depends

I was still quite young when that happened and, years later, I would become more and more interested in application security. All the different types of vulnerabilities fascinated me and I began researching them. I started with the most prevalent ones (back then and now), like XSS and SQL injections, until I became confident enough to try them on a real target: the German Telekom website. 

They were inviting hackers to try their luck and would hand out a bounty if anyone found a critical vulnerability (XSS was not included but would still land you a spot in their hall of fame). So I went ahead, tried finding even a single XSS or SQL injection, and failed miserably. I probed every parameter and tried every trick in my still very limited arsenal, but there was nothing to be found. Or so I thought— because through sheer luck, I stumbled upon a link that led me to a very familiar vulnerability: directory listing! Jackpot! But… what’s next?

I actually had no idea what to do with it, or why it was so dangerous that a vulnerability scanner would go out of its way to flag it as a problem. Maybe there was some way to view folders outside of the structure it showed me? Because all there was were images. I tried reaching other folders, but pressing the little up arrow just led me back to the website. There was no interesting folder in that list either. I did some more online research and figured it out: the mysterious and frightening vulnerability that threatened my own website years ago was… just not a big deal. There was no inherent danger in a directory listing. 

Today, I know that whether it’s a security risk strongly depends on what’s in the folder, and in the vast majority of cases, it’s a non-issue. Don’t get me wrong, there were certainly directory listings out there that have led to massive data breaches. Long, randomly generated file names containing personal information in a folder with directory listing enabled? That’s critical. Having a bunch of public PDF files visible in a download folder? Not so much.

How hard could it possibly be to filter out XSS?

And so, on the surface, I was correct in believing that directory listings can, in fact, be dangerous vulnerabilities. I just did not know enough to realize that whether they really are dangerous strongly depends on the context. And that sudden realization stuck with me because I’m reminded of it each time I audit some code or website and see security measures that may look great on the surface but fall flat when confronted with an attacker who has extensive knowledge about the vulnerabilities they are trying to prevent. 

A great example of this are XSS filters. It’s a super interesting topic because it’s so complex that even browser developers struggled to keep up with updating their built-in filters and eventually gave up trying. To see why it’s all so complicated, imagine you are developing a web application, like I was back in the day, and want to ensure there are no vulnerabilities in it. You read about some common vulnerabilities, encounter cross-site scripting, or XSS, and wonder what all the fuss is about.

The first thing you realize is that letting users publish HTML code on your web page is A Bad Thing. Right after that, you realize this is exactly what the comment feature on your site does. You’re now faced with a choice: you can encode everything, which would also prevent your trusted user base from using the full range of HTML tags to express their many emotions, or you can simply filter out the bad stuff. After checking out some XSS examples, you notice they all use the <script> tag somewhere.

So, your first order of business: reject all comments with <script> in them! That should be enough, job done… But then some creative user posts a comment like:

<script/>alert("gotcha!")</script>

Oh. I guess you see the problem—there’s no actual <script> there, right? Ok, so we won’t block anything but instead remove any mention of the word “script.” (A side effect is that you’ll never know if a user is talking about Java or JavaScript anymore, but that’s probably for the best. In fact, maybe let’s remove the word “Java” as well, just for good measure.) Any clever moves for that one, creative user?

<scscriptript>alert("hacked!")</scscriptript>

Okay, should have seen that one coming. Well, how about blocking <script in general? And to be sure, also make it case-insensitive so that nobody even has the chance to…

<img src = x onerror = "alert('too easy')">

They can use event handlers? In that case, maybe let’s just block all event handlers. And also the word alert, oh and the word eval, now that you’ve read up on it and realized that it somehow allows you to run code. And also ban parentheses, that way they can’t call any function! Hah, there is no way anybody can get around that one!

<iframe srcdoc="<scr&#x69pt>\u0065val&#x28&#x22[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]][\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28[][[]]+[]\x29[+!+[]]+\x28![]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[]\x29[+!+[]]+\x28[][[]]+[]\x29[+[]]+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28!![]+[]\x29[+!+[]]]\x28\x28![]+[]\x29[+!+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28!![]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[+!+[]+[!+[]+!+[]+!+[]]]+\x28[]+[]\x29[\x28![]+[]\x29[+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28[][[]]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28![]+[]\x29[!+[]+!+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28!![]+[]\x29[+!+[]]]\x28\x29[+!+[]+[!+[]+!+[]]]+\x28+\x28!+[]+!+[]+!+[]+[!+[]+!+[]]\x29\x29[\x28!![]+[]\x29[+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28[]+[]\x29[\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28[][[]]+[]\x29[+!+[]]+\x28![]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[]\x29[+!+[]]+\x28[][[]]+[]\x29[+[]]+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28!![]+[]\x29[+!+[]]][\x28[][[]]+[]\x29[+!+[]]+\x28![]+[]\x29[+!+[]]+\x28\x28+[]\x29[\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28[][[]]+[]\x29[+!+[]]+\x28![]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[]\x29[+!+[]]+\x28[][[]]+[]\x29[+[]]+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28!![]+[]\x29[+!+[]]]+[]\x29[+!+[]+[+!+[]]]+\x28!![]+[]\x29[!+[]+!+[]+!+[]]]]\x28!+[]+!+[]+!+[]+[!+[]+!+[]+!+[]]\x29+\x28+\x28+!+[]+[+[]]+[+!+[]]\x29\x29[\x28!![]+[]\x29[+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28[]+[]\x29[\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28[][[]]+[]\x29[+!+[]]+\x28![]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[]\x29[+!+[]]+\x28[][[]]+[]\x29[+[]]+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28!![]+[]\x29[+!+[]]][\x28[][[]]+[]\x29[+!+[]]+\x28![]+[]\x29[+!+[]]+\x28\x28+[]\x29[\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28[][[]]+[]\x29[+!+[]]+\x28![]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[]\x29[+!+[]]+\x28[][[]]+[]\x29[+[]]+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28!![]+[]\x29[+!+[]]]+[]\x29[+!+[]+[+!+[]]]+\x28!![]+[]\x29[!+[]+!+[]+!+[]]]]\x28!+[]+!+[]+[+!+[]]\x29[+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]+\x28+[![]]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+!+[]]]+\x28[][[]]+[]\x29[+!+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28+\x28!+[]+!+[]+!+[]+[!+[]+!+[]]\x29\x29[\x28!![]+[]\x29[+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28[]+[]\x29[\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28[][[]]+[]\x29[+!+[]]+\x28![]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[]\x29[+!+[]]+\x28[][[]]+[]\x29[+[]]+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28!![]+[]\x29[+!+[]]][\x28[][[]]+[]\x29[+!+[]]+\x28![]+[]\x29[+!+[]]+\x28\x28+[]\x29[\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28[][[]]+[]\x29[+!+[]]+\x28![]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[]\x29[+!+[]]+\x28[][[]]+[]\x29[+[]]+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28!![]+[]\x29[+!+[]]]+[]\x29[+!+[]+[+!+[]]]+\x28!![]+[]\x29[!+[]+!+[]+!+[]]]]\x28!+[]+!+[]+!+[]+[!+[]+!+[]+!+[]]\x29+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]][\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28[][[]]+[]\x29[+!+[]]+\x28![]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[]\x29[+!+[]]+\x28[][[]]+[]\x29[+[]]+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28!![]+[]\x29[+!+[]]]\x28\x28!![]+[]\x29[+!+[]]+\x28!![]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28[][[]]+[]\x29[+[]]+\x28!![]+[]\x29[+!+[]]+\x28[][[]]+[]\x29[+!+[]]+\x28![]+[+[]]\x29[\x28[![]]+[][[]]\x29[+!+[]+[+[]]]+\x28!![]+[]\x29[+[]]+\x28![]+[]\x29[+!+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28[![]]+[][[]]\x29[+!+[]+[+[]]]+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28![]+[]\x29[!+[]+!+[]+!+[]]]\x28\x29[+!+[]+[+[]]]+![]+\x28![]+[+[]]\x29[\x28[![]]+[][[]]\x29[+!+[]+[+[]]]+\x28!![]+[]\x29[+[]]+\x28![]+[]\x29[+!+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28[![]]+[][[]]\x29[+!+[]+[+[]]]+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28![]+[]\x29[!+[]+!+[]+!+[]]]\x28\x29[+!+[]+[+[]]]\x29\x28\x29[\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28[][[]]+[]\x29[+!+[]]+\x28![]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[]\x29[+!+[]]+\x28[][[]]+[]\x29[+[]]+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[]\x29[+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28!![]+[]\x29[+!+[]]]\x28\x29+[]\x29[!+[]+!+[]]+\x28[]+[]\x29[\x28![]+[]\x29[+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28[][[]]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]+\x28[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]+[]\x29[!+[]+!+[]+!+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28![]+[]\x29[!+[]+!+[]]+\x28!![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[+!+[]+[+[]]]+\x28!![]+[]\x29[+!+[]]]\x28\x29[+!+[]+[!+[]+!+[]]]+\x28[+[]]+![]+[][\x28![]+[]\x29[+[]]+\x28![]+[]\x29[!+[]+!+[]]+\x28![]+[]\x29[+!+[]]+\x28!![]+[]\x29[+[]]]\x29[!+[]+!+[]+[+[]]]\x29\x28\x29&#x22&#x29</scr&#x69pt>"></iframe>

Wait, what? Are you kidding me? How is that even a thing? What does any of that even mean?! 

Dissecting an elaborate XSS payload

None of the above is particularly surprising to anyone who has been into security for a while, especially if they’ve dealt with XSS filters before, but that last example is a bit… extreme. Let’s have a look at it in more detail and try to make some sense of it. 

First of all, <iframe srcdoc = "..."></iframe> is just a way to create an iframe with the HTML content specified inside of the srcdoc attribute. Within an attribute value, you can use HTML encoding. In this case, we are using hex encoding to obfuscate the script tag—&#x69 is the equivalent of the letter i. And while it’s not common knowledge, in many cases, you don’t even need to use a semicolon at the end of an HTML entity.

Next up is some Unicode encoding to encode just the letter e in eval—it’s the \u0065 you can see at the beginning of the payload. Normally in JavaScript, you can only use encodings in string values, but the Unicode one can also be used in variables or function names. And staying with encoding, the repeating \x28 and \x29 character sequences are hex-encoded parentheses. In general, encodings are a great way to get around filters and in this instance there were several to choose from. We could have gone with using HTML encoding everywhere as well, but where is the fun in that? 

Finally, there are all these weird brackets, exclamation marks, and plus signs everywhere. What is that all about? Well, JavaScript is a loosely-typed language that allows you to do all sorts of operations on different data types and will happily convert things into the “correct” format for you. Does it always make sense? Absolutely not. Here is an example: 

!0 = true          // not zero is Boolean true
!0 + [] = "true"   // not zero plus empty array is the string “true”
!!0 + [] = "false" // not not zero plus empty array is the string “false”

This already leaves you with the words true and false, so you have all the letters for the word alert, which we can then further build on. Your guess on why adding a Boolean false to an empty array makes it a string is as good as mine—but it really works. I think the eval is not even required in some cases, and I honestly don’t know the exact method to produce the above code since I used a dedicated generator for it.

Assumption is the mother of all… vulnerabilities

All this encoding fun brings us to the following (serious) question: Is it enough to know a bit about XSS and some possible payloads to decide if it’s feasible to just filter out the bad stuff? Of course not. A developer might know that XSS is bad and can be dangerous. They may find a solution that seems sensible based on the information available to them. But without complete knowledge of all the possible XSS payloads and the many unexpected features of JavaScript and HTML, they simply have no way to build an effective filter that will leave all the harmless HTML alone while filtering out all the bad stuff.

That one realization was enough to convince me I shouldn’t try to write my own XSS filters. Yes, I’ve dealt with a lot of filter bypasses in the past and I know a thing or two about them, but I don’t share my daughter’s view that I should have been admitted to Mensa International long ago—I was there when I threw my phone in the waste paper bin instead of the paper I was holding in my other hand. Despite all I’ve seen, I doubt that I know every possible way of bypassing an XSS filter and I’m sure that if I wrote one, some edge case would eventually invalidate it: multiple injection points, some new browser feature or quirk I’m not yet aware of, browser-specific behaviors… The list goes on.

The same goes for many other vulnerabilities and programming functions that, in reality, do something different than we assume, starting with misconceptions about things like the dangers of SSRF (“It’s just sending requests, you can do that on your own machine with curl”) or XSS (“Why would a popup be a danger to my website?”) I don’t think there are many people out there who completely don’t care about security and write insecure code on purpose. Especially in mature code bases, quite a lot of vulnerabilities are caused not by ignoring security but by people misunderstanding or underestimating the nature, scope, and impact of these vulnerabilities. 

Conclusion: It’s the unknown unknowns that get you

I’m sure we all have a duck swimming in a pond next to a dinosaur somewhere in our minds. For so many topics, we go through life with only some vague general concepts in place, filling in the blanks with our incomplete knowledge of the world. The problem is it’s not always clear when and how to dig deeper, or even if it’s possible. In most cases, we don’t care and it might not matter much—but for topics where the details really matter, like security, we should really spend some time to examine and challenge our existing assumptions. 

That doesn’t happen overnight, of course. But if you are a developer, security engineer, or anyone else tasked with building, testing, or auditing a feature related to application security, you should definitely get into the habit of asking yourself: “Is this sufficient? Could there be something else I don’t know about?” Crucially, you don’t have to figure it out on your own. There is rarely a mistake that’s not made twice. There are great writeups out there about bug bounty targets, published CVEs, and security bugs. There are articles in magazines like Phrack and countless posts on social media discussing exactly that.

But also don’t be afraid to get an expert opinion in the form of a pentest or a vulnerability scan using a good scanner. Both preferably from reputable companies that will give you all the technical details plus actual guidance on how to improve your security posture—because on the stormy seas of security, the “ducks are dinosaurs” level of accuracy simply does not hold water.

Sven Morgenroth

About the Author

Sven Morgenroth - Senior Security Engineer