← All talks

Tales of DOMinica - Liam Follin

BSides Galway23:4120 viewsPublished 2026-08Watch on YouTube ↗
Show transcript [en]

Thank you very much. Um, and thank you all for coming down and listening to me talk about effectively JavaScript for for the next 25 minutes or however long we have. Uh, full disclaimer, I was I was a backup speaker. So, I was only told that I was speaking yesterday. So, if any of this seems horribly rushed, it's because it is. So, hopefully you can come on a bit of a journey with me regardless. Um, we're going to be talking today about type zero cross-site scripting or DOM-based cross-site scripting. This is a kind of variation on the traditional forms. It's commonly overlooked. Um, and in fact, I have a weird obsession with it. So, you're going to sit here and if

learn about it with me today. Um, if you already know about it, then hopefully you can pick up a few more tricks. Are there any app pen testers in the room? Ooh, lovely. Okay. Anyone love JavaScript? Weird bastards, honestly, the lot of you. Um, oh yeah, I it's a 15 I did look up the age ratings. Um, and apparently 15 means I'm allowed to swear, but I can't be aggressive about it. So, just don't ask any stupid [ __ ] questions at the end. Um, quick, who am I? There's Twitter, there's GitHub. Um, there's actually some labs that I've been really that have I've written and have released um, that you can spin up and and try

practice some of these techniques on yourself. Um, if you want to look at the the GitHub. Um, the blog is me ranting about JavaScript. Um, that's that's literally all it is. Uh, but if and if you want to connect on LinkedIn and uh, have a have a conversation or exchange cat pictures, that's that's always a good good fun thing to do. Um, that's my email, please don't sign me up to any weird sites. I I get a couple of like, "Are you you a new porn hub account has been created?" every time I I do one of these. So, um, the account already exists, it's not mine, unfortunately. But, anyway. Right, onto the meat and potatoes,

honest, your honor. Right, onto the meat and potatoes of this. Here's uh, the kind of the the primary three types of cross-site scripting. We have reflected, stored, and DOM-based. And the primary difference between reflected and stored, which is the ones I'm sure most people in this room have have heard of, are reflected is kind of non-persistent cross-site scripting. So, you send something to the server, it's incorporated into the response on the server side, and execution happens when that lands back in your browser. Stored is a very similar process, but it's actually stored. So, normally it's stored within database on a web application. It's then read out of the database, incorporated into the response on the server side, and then returned

back to you. You can do a lot of testings for these in using services like Burp Repeater tool, for example. You'll see that the HTML or the attribute that you've injected has has been reflected. You can see kind of see it all coming up. DOM-based is a bit weird cuz it's not It's not the same in that the incorporation or the execution kind of vulnerability, as it were, doesn't appear on the server, it appears in the client. Which leads on to some kind of slightly unique exploitation scenarios where your payload may not be appear in the response. So, if you're doing testing using tools like Burp Repeater, you won't see it in an exploitable format.

You might start missing it. And in fact, when I was kind of cutting my teeth as a as a pen tester many years ago, finding things like this was how I showed off to my boss and I tried to get promoted and failed. But So, the execution for this one actually happens after the payload's been processed by client-side JavaScript, which becomes important when we're working working through all of this. As a bit of background, I first got interested in client-side JavaScript vulnerabilities. I saw a talk by a fellow called Tom Nom Nom. Has anyone heard of him? He does a lot of work in the prototype pollution space. And I was obsessed by Well, effectively I became obsessed with

the idea that a payload could only Well, you know, exploitation could actually happen in your your user's browser because of vulnerabilities you'd introduced in the in the browser and how those things interact with one another as opposed to it purely being a server-side issue. And that got me really interested and now I'm obsessed with JavaScript and here we are. So, uh who knows what the DOM is? Excellent. Right, for the for the for the uninitiated, it's effectively like it's a it's a programming interface for JavaScript to interact with the HTML and it's a if you ever view source on a page and it doesn't look like most of it is there. Um a lot of these client-side

frameworks now kind of build the application that you're using. You'll You'll hear them called single-page apps a lot of the time. Um not because they have a single page, so that that does trip a lot of people up, um but because it's technically only one page that is redrawn client-side, so it reduces a lot of the the load on the on the server and means the browser has to work a little bit harder to to do that. Now, JavaScript is a weird language. I love it and I'll be the first person to admit that. Um some of those quirkiness and a bit of that funkiness is is one of the reasons why DOM access gets gets

missed so much um and it's also what makes it slightly harder to to necessarily exploit. So, some of you may be familiar with what we're going to do here, but 2 + 2 in JavaScript is No, no, four. Nice, great, okay. What about 2 + 2? There you go. What about 2 + 2 - 2, all strings? What are we thinking? Two? 20, but it's a number this time. Great. Why does that work? Well, it works because of something called um type coercion, which again we can go into a a little bit later if if we have some time, but it's weird stuff like that that allows you to again just do funky things with JavaScript

that are that are quite cool. At least I think they're quite cool. Um although as I've always said, normally I bring red wine. Unfortunately, I couldn't get it through through customs as I flew out yesterday, so um we we do need some some some red wine. Normally, a little glass of red does uh does wonders when you're trying to make sense of some horrific JavaScript that some developers bytecode it. Um so, uh there you go. Medicinal purposes, I've given you permission to drink red wine later when you're trying to do do the labs. Um please drink responsibly. So, are we ready? We've learned the JavaScript side of G. There's three types of XSS and the red

wine's very very tasty. Um if we're all sitting comfortably, which I hope we all are, let's get on to this. Um so, the first step of exploiting DOM XSS is understanding two things. One's called a source and one's called a sink. Sources are the place where user supplied input is passed to JavaScript and the sink is when is where execution occurs. Normally, that's via a kind of inner HTML call or or some kind of reincorporation back into the DOM. Um occasionally, it's using things like eval and and things like that, which we'll get on to. This is the the first of the challenges on the the labs that I've produced. It's very very basic. You probably wouldn't

see this in the wild and if you do, should have words with whoever wrote it. Um we have a form, sends a get request with the URL parameter search. Um you have some JavaScript there. So, you have a tracking function. Um there's um URL parameters which just grabs the URL parameters from the search bar. Um you grab the search term from that and then it runs the tracking function with that with that search term and that just grabs the tracking and sets inner HTML people search. Any idea what payload we might be able to use here to exploit this? Script tags, maybe? Let's give it a shot. So, this is the standard Sorry, this is

a little bit small, but at the top there, you can see we've got a script alert one and then a closing script tag. And we'll also we've also seen that there's no execution occurring here. Um which we'll get on to. This is one of the reasons why DOM XSS is slightly harder, because you can see here that it is it has been incorporated um just at the bottom there. So, the script has been injected, you know, we follow that through with script and search terms and we inject that back into the DOM. Um but there's still no execution. So, if you were using kind of this is the the classic bread and butter cross-site scripting demonstration. Um

and if you were using that, you would have missed DOM XSS because script elements inserted using innerHTML will not have been evaluated. So, if you were trying to find this XSS, you wouldn't find it. You'd say, "No, no, no, there's no vulnerabilities there." When in actual fact, what we can do is we can use image tags. Anyone heard of a kind of broken image tag payload? So, image source equals X or one on error equals and then your your JavaScript. So, using error handlers in a in a broken image tag. Um I always wanted to write a lab where both /x and /1 resolved to a valid image and just watch people tear their hair

out as which is clearly what's happened to me with all my JavaScript love, but I have not decided to be that evil yet. So, if you use an image tag, it does render because again, images are allowed to evaluate. And then if you throw your broken image tag, you can see that we get our our alert there because it is again injecting it back into the DOM. Um the source fails, the on error handler is then triggered, and our alert happens. Which is But, if I want to fuzz a parameter for cross-site scripting and see if any parameters are reflected, you occasionally do this using kind of Burp Intruder. Other proxies are available, but are

worse. And uh again, you you just wouldn't you wouldn't see it there. Um you can see we've searched for image source X and then in the response, we search for that that string and and it's not appearing cuz again, it's being grabbed client-side and then reincorporated back in. So, uh just to really home there that you can't use fuzzing tools or traditional fuzzing tools to try and identify these sorts of things. Uh but now has um DOM Invader built built into its inbuilt browser. Um DOM Invader does horribly break some applications, uh but it also will help you identify some of the kind of slightly easier versions of this. It's also really good at prototype pollution

um if you ever find that. We'll move on to a slightly uh slightly more realistic example. Um this is loosely based on on sinks that I have actually seen uh within um within applications. Um the kind of traditional basic protections are generally around blocking kind of alerts. So, you know, most people if you've ever tried to mess around with like WAF bypasses, um you can say oh well alert will be blocked so you'll have to use prompts or they won't allow you to do function calls. They normally write some really weird regex which is like there's a string and then brackets. No, you're not allowed to do that. Um so, but what fortunately we are able to to defeat

that. Again, we are looking slightly more realistic here. There's more JavaScript. Who here loves JavaScript already? No? Okay, yeah, one or two more. No. Hopefully by the end of this you're all going to be obsessed. And then you'll waste your life talking about it. Uh it's a good life really. So, we've got another uh tracking function where evaling um document.getElementById tracker in HTML. So, it's very similar to the one that we saw previously. Um but again slightly slightly different there. Um we grab our search term. Um we run the the tracking uh function using it and then it it evaluates. Now, is the sink here in HTML or eval? Now, oh yeah. Sorry, I missed one bit.

Um we're actually running encodeURI on the uh on the URL search params as well. So, if anything that uh just makes things safe to be incorporated into a URL JavaScript means it won't break anything. Um so, let's try the innerHTML call. So, well, what we'll do is we'll try and add an attribute. We'll do it as something called attribute injection onto this. So, we're injecting into the source. We're going to try and close out the source, add an onerror handler, and then um uh kind of and then that will when that gets reincorporated, um we'll then uh get our execution. You can see here though that unfortunately uh encodeURI doesn't encode double quotes, so you

can't break out of that um using that particular particular technique. Um or search works. And then oh, I'm sorry, it encodes spaces there. Um so, you you you're not necessarily able to do that unless it's incorporated into the uh into the source value of our of our image tag. Um unfortunately, properly escaped, so we're then not able to to use the innerHTML. But, that is not going to stop us from calling alert one because what you can actually do instead is encodeURI doesn't encode single quotes, slashes, or brackets. So, we can break out of the innerHTML call and instead call alert one. So, what that actually looks like here is if we have our search

term as as alert one there, and we're using kind of some chunky string uh concatenation at the top. As you work your way down here, you can see that the the function call that's getting eval'd is actually image source equals, so it's injecting a broken image tag, an actual broken image tag as opposed to a payload of a broken image tag. Um and then we're calling alert one there as well. So, our payload is uh as you can see, it's the search term, it's the single quote, semicolon, alert one, and then two two forward slashes, which is just a JavaScript comment, so you can comment the rest of the string out. Now, let's get on to the real deal.

This is an actual vulnerability that I exploited in an application pen test about three three years ago now. Um it's it's full to DOM based cross site scripting. Um, the team that we reported it to was bloody fuming cuz it had been missed by 15 years of check testers beforehand. Um, so it would have got high level pen testers within the UK. And I was over the moon. I was the smuggest little bastard you've ever seen for about 2 weeks after this. I wouldn't shut up about it. And in fact, I'm still banging on about it now. So, clearly I'm clearly I'm very proud of the of the DOM exercises. Um, this forms on the labs

that I've released. This forms, um, the this is the the real life example of challenge 16 within the the labs. I do need to add some more real life examples. I found found some more since then. Um, but this one was was so cool. I I loved it out afterwards. Um, and and well, I'll run you through it. I'll run you through it now. Um, so, again, we go back to understanding the the problem. We have a an input with a value. So, this is just an arbitrary URL or a path. If our URL is document Yeah, so you grab the value out of the input. Um, you have the iframe. Uh, you grab the [ __ ] value of an

iframe and you set the iframe source to uh to the URL that we've we've just grabbed. So, who here knows about JavaScript URIs? Silence is golden. Right, so, yeah, a little bit. So, a JavaScript URI is a is a valid So, JavaScript is a valid protocol. So, you know how we've got HTTP, HTTPS. You can actually use JavaScript colon and then JavaScript and that will execute in a in a browser. In fact, um, if we we should have some time afterwards uh cuz I've had a lot of coffee this morning. So, I'm speaking quite fast. Uh, to to actually demonstrate this properly. But, you in sources of iframes, if you have JavaScript colon and then alert

one, that will actually execute. And we can abuse this if things are ever written insecurely to um to the sources of iframes. Um, you can actually use this in in browsers. So, browsers will support both data URIs and and JavaScript URIs. Are there any red teams in the room? Excellent. One. So, you can use data URIs as a um to bypass web content filtering as well. Uh to as a kind of infoleak spill um kind of localized browser exploit um to to do fun things like that. But, that's an aside. That's not what we're here to talk about today. We're here to talk about JavaScript, which is way cooler. So, view based on this information, we

can attempt to exploit it. So, JavaScript one there, that is a valid URL, but it's been blotted out of the response. We haven't been able to do our injection. Um and this is a more accurate representation of what happens in modern applications. It will be some of it will be client-side and some of it will be server-side. There'll be some server-side validation going on that you'll have to bypass before you can then get into this. I'll This took me about 7 hours sat in a site in in the UK. Um and I will spare you all the sweating and swearing and all the other, you know, frustrations that I went through over the course of those of those 7

hours. And I forgot to take lunch and I get really hangry, so I probably should have just broken for that. And then, well um anyway. This is the representation of the uh back end of of the function that we were we were testing. Um so, you can see it's if your URL the first is just a if there's a you not URL parameter set, it'll it'll set one. Um if there is a URL parameter set, then it will grab it. Uh if your URL starts with a slash um or your URL starts with two slashes, um two slashes is the shorthand for whatever protocol the page is being loaded in. So, if you don't know whether your page

is going to be loaded over HTTP or HTTPS, you'll just use two slashes as a as a way to do it. You can actually use that for open redirection as well. Um your browser parsing will strip uh, certain characters. So, if you do So, So, if they blocked two slashes at the front of a redirect parameter and you're trying to get open redirection, you can do {slash} %09, which is the tab character, and then another slash, and then google.com, and that will actually, once it's processed, it'll strip out the tab, and you'll get two forward slashes, and then you'll be able to redirect yourself or your unsuspecting user to all the nasty little places you want to

send them. Good fun. But, um, if the URL doesn't start with https or javascript, in the This is just a kind of mocked up example of this. In the actual example, they were using a library to to do the validation. Um, that library, uh, effectively just checked checked checked if the, um, uh, if there was a valid protocol and then colon {slash} {slash} in there. Uh, javascript, of course, being a valid protocol, so that bypassed the bypassed the validation. Um, and then this also checks, uh, when you run your old path, um, um, and that and that lock on there, so that just grabs the domain part of whatever request you're sending. Um, if that doesn't equal the host header, then

the URL is is that. Um, and then it's going to unquote it and and return it. So, using this, we can build this. Uh, beautiful beautiful payload. Javascript: hello {slash} {slash} then the I mean, this is running on localhost, so obviously in the example, when we were actually exploiting this, it wasn't. Um, took bloody ages to figure that out. Uh, and then %250A. Now, who here's good enough at URL encoding to tell me what %250A is? Yes. >> It's a percentage. >> Yes. So, %25 is percentage, and then 0A is a new line. Um, so, what this does is it, because of how URL decoding works, when this gets passed back to the server then

reincorporated into the response, it'll be decoded once. So, this then goes to localhost %0A 1 is then processed again when it gets sent into the into the source of an iframe. What this does is it starts your JavaScript with a comment of localhost and then it goes on to a new line because two forward slashes is just a it just comments out that line. You're then on a new line you can call alert one and that will then execute. So, when this is then reflected, you get your JavaScript execution nicely and you run around the office and you high-five people. Thank you. Oh. So, do this. Anyone do any sports, combat sports in the room?

Yes. Yeah, a few people, cool. Uh so, this is me rolling around on the floor with some some guy trying to break his ACL. Um with their ACL. With I'm very friendly, I promise. Um and again that that being able to exploit things like this or being able to um uh to to kind of find novel vulnerabilities, especially niche ones like your DOM XSS and some of the other ones that we've had, that does set you apart. Um if there are, you know, pen testers in the room or somebody who wants to get into security testing, then um being able to find things that others don't is uh makes you look really good to your bosses and hopefully gets

you promoted and paid more and then you can thank me by buying me loads of beers. Woo. Quick start on to can we fix it? Yes. Um you can't uh stop trying to trust user supplied uh user supplied content. Um this is the the big one around cross-site scripting. Um there are libraries out there that will do a lot of the escaping or or heavy lifting for you. They're not infallible um but if you are injecting things like this, just make sure you're using tools like DOM Invader. You don't have to be an expert uh in things, but uh things like that will will help for sure it up. Um and yeah, try and encode content when you

can. Do um uh uh entity encoding. So, you know, um use tag signs become and LT and GT, um things things like that. We've got some resources. So obviously PortSwigger is great. The labs are up there as well that I wrote to help people learn a little bit more about this. Code Wars is really good for learning JavaScript. You get your like you get belts assigned to you. It's all very funky. And xsses.uk is run by one of the testers over at Pentest Limited in the UK. And it's a kind of leaderboard based CTF challenge style thing with loads and loads and loads of cross-site scripting challenges on. And they now do SQL injection challenges as well.

Um and yeah, see if you can get yourself on the leaderboard. Um I spend far too much of my time on that site. Well, thank you very much for listening. I think we've got 2 minutes for questions if anyone has any. I definitely didn't explain it that well so. Yeah. Yes, mate. >> Have you dived into browser logging XSS? >> No, no, unfortunately. But that is a fascinating topic. I do like reading about it. Any more for any more? Yes mate. >> Yeah, could you like do like a demonstration of how you would use like Burp Suite? >> Yeah. >> And DOM Invader? >> Yeah. >> Yeah. So DOM Invader Here's one I prepared earlier.

Is anyone doing the that? We that one? No, I'm really I'm sorry. Aha, there you go. [ __ ] See if I can This is quite hard to do. Beautiful. So this is the This is a CTF that's going on. This is actually very difficult. But you see how up there in the top corner there's some some plugins. If you switch that one. Yeah, so DOM Invader is currently off. You switch that on and then you reload the page. And uh yeah, I'm not connected to the internet, but that would um that does a lot of things for you. Then there's different attack types, DOM Clobbering and Prototype Pollution. Um so you just kind of iterate through those. That's

how you'd use that particular tool to help you uh identify stuff. Um there are of course other options as well. There's some scripts on GitHub. Um or you can just sit and get weirdly obsessed with JavaScripting. Give it a try yourself. Awesome. I think we're out of time. So uh thank you very much for coming and I hope you have a good night.

[ feedback ]