javascriptjqueryhtmlspring-mvcvelocity

How to make a live HTML preview textarea safe against HTML/Script Injection


I'm turning here as a last resort. I've scoured google and I'm having troubles coming to a solution. I have a form with a textarea element that allows you to type html in the area and it will render the HTML markup live as you type if you have the preview mode active. Not too different from the way StackOverflow shows the preview below a new post.

However, I have recently discovered that my functionality has a vulnerability. All I got to do is type something like:

</textarea>
     <script>alert("Hello World!");</script>
<textarea style="display: none;"> 

And not only does this run from within the textarea live, if you save the form and reload said data on a different page this code still executes within the textarea on said different page but unbeknownst to the user; to them all the see is a textarea (if there is no alert obviously).

I found this post; Live preview of textarea input with javascript html, and attempted to refactor my JS to the accepted answer there, because I noticed I couldn't write a script tag in the JSFiddle example, though maybe that's some JSFiddle blocking that behaviour, but I couldn't get it working within my JS file.

These few lines is what I use to live render HTML markup:

$(".main").on("keyup", "#actualTextArea", function () {
    $('#previewTextArea').html($('#actualTextArea').val());
});

$(".main").on("keydown", "#actualTextArea", function () {
    $('#previewTextArea').html($('#actualTextArea').val());
});

Is there a way this can be refactored so it's safe? My only idea at the moment is to wipe the live preview and use a toggle on/off and encode it, but I really think this is a cool feature and would like to keep it live instead of toggle. Is there a way to "live encode" it or escape certain tags or something?


Solution

  • Edit: Actually, I think this solution is a little cleaner, and makes the below code unnecessary. In the velocity page all that is needed is to take advantage of the Spring framework. So I replace the textarea with this like so:

    #springBindEscaped("myJavaObj.textAreaText" true)
    <textarea id="actualTextArea" name="${status.expression}" class="myClass" rows="10" cols="120">$!status.value</textarea>
    

    This paired with some backend Java validation and it ends up being a much cleaner solution.

    But if you want a non-spring/ velocity solution, then this below works just fine


    I cobbled together a quick fix as my main purpose is to eliminate the ability for others to execute scripts easily. It's not ideal, and I"m not claiming it to be the best answer, so if someone finds a better solution, please do share. I created a "sanitize" function like so:

    function sanitize(text){
        var sanitized = text.replace("<script>", "");
        sanitized = sanitized.replace("</script>", "");
        return sanitized;
    }
    

    Then the previous two event handlers now look like:

    $(".main").on("keyup", "#actualTextArea", function () {
        var textAreaMarkup = $('#actualTextArea').val();
        var sanitizedMarkup = sanitize(textAreaMarkup );
        $('#actualTextArea').val(sanitizedMarkup);
        $('#previewTextArea').html(sanitizedMarkup);
    });
    
     // This one can remain unchanged and infact needs to be
     // If it's the same as above it will wipe the text area
     //  on a highlight-backspace
    $(".main").on("keydown", "#actualTextArea", function () {
        $('#previewTextArea').html($('#actualTextArea').val());
    });
    

    Along with Java side sanitation to prevent anything harmful being stored in the DB, this serves my purpose, but I'm very open to a better solution if it exists.