Preventing HTML and Script injections in Javascript

165.4k views Asked by At

Assume I have a page with an input box. The user types something into the input box and hits a button. The button triggers a function that picks up the value typed into the text box and outputs it onto the page beneath the text box for whatever reason.

Now this has been disturbingly difficult to find a definitive answer on or I wouldn't be asking but how would you go about outputting this string:

<script>alert("hello")</script> <h1> Hello World </h1>

So that neither the script is executed nor the HTML element is displayed?

What I'm really asking here is if there is a standard method of avoiding both HTML and Script injection in Javascript. Everyone seems to have a different way of doing it (I'm using jQuery so I know I can simply output the string to the text element rather than the html element for instance, that's not the point though).

8

There are 8 answers

8
TastySpaceApple On BEST ANSWER

You can encode the < and > to their HTML equivelant.

html = html.replace(/</g, "&lt;").replace(/>/g, "&gt;");

How to display HTML tags as plain text

8
BiAiB On
myDiv.textContent = arbitraryHtmlString 

as @Dan pointed out, do not use innerHTML, even in nodes you don't append to the document because deffered callbacks and scripts are always executed. You can check this https://gomakethings.com/preventing-cross-site-scripting-attacks-when-using-innerhtml-in-vanilla-javascript/ for more info.

0
Max On

My solution using typescript + decorators + regex

const removeTag = new RegExp("(<[a-zA-Z0-9]+>)|(</[a-zA-Z0-9]+>)", "g");
return value.replace(removeTag, "");

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
function filter(target) {
    return class extends target {
        constructor(...args) {
            super(...args);
        }
        setState(opts) {
            const state = {
                username: this.filter(opts.username),
                password: this.filter(opts.password),
            };
            super.setState(state);
        }
        filter(value) {
            const removeTag = new RegExp("(<[a-zA-Z0-9]+>)|(</[a-zA-Z0-9]+>)", "g");
            return value.replace(removeTag, "");
        }
    };
}
let Form = class Form {
    constructor() {
        this.state = {
            username: "",
            password: "",
        };
    }
    setState(opts) {
        this.state = {
            ...this.state,
            ...opts,
        };
    }
    getState() {
        return this.state;
    }
};
Form = __decorate([
    filter,
    __metadata("design:paramtypes", [])
], Form);
function getElement(key) {
    return document.getElementById(key);
}
const button = getElement("btn");
const username = getElement("username");
const password = getElement("password");
const usernameOutput = getElement("username-output");
const passwordOutput = getElement("password-output");
function handleClick() {
    const form = new Form();
    form.setState({ username: username.value, password: password.value });
    usernameOutput.innerHTML = `Username: ${form.getState().username}`;
    passwordOutput.innerHTML = `Password: ${form.getState().password}`;
}
button.onclick = handleClick;
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <style>
      :root {
        --bg: #1d1907;
        --foreground: #e3e0cd;
        --primary: #cfb53b;
        --black: #333;
        --white: #fafafa;
      }

      @keyframes borderColor {
        from {
          border-bottom: 1px solid var(--foreground);
        }

        to {
          border-bottom: 1px solid var(--primary);
        }
      }

      * {
        outline: none;
        border: none;
      }

      body {
        padding: 0.5rem;
        font-family: "Fira Code";
        background-color: var(--bg);
        color: var(--foreground);
      }

      input {
        border-bottom: 1px solid var(--foreground);
        background-color: var(--black);
        color: var(--foreground);
        padding: 0.5rem;
      }

      input:focus {
        animation-name: borderColor;
        animation-duration: 3s;
        animation-fill-mode: forwards;
      }

      button {
        padding: 0.5rem;
        border-radius: 3px;
        border: 1px solid var(--primary);
        background-color: var(--primary);
        color: var(--white);
      }

      button:hover,
      button:active {
        background-color: var(--white);
        color: var(--primary);
      }

      .form {
        margin-bottom: 2rem;
      }
    </style>
    <title>Decorator</title>
  </head>
  <body>
    <h1>Prevent Injection</h1>
    <div class="form">
      <div class="form-group">
        <label for="username">Username</label>
        <input type="text" id="username" placeholder="Type your username" />
      </div>
      <div class="form-group">
        <label for="password">Password</label>
        <input type="password" id="password" placeholder="Type your password" />
      </div>
      <div class="form-group">
        <button id="btn">Enviar</button>
      </div>
    </div>
    <div class="form-result">
      <p id="username-output">Username:</p>
      <p id="password-output">Password:</p>
    </div>
    <script src="/dist/pratica1.js"></script>
  </body>
</html>

Typescript Code bellow:

    type State = {
  username: string;
  password: string;
};

function filter<T extends new (...args: any[]) => any>(target: T): T {
  return class extends target {
    constructor(...args: any[]) {
      super(...args);
    }

    setState(opts: State) {
      const state = {
        username: this.filter(opts.username),
        password: this.filter(opts.password),
      };
      super.setState(state);
    }

    filter(value: string) {
      const removeTag = new RegExp("(<[a-zA-Z0-9]+>)|(</[a-zA-Z0-9]+>)", "g");
      return value.replace(removeTag, "");
    }
  };
}

@filter
class Form {
  private state: State;

  constructor() {
    this.state = {
      username: "",
      password: "",
    };
  }

  setState(opts: State) {
    this.state = {
      ...this.state,
      ...opts,
    };
  }

  getState() {
    return this.state;
  }
}

function getElement(key: string): HTMLElement | null {
  return document.getElementById(key);
}

const button = getElement("btn") as HTMLButtonElement;
const username = getElement("username") as HTMLInputElement;
const password = getElement("password") as HTMLInputElement;
const usernameOutput = getElement("username-output") as HTMLParagraphElement;
const passwordOutput = getElement("password-output") as HTMLParagraphElement;

function handleClick() {
  const form = new Form();
  form.setState({ username: username.value, password: password.value });
  usernameOutput.innerHTML = `Username: ${form.getState().username}`;
  passwordOutput.innerHTML = `Password: ${form.getState().password}`;
}

button.onclick = handleClick;
0
Billy Bob On

Try this method to convert a 'string that could potentially contain HTML code' to 'text format':

$msg = "<div></div>";
$safe_msg = htmlspecialchars($msg, ENT_QUOTES);
echo $safe_msg;
0
Karthik SK On

Use this,

function restrict(elem){
  var tf = _(elem);
  var rx = new RegExp;
  if(elem == "email"){
       rx = /[ '"]/gi;
  }else if(elem == "search" || elem == "comment"){
    rx = /[^a-z 0-9.,?]/gi;
  }else{
      rx =  /[^a-z0-9]/gi;
  }
  tf.value = tf.value.replace(rx , "" );
}

On the backend, for java , Try using StringUtils class or a custom script.

public static String HTMLEncode(String aTagFragment) {
        final StringBuffer result = new StringBuffer();
        final StringCharacterIterator iterator = new
                StringCharacterIterator(aTagFragment);
        char character = iterator.current();
        while (character != StringCharacterIterator.DONE )
        {
            if (character == '<')
                result.append("&lt;");
            else if (character == '>')
                result.append("&gt;");
            else if (character == '\"')
                result.append("&quot;");
            else if (character == '\'')
                result.append("&#039;");
            else if (character == '\\')
                result.append("&#092;");
            else if (character == '&')
                result.append("&amp;");
            else {
            //the char is not a special one
            //add it to the result as is
                result.append(character);
            }
            character = iterator.next();
        }
        return result.toString();
    }
0
Timothy Kanski On

A one-liner:

var encodedMsg = $('<div />').text(message).html();

See it work:

https://jsfiddle.net/TimothyKanski/wnt8o12j/

0
hestellezg On

From here

var string="<script>...</script>";
string=encodeURIComponent(string); // %3Cscript%3E...%3C/script%3
1
Tadas Janca On

I use this function htmlentities($string):

$msg = "<script>alert("hello")</script> <h1> Hello World </h1>"
$msg = htmlentities($msg);
echo $msg;