In creating a simple demonstration of XSS I'm failing to see the result of the injected code, and I'm wondering what simple thing I've overlooked.
This is the code that handles the AJAX request after the user presses enter in the specified input element:
document.getElementById('xssVulnerable')
.addEventListener( 'keyup', function(event) {
if (event.key === "Enter") {
let msg = document.getElementById('xssVulnerable').value;
fetch('xssVulnerable.php', {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify( {'userInput':msg} )
})
.then( response => response.text() )
.then( data => {
document.getElementById('xssEcho').innerHTML = data;
});
}
});
I originally had this using GET but had suspected that the url-encoding of the parameters was making some subtle difference. To attempt to avoid that I've used POST body instead. Removing the Content-type header makes no difference to the results.
The HTML excerpt is trivial, and does show the echoed output correctly, unless I provide a string with a script tag. I.e. if I input <strong>Hi</strong>
I will see a bolded Hi inserted. If I use <script>console.log('hi');</script>
I see no output, including nothing in the console but I can see the injected tag through the element browser.
<input type="text" id="xssVulnerable">
<p id='xssEcho'></p>
The server side response is similarly trivial, by intent:
<?php
$body = json_decode(file_get_contents('php://input'), true);
echo $body['userInput'];
?>
Double checking the documentation I found no evidence that the .text() method performs any sanitization, and neither is there any automatic sanitization by the reading of the .value property nor by the use of .innerHTML The rendering of the strong tag exemplifies that the code is being re-rendered on receipt, which is as expected. Neither did I find any reason why a script tag could not be nested inside a paragraph tag.
I accept that this is not a traditional XSS attack and that a more relevant example would be if the string were read from a file or database and output without sanitization, but the goal is to demonstrate the problem in a simple, introductory manner.
I tested this in current versions of Chrome and Edge, though I'm also unaware of any behavior these browsers might have to suppress a dynamically injected script.
What nuance have I missed?