防止 Javascript 中的 HTML 和脚本注入

Posted

技术标签:

【中文标题】防止 Javascript 中的 HTML 和脚本注入【英文标题】:Preventing HTML and Script injections in Javascript 【发布时间】:2014-01-18 06:42:46 【问题描述】:

假设我有一个带有输入框的页面。用户在输入框中输入内容并点击按钮。该按钮触发一个函数,该函数获取输入到文本框中的值并将其输出到文本框下方的页面上,无论出于何种原因。

现在很难找到明确的答案,否则我不会问,但你会如何输出这个字符串:

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

这样既不执行脚本也不显示 html 元素?

我在这里真正要问的是,是否有一种标准方法可以避免 Javascript 中的 HTML 和脚本注入。每个人似乎都有不同的方法(我正在使用 jQuery,所以我知道我可以简单地将字符串输出到 text 元素而不是 html 元素,但这不是重点)。

【问题讨论】:

How to prevent javascript injection attacks within user-generated HTML的可能重复 您要阻止 所有 HTML 注入,还是仅阻止 不安全 注入? 另外,如果用例真的如你所说,而且这只是客户端 JavaScript,你真的不需要防止“注入”。只有当输入没有显示给其他人时,用户才能攻击自己(如果它显示给其他用户,你会在服务器端清理输入)。 所有,这更多是关于防止此类事情发生的概念和方法的解释。什么是不安全的 html 注入而不是安全的? &lt;h1&gt;Hello World&lt;/h1&gt; 是一种安全注入,因为它不会给用户带来安全风险。如果您想防止 HTML/JS 注入,您可以删除编码 HTML 标记。就这么简单。 【参考方案1】:

您可以将 &lt;&gt; 编码为它们的 HTML 等效项。

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

How to display HTML tags as plain text

【讨论】:

王牌!正是我想要的,如果不清楚,请道歉。这是一个正则表达式呢?除了对其他代码稍作修改外,我没有特别使用它们。 这是一个非常简单的正则表达式——表达式基本上是&lt;。在 javascript 中,正则表达式在斜杠 (//) 内定义,g 表示它应该搜索所有出现的事件(g = 全局)。 @dlampard 请注意,这正是 .text() 所做的,所以如果你有 jQuery,就不需要自定义正则表达式。 好的,我试图避免使用 jQuery,因为它在我试图理解概念和基础知识时掩盖了实际发生的事情。不过我确实喜欢它。 如果您只想允许一些标签,例如粗体或斜体,您知道该怎么做吗?【参考方案2】:
myDiv.textContent = arbitraryHtmlString 

正如@Dan 指出的那样,不要使用innerHTML,即使在你不附加到文档的节点中,因为延迟的回调和脚本总是被执行。您可以查看此https://gomakethings.com/preventing-cross-site-scripting-attacks-when-using-innerhtml-in-vanilla-javascript/ 了解更多信息。

【讨论】:

请注意,该问题不包含jquery 标记,因此您应该尝试用纯javascript 回答。 如果 OP 要求,我将详细说明并提供无 jquery 的答案。这里的原理是一样的。 我不完全确定为什么,但如果 html 的值为 0,这似乎返回一个空字符串。 你必须给它一个字符串,所以"0" 可以工作。您可以检查源 github.com/jquery/jquery/blob/2.2-stable/src/core/parseHTML.js 并查看它丢弃非字符串输入 问题:您确定temp.innerHTML = arbitraryHtmlString; 不会开始预加载图像并运行任何在图像标签等中定义的onload 处理程序吗?【参考方案3】:

单行:

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

看看效果:

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

【讨论】:

【参考方案4】:

我使用这个函数 htmlentities($string):

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

【讨论】:

【参考方案5】:

From here

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

【讨论】:

【参考方案6】:

我使用打字稿+装饰器+正则表达式的解决方案

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>

打字稿代码如下:

    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;

【讨论】:

【参考方案7】:

试试这个方法将“可能包含 html 代码的字符串”转换为“文本格式”:

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

希望这会有所帮助!

【讨论】:

【参考方案8】:

用这个,

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 , "" );

在后端,对于 java ,尝试使用 StringUtils 类或自定义脚本。

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();
    

【讨论】:

以上是关于防止 Javascript 中的 HTML 和脚本注入的主要内容,如果未能解决你的问题,请参考以下文章

如何防止用户生成的 HTML 中的 Javascript 注入攻击

防止在 Asp.Net Core ViewComponents 中加载多个 JavaScript 脚本

[JavaWeb]_[初级]_[对Html特殊符号进行转义防止XSS攻击和反转义]

[JavaWeb]_[初级]_[对Html特殊符号进行转义防止XSS攻击和反转义]

如何防止部分视图中的脚本多次加载并在同一页面中多次使用部分时导致错误

防止前端脚本JavaScript注入