These automated checks work by putting a script tag containing a random token into every field. For example, /someRequest?foo=x&bar=y
might become:
GET /someRequest?foo=<script>alert("147230578")</script>&bar=<script>alert("561972456")</script>
It maintains a map of fields and the random values it chose (in this case foo=147230578, bar=561972456) in order to trace which parameter is injectable.
It then checks to see if <script>alert("147230578")</script>
or <script>alert("561972456")</script>
appear in the page without encoding. If the string is found, it knows XSS is very likely possible. If not, it either didn't appear in the page at all or it got HTML encoded.
In your case, it found that if it did get those strings reflected back, and identified which field was injectable. The reason it didn't execute is that the script tag got injected into a HTML attribute.
So, looking at your injection:
<input type="text" name="cn" size="20" maxlength="20" value="<script>alert(1);</script>">
What happens here is the HTML is malformed, so the browser's renderer just tries to make the best of it, ignores both tags, and closes everything after the </script>
. It then sees ">
which it assumes is content and renders it on the page.
If we instead inject "><script>alert(1)</script>
into the parameter, we get this:
<input type="text" name="cn" size="20" maxlength="20" value=""><script>alert(1)</script>">
This time the input tag is closed, which causes the script
tag to get executed. Successful XSS! Again, the ">
at the end just gets treated as content.
For a bonus round, what might we do if the vulnerable parameter doesn't allow the use of triangle brackets? Since we're already in an attribute, we can manipulate the input
element we're injecting into in order to execute script:
" onmouseenter="alert(1)" x="
This creates the following output:
<input type="text" name="cn" size="20" maxlength="20" value="" onmouseenter="alert(1)" x="">
Note that the x
attribute doesn't do anything, it's just there to let us maintain valid HTML; unknown attributes are just ignored.
In this case, when the user's mouse enters the input element, it executes the JavaScript and we get XSS again.
But requiring user interaction sucks! We could use onload
but this is often finnicky. Instead we can utilise some CSS tricks to force the user interaction:
" onmouseenter="alert(1)" style="display:block;position:absolute;top:0;left:0;width:50000px;height:50000px;z-index:999999;
This then becomes:
<input type="text" name="cn" size="20" maxlength="20" value="" onmouseenter="alert(1)" style="display:block;position:absolute;top:0;left:0;width:50000px;height:50000px;z-index:999999;">
The style here looks like a mouthful but it's quite simple:
- Make the element display as a block element (so we can size it arbitrarily)
- Make the element's position absolute within the window (so we can set its position to anywhere)
- Move the element to (0,0)
- Make the element completely fill the page (so the user's mouse has to enter it!)
- Make the element appear on top of everything else.
Hopefully this explains how this all works and how you can go from the example given by ZAP to a working XSS exploit.