在 Javascript 中将用户输入添加到 DOM 之前对其进行清理

IT技术 javascript xss escaping
2021-01-31 19:51:16

我正在为空闲时间正在处理的聊天应用程序编写 JS,我需要根据用户提交的数据更改 HTML 标识符。这通常在概念上很不稳定,我什至不会尝试,但我认为这次我没有太多选择。然后我需要做的是转义 HTML id 以确保它不会允许 XSS 或破坏 HTML。

这是代码:

var user_id = escape(id)
var txt = '<div class="chut">'+
            '<div class="log" id="chut_'+user_id+'"></div>'+
            '<textarea id="chut_'+user_id+'_msg"></textarea>'+
            '<label for="chut_'+user_id+'_to">To:</label>'+
            '<input type="text" id="chut_'+user_id+'_to" value='+user_id+' readonly="readonly" />'+
            '<input type="submit" id="chut_'+user_id+'_send" value="Message"/>'+
          '</div>';

id避免上述任何类型的问题的最佳逃避方法是什么?正如您所看到的,现在我正在使用内置escape()函数,但我不确定与其他替代方案相比它应该有多好。我主要习惯于在输入进入文本节点之前对其进行消毒,而不是 id 本身。

6个回答

永远不要使用escape(). 这与 HTML 编码无关。它更像是 URL 编码,但它甚至不正确。这是一种奇怪的非标准编码,仅在 JavaScript 中可用。

如果你想要一个 HTML 编码器,你必须自己编写它,因为 JavaScript 没有给你。例如:

function encodeHTML(s) {
    return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
}

然而,虽然这足以将您user_id放在像 一样的地方input value,但这还不够,id因为 ID 只能使用有限的字符选择。(而且%不在其中,所以escape()甚至encodeURIComponent()都不好。)

您可以发明自己的编码方案来将任何字符放入 ID,例如:

function encodeID(s) {
    if (s==='') return '_';
    return s.replace(/[^a-zA-Z0-9.-]/g, function(match) {
        return '_'+match[0].charCodeAt(0).toString(16)+'_';
    });
}

但是如果同样的user_id情况发生两次,你仍然会遇到问题老实说,乱扔 HTML 字符串的整个过程通常是个坏主意。改用 DOM 方法,并保留对每个元素的 JavaScript 引用,这样您就不必继续调用getElementById,也不必担心如何将任意字符串插入到 ID 中。

例如。:

function addChut(user_id) {
    var log= document.createElement('div');
    log.className= 'log';
    var textarea= document.createElement('textarea');
    var input= document.createElement('input');
    input.value= user_id;
    input.readonly= True;
    var button= document.createElement('input');
    button.type= 'button';
    button.value= 'Message';

    var chut= document.createElement('div');
    chut.className= 'chut';
    chut.appendChild(log);
    chut.appendChild(textarea);
    chut.appendChild(input);
    chut.appendChild(button);
    document.getElementById('chuts').appendChild(chut);

    button.onclick= function() {
        alert('Send '+textarea.value+' to '+user_id);
    };

    return chut;
}

您还可以使用便利函数或 JS 框架来减少那里的 create-set-appends 调用的冗长。

预计到达时间:

我目前使用 jQuery 作为框架

好的,然后考虑 jQuery 1.4 创建快捷方式,例如:

var log= $('<div>', {className: 'log'});
var input= $('<input>', {readOnly: true, val: user_id});
...

我现在的问题是我使用 JSONP 向页面添加元素和事件,因此在显示消息之前我无法知道这些元素是否已经存在。

您可以user_id在 JavaScript 中保持对元素节点(或包装器对象)的查找,以节省将该信息放在 DOM 本身中,其中可以进入的字符id受到限制。

var chut_lookup= {};
...

function getChut(user_id) {
    var key= '_map_'+user_id;
    if (key in chut_lookup)
        return chut_lookup[key];
    return chut_lookup[key]= addChut(user_id);
}

_map_前缀是因为 JavaScript 对象不能完全用作任意字符串的映射。空字符串和 IE 中的一些Object成员名称会混淆它。)

我目前使用 jQuery 作为框架,因此任何与此相关的想法都会有所帮助。我现在的问题是我使用 JSONP 向页面添加元素和事件,因此在显示消息之前我无法知道这些元素是否已经存在。这让我觉得我不得不使用蹩脚的方法,我必须找到要选择的元素或添加它们(如果它们已经不存在)。因此,我认为您的最后一个建议行不通,但我可能是错的。受限字符集让我觉得 ID 的 MD5 可能就是我所能做的。
2021-03-27 19:51:16
虽然我可以添加一个关于用户名接受字符的假设,但使用正则表达式并完成它。
2021-04-02 19:51:16

你可以使用这个:

function sanitize(string) {
  const map = {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#x27;',
      "/": '&#x2F;',
  };
  const reg = /[&<>"'/]/ig;
  return string.replace(reg, (match)=>(map[match]));
}

另请参阅 OWASP XSS 预防备忘单

反引号呢:`?
2021-03-29 19:51:16
@JohnBalvinArias 你可以将它添加到上面的例子中 &grave;
2021-04-11 19:51:16

您可以使用简单的正则表达式来断言 id 仅包含允许的字符,如下所示:

if(id.match(/^[0-9a-zA-Z]{1,16}$/)){
    //The id is fine
}
else{
    //The id is illegal
}

我的示例只允许使用字母数字字符和长度为 1 到 16 的字符串,您应该更改它以匹配您使用的 id 类型。

顺便说一下,在第 6 行,value 属性缺少一对引号,当您在两个级别上引用时很容易犯这个错误。

我看不到您的实际数据流,根据上下文,此检查可能根本不需要,或者可能不够。为了进行适当的安全审查,我们需要更多信息。

一般来说,关于内置的转义或消毒功能,不要盲目相信它们。您需要确切地知道他们做什么,并且您需要确定这实际上是您所需要的。如果它不是您所需要的,那么您自己的代码,大多数情况下,像我给您的那种简单的白名单正则表达式就可以正常工作。

由于您要转义的文本将出现在 HTML 属性中,因此您必须确保不仅要转义 HTML 实体,还要转义 HTML 属性:

var ESC_MAP = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;'
};

function escapeHTML(s, forAttribute) {
    return s.replace(forAttribute ? /[&<>'"]/g : /[&<>]/g, function(c) {
        return ESC_MAP[c];
    });
}

然后,您的转义代码变为var user_id = escapeHTML(id, true).

有关更多信息,请参阅Javascript 中的万无一失的 HTML 转义

在 HTML 属性中使用用户提供的数据时,您需要采取额外的预防措施。因为属性比 HTML 标签内的输出具有更多的攻击向量。

避免 XSS 攻击的唯一方法是对除字母数字字符之外的所有内容进行编码。使用 &#xHH; 转义所有 ASCII 值小于 256 的字符 格式。不幸的是,如果您使用 CSS 类和 javascript 来获取这些元素,这可能会导致您的场景出现问题。

OWASP 对如何缓解 HTML 属性 XSS 有很好的描述:

http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.233_-_JavaScript_Escape_Before_Inserting_Untrusted_Data_into_HTML_JavaScript_Data_Values