Search code examples
securityencodingfetch-apirazor-pagessanitization

When sanitize/encode while implementing tags system like on SO


In my development I have a tag system that closely matches the one SO has. And it also allows non-Latin characters.

  • User can enter new tag and it is saved to the DB.
  • Existing tags are shown to the user when they type tag prefix. Fetch API is used for this.

I'm using Razor pages. At what point and how should I sanitize/encode strings in this flow?

Here is an example of my fetch request:

try {
    const response = await fetch("api/tags?" + new URLSearchParams({ prefix: curPrefix, count: 12 }));
    if (!response.ok) throw new Error("Network response was not OK");
    const jsonData = await response.json();
    if (jsonData.prefix === getPrefix()) {
        var newTags = jsonData.tags.filter(tag => !selectedTags.find(x => x.name == tag.name));
        setSuggestedTags(newTags);
    }
} catch (error) {
    console.error("There has been a problem with your fetch operation:", error);
}

Solution

  • Basically whenever you process Data coming from the client, you have to be careful, including when you send user-generated data back to the browser. In your example, some crucial bits are missing, but here is a full example of how it might be vulnerable to XSS.

    The Page Model’s action method that returns some tags. Some of them contain HTML and because it’s a JsonResult the HTML makes it through unescaped:

    public IActionResult OnGetTags()
        => new JsonResult((List<string>)[ "Carrot", "<strong>Spinach</strong>", """<img src="nothing" onerror="alert('Mexican Jumping Bean');"/>"""]);
    

    The Razor Page, in which the tags are fetched and inserted into the page using innerHTML, which allows the browser to actually parse the HTML code:

    <button onclick="fetchTags()">Suggest some Tags pls</button>
    <ul id="tags"></ul>
    
    <script>
        async function fetchTags() {
            try {
                const response = await fetch('@Url.Page("", "Tags")');
    
                if (!response.ok)
                    throw new Error("Network response was not OK");
    
                setSuggestedTags(await response.json());
            }
            catch (error) {
                console.error("There has been a problem with your fetch operation:", error);
            }
        }
    
        function setSuggestedTags(newTags) {
            newTags.forEach(t => {
                const li = document.createElement('li');
                li.innerHTML = t; //this pwns your visitor
                tags.append(li);
            });
        }
    </script>
    

    This would be fine if you used innerText instead of innerHTML, then the HTML tags would just be displayed on the page naked, looking just as ugly as they were input in the first place.

    Likewise if you used the usual Razor syntax to put tags on the page, it would automatically escape them:

    <ul>
    @foreach (var tag in Model.Tags) {
        <li>@tag</li> @* this is fine *@
    }
    </ul>
    

    There are of course other places you may want to prevent this. For example you could make sure such tags are rejected when a user tries to save them in the first place. Importantly this would have to happen server-side, perhaps by carefully stripping all kinds of special characters or rejecting any tags containing them. What I wouldn’t do is escape incoming data and store it that way, because it will lead to double-escaping and different contexts reqiure different escapings anyway. Plus it’s going to be a headache to search the database later, when every non-english-letter character might potentially be &Aacute; or something.

    When storing tags in your database, make sure to use parameterisation to prevent SQL injections. Some tools mostly take care of this on their own, e.g. with Entity Framework you usually don’t work directly with SQL.