← All talks

Building An XSS Playground - Paul Johnston

BSides Leeds26:3990 viewsPublished 2025-08Watch on YouTube ↗
Speakers
Tags
CategoryTechnical
Show transcript [en]

Hi. Uh hello everybody. So uh I'm Paul. I work as a pentester at Pentest Limited. Uh now a couple of years ago I was just writing up a blog post based on some research I'd done into Uniode XSS. Uh most people are familiar with the wide ASKI variant of XSS. Um, but I've been looking into uni code attacks using combining characters. Now, when you're blogging about a topic like this, it's a really nice touch to include a lab environment that people can experiment with that demonstrates the the exact behavior uh that that you've identified as vulnerable. Now, it's easy peasy to spin up across scripting lab. There's loads of uh free app hosting services. Um but the thing is XSS it's not really

a user to server attack. It's a user to user attack. Um so when you're using a lab environment that just has the server component there's a little bit missing in that you don't have like simulated victim. Um, so what would be handy to have is some kind of headless browser system that simulates a victim so you can confirm that your XSS payload actually works on someone else's browser. And it's a bit harder to develop that than it is to develop an XSS lab. Uh, and I thought about this and I thought, well, I can I can make one. And actually, if I do that, this isn't just applicable to the XSS lab I want for my

blog. this is actually more generally applicable. And then I thought, well, it would be really handy if someone could provide a hosting environment where you could just host your custom XSS lab and then it could benefit from sitewide infrastructure specifically from the ability to simulate a victim. And I had a look around and nothing like that existed. Uh, so I thought in the sort of spirit of hacking and the internet and open source and all that that um that I'd uh get busy and make something. Okay, so here's the site. So what we're looking at here, this is a React front end uh talking to a KTOR back end and the key page here is this list of all

the labs. Um so anyone can contribute a lab in practice. The the vast majority of these are ones that I've made. uh and it cover three difficulty levels. So the easy labs go through the basic XSS skills. So So I'd expect that like most pentesters would be able to solve the easy labs. Um and that people who maybe aren't pentesters wanted to learn about cross description. That's a good place to start. The moderate labs each one of them to successfully exploit them you have to use a specific advanced technique. Um, so I think in experience that pentesters would know some of these, not all of these. If you can learn how to do all the moderate labs, you're going to have

pretty sharp excesss skills. And then the hard labs, these are a bit more like research projects in that I wouldn't ex I would expect that most very experienced pentesters would struggle to exploit the hard labs. Okay. So let's um let's start at the beginning. So the very first easy lab which is this basic reflective XSS. So when we click into this we've got some basic information about who's created it rating and stuff. Um there's also a link to a solution video. Um, I realized that while there's quite a lot of content that explains stuff in long form, I just felt it kind of fitted with my educational style to avoid having much uh written stuff and focus

on videos. But the really interesting bit is this button we've got to launch the lab. Now just one thing to notice once we click into the lab uh we're now in a subdomain. The idea is all the vulnerable functionality is kept in subdomain. So the the main xssse.uk domain hopefully is is secure. Um also there's no react here. This is like direct server side rendering. So, um, I'm figuring most people here are going to be familiar with XSS, but I'll just I'll just start from the basics here. So, if we if we just look at what the app asked us to do to enter our name and it says hello to us. So, this has

demonstrated the the major component in that there's reflection. There's user input taken and it's rendered in an HTML page. Now the next question is how does this handle HTML tags? And when we look at the reflection, the thing to observe is that that italics tag that has taken effect. We've we've seen that it's it's made the text be visually itallic. And if we look at the page source, we can see that that tag has been reflected exactly as it's been input. There's been no sanitization done. So this is a very strong indicator that there's going to be a cross-ite scripting vulnerability. Okay. Now, if we just go back to the lab page, we've got an objective here. So

run the following JavaScript code. So I can copy that to the clipboard. And what I'm going to do now is inject a script tag and paste in the JavaScript payload and close the tag. Okay. And that popup demonstrates that we've been able to inject JavaScript into the page. Now, um, for this vulnerability, the only objective was to inject JavaScript. you don't need to go further and start stealing the users's cookies and doing unauthorized actions. And the other thing that makes this lab uh particularly easy is if we look in the URL, we can see our payload is right there in the URL. It is a get parameter that is vulnerable. And what that means is to attack a

victim, we just take this URL and we send this to them in some manner. If it's by email, by instant messenger, whatever it is, we send it to them and there's an element of social engineering and that we need them to click this link. Now, for the purpose of the lab site, this social engineering is out of scope. We just take the URL and then here we've got the ability to submit a payload. So what what that's doing that's simulating this idea of sending it off to a user and that the user social engineer to click it and we can see there that it's it's confirmed we've successfully um injected ISS and it's confirmed that

we've solved the lab. Okay. Now what I what I'll show now is uh how we go about constructing that lab. So we've got this my labs page here. Uh and we've got the ability to create a lab. So I can create one right now for resize this. Uh, so there's a whole load of options that control some of the finer behavior, but we're going to leave all those at the def as the defaults. And we can see here that there's two files that make up the lab. Just close our previous tab. And we can preview the lab. We can see we've got a little skeleton. Um, and then we can start customizing it. So, if I want to put some custom

text in here,

I can I can edit the HTML. Then when we go back here, we can refresh it. So, we've got we've got full control. Now, that's just a static HTML page. For the interesting behavior, uh we need to be dynamic. So the form it's got this action. So the the action is it will it will send the form data to target.ftl. Now targetftl this is a free marker template. So free marker is one of the leading Java templating engines. Um it's pretty customizable. So, it's both sandboxed and it gives lab writers a fairly free ability to make the lab work how they want. Um, now right now it's just echoing the content back. So, this is going to be

easy peasy to exploit. In fact, I feel that might be a little too easy for our sample lab. So, what we can do is we can now start putting some sanitization in place in the lab. Um, I mean, I could I could do that and this would be properly sanitized, but what what is quite helpful to do is to do sanitization that's incomplete so that people can still attack it. It's just that it's harder than a basic example. So, what I could do is use the uh replace function to to remove script. So just save that. Now if I refresh this, we no longer see the payload. And if we look in the source, we can see the bits

of the tag that are left, but the script bit has been removed. So we need to do some more advanced technique. Now there's there's loads of things we could do, but what what is sufficient actually for this example is to use mixed case because we were only stripping out based on the lowerase script. If we do that, we've successfully got a payload through. Um, something that's worth pointing out is at this stage, this lab is private. So to access it, you need to have this token in the URL. Um, so some some other person can't just come along and look at it. And if you wanted, you could share this with colleagues. But where it gets interesting is when we

publish labs to the the main labs page. Now there is one quality control before a lab's allowed to be published in that the lab author has to submit the working solution. Uh someone did actually say to me in the corridor before this that there's one on there they thought was not solvable but I know for it to have been published the the author had to have submitted something and we are going to go in the backend database and dig out the the the payload they used. Okay. So we'll take this working payload and it we can submit the payload to the headless browser from uh from the lab manager screen. And now that we've successfully solved it,

we can publish the lab. If you look now new labs page, we've got a besides leads lab. Now what I am going to do because this is just a demo, I am now going to immediately unpublish it. Um, all this stuff, it's only reactively moderated. So you can just jump on and publish stuff. But if I see something that I think's not in line with the quality of the site, I will uh I yeah, I'll I'll hide it. So, back on my labs and got an unpublish button.

Okay. Yeah. And it's gone. So, that's the that's like the lab creation side of it. Um, now I just wanted to show you as well the a little bit about how the headless browser works. So I've got a development environment here. What this does is this drives the browser using Selenium. So we'll just kick this off now and we'll see it pop up. It's going to submit a payload. It causes a popup and then it closes. [Music] And if you if we look this out here um so the code it's extracted the flag cookie uh from the back end just in incidentally this flag cookie every time you access a lab with no flag cookie you

get a random one allocated so it's quite transparent. So out of this selenium driven browser, we've extracted the flag cookie. Uh we've captured the alert text and then we've confirmed that the alert text contains the cookie. So that proves that the target JavaScript it's been delivered and it's been executed in the right domain context. And just to prove this isn't all smoke and mirrors, if I go into the payload here and say there's a typo, um, when we redeploy this, it might just it might just take a minute to build, we're going to see different behavior. See, it's now it's popped up saying undefined. And if we look at the output, extracted a flag cookie, but the alert text, it's

not what it's supposed to be. And so it's not the lab is not successfully solved. Now, just just to demonstrate some, if I put that back now, this was done with a with like a normal headed browser that interacts interacts with the desktop. Um, but we can pass a headless argument into Selenium. And what's going to happen now is all the same mechanics with the browser is going to happen. It's going to run the payload in the browser. It's going to detect the alert box, extract the cookie, determine success or fail, but it's going to do all that without interacting with the desktop UI. And there we go. The payloads worked. We didn't need an actual desktop. Um, so

what what I've got is um I've got a Digital Ocean VPS just running the headless browser. Um, I figured early on that this was quite a key concern for the robustness of the site. So there's one VPS that runs the the web app and a separate VPS that runs the headless browser just and a little micros service that allows them to communicate. Um, and then only registered users can submit URLs. You can only do one every 60 seconds. Um, and it's talking through a proxy. So that that headless Chrome goes through a Squid proxy. The Squid proxy will only let it talk to XSSE domains, not anything else. Um, I do I do worry that there might be some

weaknesses in my lockdown. Um, but it's pretty I mean ultimately even if that server got trashed, it wouldn't actually matter that much. Now, now that we've got this infrastructure of a headless browser that can prove that the user successfully exploited the lab, this is really handy because we can now do uh a bit of gamification based on the successful solutions. Um, so when you solve a lab, you go on the hall of fame. I mean, there's because this is the basic lab, there's tons of people on there. If you look at the the more advanced labs, there's only a more select group of people who've got through it. And you get one point for an

easy, five for a moderate, 10 for a hard. And that what that also likes to do is that there's an overall hall of fame for the for the top solvers. Now, as we as we go through the labs, I've had to develop all sorts of functionality to properly support more complex labs. Um, now if we if we just look again at reflective. Now, there's no stored features in this. So, each time we launch the lab, we get the same URL. um but that won't cut it for doing stored XSS because one person's payload would interact with another user's um work. So the way stored works is every time it's launched you get a fresh lab URL and any save saved state is

specific to that instance. It's not shared with the other instances. Now the next bit that um that that needed more infrastructure was this one. Now if I just take a quick look at this. So if I inject in inject the script tag. So an initial look at this post request XSS is that it's trivially exploitable. But the difference is the payload is not in the URL. So we have to work out something that we can send to a victim that they'll click and it will execute. And the way that we do this in the wild is that the attacker would have an attacker controlled website which they link to and then cause the form post.

Now we've already got all the infrastructure for creating labs. So we can actually leverage this. So everyone gets a payload hosting lab. So it's it's basically the same as the other labs. There's the it's less configurable because most of those options wouldn't be useful, but the core of having different files and the lab URL that's exactly the same. Uh, and what I've created here, so it's a form and it posts off to the to the target site. It's got the payload in the form values and also it's got an onload handler so that as soon as the page opens, it's going to submit the form, trigger the exercise. So all we need to do here

is one click and an exploit. And what we can do is we can right click and copy that URL and go back to the labs page. So now we're posting in not a link to the target site but a link to our payload hosting site. Um, but when we run that under the hood, the exact same thing is going to happen. And then because the headless browser has seen the alert box, we've successfully solved the lab. Now, um, another piece that involved um, creating more infrastructure was this lab. Now the objective is different here. The objective is to capture the cookie. We launch this. There is a little hint in the rubric that we can use the access

log on our payload hosting lab. So if we look down here, there's an access log. And if I just try from my browser, we can see we've got a couple of requests. So again, I'll copy that address. Now, the standard trick for extracting a cookie is to create a new image and and load it from an attacker controlled web server and append document cookie to that. So if I submit that, well, ostensively nothing's happened. Um, but we can see from the access log, we've captured a request that contains the flag cookie. Now at this point, we have only exploited ourselves. So what we need to do is grab the URL and paste it into here. This will send

it off to the headless browser. keep watching our access log and we can see now we've grabbed another flag cookie. If we take that we can paste it into here and we've successfully solved the lab. Um so there's all sorts of functionality here um to support all sorts of advanced cross scripting scenarios. Um so we've been through like stored payload hosting lab um and the access log uh the support the support for raw sockets custom headers URL URL rewrites and there's loads of functions for doing things like uh stripping of HTML tags extracting metadata from JPEG images all that stuff to to make it possible to host most like really sophisticated labs, which is what what the moderate

and hard um sections rely on. Um and just to give you a little teaser, all that time we were logged on uh as demo user um I've got an admin user as well. So we can for example um have a nosy at the payload. So, okay, a lot of these just now that was this is the demo I was doing just now and this is I think me practicing last night but you can see there's other users submitting things you can see see whether it's work I sorry I can see by the way if people get to 100 score I don't always do this but if it and I've had some communication with I normally

give them access right to uh um uh to view the payloads Yeah. So, it's been it's been a load of fun making it. Um, I guess I'd say as a little call to action and be come and get involved. I'd love for more people to submit labs. I'd love for some people to submit third party solution videos cuz right now it's just me and people are probably sick of my voice. Um, one one area that I've not explored at all and could be a lot of fun is labs that rely on specific vulnerabilities and JavaScript libraries. You know, there's all these things like version X.Y jQuery UI. It would be quite cool to have a whole

section that targeted out outdated JavaScript libraries. Um, and beyond that, um, couple of things I've got in the pipeline. I do want to create a a CTF mode uh that would be for use in person at conferences. So you could like get into teams and like have a team score maybe do it times. Not not figuring out exactly how that would work. Um and then although I've bought boxed myself in a bit with the name of the site, I do want to add SQL injection to this. it it would be really cool that you could just spin up a virtual database and a bit of app code that had the had particular vulnerabilities and just like how the headless browser is

off on a separate server I just have the vulnerable database off on a separate server from the from the main app. Um so that's me. Uh hope that was useful.