Pentesterlabs - Web For Pentester 1 - XSS Challenges


If you haven’t already, please read Web for pentester 1 - Setting Up

This guide will walk you through the Pentesterlabs - Web for pentester 1 XSS challenges.

The XSS challenge consists of 9 different tests, increasing in difficulty level as you progress.

Each level employs new filtering and protection techniques, so it is imperative that you learn to adapt your XSS payloads to the target.

Now if you’re ready to get started, let’s get into it.

My Setup

My setup for this challenge includes:

  1. Kali linux testing vm
  2. burpsuite
  3. browser - Firefox ESR - Proxied through burpsuite
  4. Web for pentester 1 installed in a vm

Challenge 1

Challenge source code for reference:

<?php require_once '../header.php'; ?>
 echo $_GET["name"];

<?php require_once '../footer.php'; ?>

Challenge 1 starts off with a blank web page and the text “hello hacker”.

The webpage itself is fairly bland. Turning to the URL, we see that the website runs on PHP, and this page takes a URL parameter called “name”, which is currently set to “hacker”

By changing this URL parameter to “pentester”, we can see that the web page now shows “hello pentester”.

By doing this, it is now clear that the value in the URL is being reflected in the page body.

I wonder if this will accept angle brackets? :thonk:

Passing the name parameter the following payload pentester<123> shows that the webpage is reflecting the angle brackets.

If we look at the response in burpsuite, it is clear that these angle brackets are not being properly encoded, and are instead being treated as part of the HTML code!

note that burp is syntax highlighting the <123> portion, this indicates that burp is recognising it as a potential HTML tag

Now for the payload!

Now that we know that it accepts and renders angle brackets, we can try a <script> payload.

Here, i am using <script>alert('xss')</script> as my attacking payload


Congratulations on completing challenge 1. Let’s move on to the next.

Challenge 2

Challenge source code for reference:

<?php require_once '../header.php'; ?>
 $name =  $_GET["name"];
 $name = preg_replace("/<script>/","", $name);
 $name = preg_replace("/<\/script>/","", $name);
echo $name;
<?php require_once '../footer.php'; ?>

At first sight, challenge 2 looks identical to challenge 1… did they make a mistake?

Let’s find out.

Challenge 2 starts the same way, with a webpage containing “hello hacker”, and a URL with the “name=hacker” parameter. It even reflects our input the same as the last one did!

Since this is all the same as last time, surely the same payload will work again …right?

But…. But… Where is the XSS???

This is the first stage where we encounter input validation and XSS filtering. After submitting this payload, we can see that the web page rendered “hello alert(‘xss’)”, but doesnt show the script tags we included. From this, we can see that it is only filtering out the script tags.

Let’s test this theory, by injecting something other than script tags, that will show us if the HTML is rendering our code:

By using a <h2> heading tag, we have confirmed that there is still HTML injection. Which means that XSS is still possible, we just need to amend our payload!

Unfortunately for developers, filtering user input is difficult, because there is alot of cases to cover. Since this developer is still allowing HTML code to be rendered, maybe they just did a check for the string “script”.

If we CaMeL CaSe the payload, it wont match the string comparison for the word “script”, so now we can use <ScRiPt>alert('xss')</ScRiPT>

It Works! XSS Achieved!

Now on to challenge 3.

Challenge 3

Challenge source code for reference:

<?php require_once '../header.php'; ?>
 $name =  $_GET["name"];
 $name = preg_replace("/<script>/i","", $name);
 $name = preg_replace("/<\/script>/i","", $name);
echo $name;
<?php require_once '../footer.php'; ?>

Okay, so now we know that this developer is actively trying to keep us from popping that sweet sweet xss.

Let’s have a look at what’s going on this time.

In challenge 3 we have the same page structure. Trying our original two payloads, we can see that <script> and <ScRiPt> get removed, and we just get left with “hello alert(‘xss’)” again.

Why don’t we try something new, let’s try putting the script payload in the middle of a word to see how the server reacts, and if it picks it up.

Okay, what seems to be happening is that the developer is just removing all instances of the “script” tag from all strings. So now how do we pop the xss?

Well… if we look closely at what happened with the payload "hacker<script>mans", we can see that it removed the <script> from the middle, and concatenated the rest of the word back together.

Using this, we can piece together a new payload, so that when the server removes the script tag in the middle, it will concatenate the surrounding text back together to make a new script tag.

I know that sounds confusing, but this is what that means:


What will happen here is the server is going to take the “script” tags out of the middle, but the filter won’t recognise the outer tags as script tags because they arent complete. When the server removes the middle tags, it will push the gaps back together, to make a full script tag, causing the xss to pop.

It’s basically a javascript fusion dance.

The payload works!

On to challenge 4.

Challenge 4

Challenge source code for reference:

<?php require_once '../header.php'; 

if (preg_match('/script/i', $_GET["name"])) {

Hello <?php  echo $_GET["name"]; ?>
<?php require_once '../footer.php'; ?> 

Challenge 4, same concept, you know the drill. The main difference now, is that any time a script tag is detected, the page returns “error”.

Looks like it might finally be time to graduate from Script payloads, into something a little more advanced.

Enter… The image tag.

Image (<img>) tags can also be used to run javascript, as they can accept javascript event handlers within the tag itself. for example <img src=cat.jpg onclick=alert("meow")>

Here, onclick is a javascript event handler that runs javascript code when the image is clicked.

For XSS, the most common payload is <img src=x onerror=alert('xss')> The way this works, is when the webpage tries to load the image, it will fail because image “x” does not exist. when it fails, the onerror event handler will be triggered, causing the javascript to run.

Images are a wonderful thing. They say that one image can speak a thousand words, but honestly i just want to see one …. 'xss'

Also notice the broken image symbol being rendered, this is an indication that the payload is running correctly.

If we also take a look at our burp logs, we can see the webpage trying to request the image at “/xss/x” which doesnt exist. The server then returns a 404 not found error, which triggers the xss payload

Challenge 5

Challenge source code for reference:

<?php require_once '../header.php'; 

if (preg_match('/alert/i', $_GET["name"])) {

Hello <?php  echo $_GET["name"]; ?>
<?php require_once '../footer.php'; ?> 

This challenge is a strange one. In challenge 5, we have the same setup as before, but it seemingly allows script tags this time.

If we pass a script tag into the name parameter, it doesnt throw an error anymore!

If we look in our burp response, we can see that the script tag is still not being encoded and is rendering as HTML:

Another way to check if something is rendering as HTML is to view the page source in the browser’s inspect element feature (F12 or Right Click -> Inpect Element)

In both cases, we can see that the script tag is running as normal. Does that mean that we can use <script>alert(1)</script> again?

Unfortunately not… if we try that payload, the webpage throws an error like before.

Since we have ruled out that it is an issue with script tags, our next course of action is to check the other components of the payload.

With what we have, the only remaining section to test is the actual javascript itself (ie. the alert(1))

Fortunately for us, there are alot more ways that we can see that our payload is running.

Here are some of our options:

  1. Use another form of popup such as:
    • prompt
    • confirm
  2. Print data somewhere (for example to the console):
    • <script>console.log("xss payload")</script>
  3. Make a web request to somewhere (such as
    • <script>document.location("http://url")</script>
  4. Insert an image into the page body with an event handler onmouseover or onload

Okay, let’s try all of these out!

Note that i have changed my vm network types to NatNetwork here, so that my kali vm can reach the internet to test the webhook payload

Other popup types


The “confirm” payload pops up an alert box with both cancel and OK buttons

Payload used: <script>confirm('xss')</script>


The “prompt” payload pops up an alert box with a field for user input

Payload used: <script>prompt('xss')</script>

Printing data

With this payload, there is no alert box, instead our javascript runs by printing a message to the console. This is a much more subtle attack that has good real world applications, as it is cleaner than popping alert boxes all over a website (even though it’s not as fun)

Payload used: <script>console.log('xss')</script>

Sending web requests is a great resource for testing the sending and receiving of HTTP requests. By using XSS to send a request from the user’s browser to your custom url, you can check that the XSS is working properly.

Payload used: <script>document.location='{Webhook_Id}?testParam=xsstest'</script>

Embedding Images

We can embed images into webpages using HTML injection. Often, this is a decent proof of concept for HTML / Javascript injection, however the ability to run javascript via that image tag is really what makes it ‘XSS’. Here, the proof of concept uses an onmouseover event handler, that runs the javascript payload when the user mouses over the image.

Payload used: <img src='' onmouseover=confirm('xss')>

Challenge 6

Challenge source code for reference:

<?php require_once '../header.php'; ?>
 var $a= "<?php  echo $_GET["name"]; ?>";
 <?php require_once '../footer.php'; ?>

Challenge 6 is where things get shaken up a bit, as our destination for our injection point has changed. (i’ll explain that one in a second).

This maintains the same name=hacker URL format as the previous exercises, but this time, when we use a <script> or <img> payload, nothing appears to happen. The page simply says “hello”.

This is where our trusty friend burpsuite (or even inspect element) comes in handy. By looking at the HTML response, we can see that our payload is actually being dropped into a set of script tags already!

So can’t we just drop an “alert(1)” in there and call it a day? Unfortunately no…

If we look closer, our payload is encased by a pair of double quotes (""), meaning that it is being treated as a string, and assigned to the variable “$a”

This means that our destination (the place our payload ends up) is now inside the statement var $a = "{destination}";

What we need to do here is “escape” out of the double quotes, and complete the remainder of the javascript line before we can add our own line of malicious code.

Our first step here is to add in a double quote to break out of the existing ones. THis can simply be done using "alert(1)

Once we have that, we need to complete the line using valid syntax by adding a semi-colon to end the line.

Our payload is now ";alert(1)

If we run this as-is, we can see that our payload fails with this error in the console:

This means that we have not properly completed the line.

If we refer back to the HTML code in burp, we can see that even though we have broken out of the double quotes, a double quote is still being appended to the end of our payload causing a syntax error. In order to fix this, we can comment out the rest of the line using //

Now our payload should work:

Payload used: ";alert('xss')\\

Challenge 7

Challenge source code for reference:

<?php require_once '../header.php'; ?>
 var $a= '<?php  echo htmlentities($_GET["name"]); ?>';
<?php require_once '../footer.php'; ?>

Challenge 7 is identical to challenge 6, except using a single quote to break the string instead of a double quote.

Payload used: ';alert('xss')\\

Challenge 8

Challenge source code for reference:

  require_once '../header.php'; 

  if (isset($_POST["name"])) {
    echo "HELLO ".htmlentities($_POST["name"]);
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST">
  Your name:<input type="text" name="name" />
  <input type="submit" name="submit"/>

  require_once '../footer.php'; 


Challenge 8 is a bit of a step up in terms of difficulty, as it relies on some problem solving skills, and works very differently to previous challenges.

Challenge 8 is a new type of challenge that presents you with a prompt asking for your name, and a button that says submit query, with nothing special in the URL.

If we try all of our usual attacks, we can see that the response is properly encoding our XSS payloads, with &lt; and &gt; (stands for less-than and greater-than, ie. the < and > symbols).

After exhausting all options in the input box, we return to the URL, looking to see if there is something that will reflect our input.

Unfortunately, no such luck, however if we play around with the URL a bit, we can see that adding characters after example8.php results in a 404 not found page:

but if we add a / before our characters, it doesnt matter what we give it, it will load the same page over again.

At this point, we turn to the HTML code, to see if this gives us any clues.

If we play around with various tag closings, we can see that most do nothing, however a combination of " and > (ie, exiting a string and closing a HTML tag), will print the rest of that line of HTML to the screen

In our HTML code, we can see that our input in the URL is being reflected into the form’s “action” URL. Using this, we can see the rest of the HTML line that needs to be completed for our payload to work.

The easiest way to do this, is to simply take the remainder of the line, and put it after the / in the URL:

By doing this, we have successfully completed the line of HTML code, and now have an injection point for our xss payload within the form body.

We can now use a standard XSS payload to complete the challenge.

Payload used: /" method="POST"><img src=x onerror=alert('xss')>

Challenge 9

Challenge 9 is an easy one, as it is essentially the same as previous challenges, however it introduces a new type of XSS known as DOM based XSS.

Unfortuantely this challenge no longer works on most modern browsers, as they all have built in protections against DOM based XSS. I was not able to successfully exploit this issue using the versions of firefox and chromium i had installed, however if you use something less secure like Internet Explorer, this exploit will run successfully.

When run in firefox, the payload that is supposed to work (replacing the text after the # with a standard script alert 1) reflects the payload to the page, but does not execute.

You’re Done

Congratulations! You’ve made it all the way through the Pentesterlabs XSS challenges in Web for Pentester 1!

Now time to move on to the next lot of challenges.

Stay tuned for up coming posts and walkthroughs for the other challenges in Web for Pentester 1.

Happy Hacking Everyone