So, here is a piece of code I came across.


    $name = $_GET['name'];

    if (!empty($name)) {
            $name = str_replace("<", "&lt;", $name);    //line 1
            $name = str_replace(">", "&gt;", $name);    //line 2
            $name = urldecode($name);                   //line 3

            echo $name;



This code I found in one of the ethical hacking practicing frameworks named bWAPP at security level 1. The potential solution to bypass this technique is to URL encode the XSS string twice.

Let's assume my XSS string is

<script>alert('XSS');</script>       //checkpoint alpha

Then, encoding it once should give me

%3Cscript%3Ealert%28%27XSS%27%29%3C%2fscript%3E  //checkpoint beta

If I trace it down, on line 1 and 2 nothing happens. But on line 3 it gets decoded. And so, the resultant string I get back becomes checkpoint alpha. So, when I echo it, I should get an alert poped up.

Nothing like this happens and I just get the checkpoint alpha displayed in clear text.

When, I double encode it,(which is the potential solution to this problem), I get,


Now, this statement again passes through line 1 and 2, and on stumbling upon line 3, it gets decoded to checkpoint beta and gets echoed.

When it gets echoed, I should get checkpoint beta in clear text but Suddenly the browser pops up the alert box with XSS working perfectly.

I am new in this field and currently trying to clear my basic concepts related to web development. Please pardon my incorrect statement formation. Thank you.

2 Answers 2


When PHP retrieves the GET parameter with $name = $_GET['name'], it performs an implicit URL decoding. PHP doesn't require you to decode an URL parameter yourself.

These both result in the exact same value for $name:


So even if < looks like %3c in your URL, it will be stored as a literal < in $name. In consequence, the str_replace() part correctly recognizes < and > and filters these characters out, thereby preventing XSS.

The vulnerable part is the explicit urldecode() at the end.

If you provide the URL http://yourapp/?name=%253c, then variable $name will literally contain%3c (that's three characters) and the str_replace() functions will ignore it because neither of %, 3, c looks like an angle bracket. Then, in the end, urldecode() unnecessarily performs another decoding and turns the sequence %3c into a literal < after it has already passed the replace filter.

If it's still a bit unclear to you, replace $name = $_GET['name'] with this:

$name = urldecode('%253Cscript%253Ealert%25281%2529%253C%252fscript%253E')

You will see that this has the same effect as providing the string as the URL parameter and it shows that two urldecodes are redundant.

  • So, in short, php recognised %3C and < same. And it stored it as < ???
    – Panda
    Mar 31, 2017 at 12:33
  • @M.S.P Yes, but only if it comes from a URL parameter, as is the case with $_GET['name'].
    – Arminius
    Mar 31, 2017 at 12:37
  • That's what I tried to say, but you have put it more clearly than I did! :D
    – Wealot
    Mar 31, 2017 at 12:48
  • Well, that clears both the contradictions! Thank you @Arminius
    – Panda
    Apr 1, 2017 at 3:12

Ok so I am not a php developer (or a developer at all), but I tested this.

tldr: $_GET['name'] urldecodes the parameter name

I used your code but put an:

echo $name;

At every line.

I tested the following:

  • <test>, the $name parameter starts as and ends with escaped characters
  • %3Ctest%3E, the $name parameter starts as after the $_GET, so immediatly the parameter is already urldecoded once
  • %253Ctest%253E, the $name parameter starts as %3Ctest%3E after the $_GET so is decoded once. Then the validation fails and the second decode decodes the parameter to

To answer your contradictions:

Contradiction 1 look at what you actually get back using firebug or a proxy or http requester. Your browser says it shows you but what it actually got was < test > and interpreted that to . This can be confusing but the browser got your escaped scentence back.

Contradiction 2 Beta is encoded once, gets decoded once at the start request ($_GET), then the < and > get changed to < and >.

  • I sincerely appreciate your answer, but, I am not able to understand what you are trying to convey. Kindly, make it clear.
    – Panda
    Mar 31, 2017 at 10:36
  • @M.S.P I changed it a bit as I made a mistake in C2 and stack doesn't like html to much :D. What part is unclear, so I can explain better
    – Wealot
    Mar 31, 2017 at 10:43

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .