Home > Software engineering >  How to stop HTML injection when using template literals
How to stop HTML injection when using template literals

Time:06-20

I have a simple CRUD webapp that's built upon populating template literals based on user input. The issue is that if the user inputs something like <b>Hey</b> then it's rendered as bold on the page:

const element = (name) => {
  const elementHTML= 
            `<div>
              <ul>
                <li>${name}</li>
              </ul>
            </div>`
  const element = document.createElement('div')
  element.innerHTML = elementHTML
  return element.firstChild
}

const userInput = '<b>foo</b>'
document.body.appendChild(element(userInput)) // 

How can I stop this interaction? My guess is to use some invisible character around the ${embedded expression} to stop what's inside from being interpreted as HTML, so something like:

const elementhtml = `<div>|${name}|</div>` Where | would be the invisible character.

Is there a best practice to draw the expressions as text instead of HTML? I understand that this is a product of using innerHTML to create the element, but isn't that necessary to make use of template literals?

CodePudding user response:

Don't interpolate input from the user into HTML markup directly. Either escape the entities so they're interpreted as plain text instead of HTML tags, or assign to the .textContent of the element instead.

const element = (name) => {
  const elementHTML= 
            `<div>
              <ul>
                <li></li>
              </ul>
            </div>`
  const element = document.createElement('div')
  element.innerHTML = elementHTML
  element.querySelector('li').textContent = name;
  return element.firstChild
}

const userInput = '<b>foo</b>'
document.body.appendChild(element(userInput))

CodePudding user response:

You can .replace() < and > with unicode HTML entities &lt; and &gt;, respectively.

const element = (name) => {
  const elementHTML =
    `<div>
              <ul>
                <li>${name}</li>
              </ul>
            </div>`;
  const element = document.createElement('div');
  element.innerHTML = elementHTML;
  return element.firstChild;
}

const userInput = (data) => data.replace(/</g, '&lt;').replace(/>/g, '&gt;');
document.body.appendChild(element(userInput(`<b>foo</b>`)));

CodePudding user response:

Do the same you did but don't mention it directly in the li tag. Do it this way: document.getElementById("li").innerText=name

CodePudding user response:

I see you are getting name from the user. You can validate the name to be a real name first, so you don't need to think of the HTML. This Regular Expression can be used to validate a name.

/^[a-z ] $/i characters from a-z case insensitive, and spaces are allowed. Meaning the user cannot put html tags in there.

Even if you have validated user input, you should still not use innerHTML as it will take the name or text as html. You should instead use innerText or textContent to prevent that.

You can also you replace() and replaceAll() methods to remove parts of the text, this could be the html or any other tags inside the text

  • Related