在将用户输入添加到使用Javascript的DOM中之前对用户输入进行消毒

问题描述:

我正在为空闲时间正在编写的聊天应用程序编写JS,并且需要根据用户提交的数据更改HTML标识。通常情况下,这通常是一些不稳定的事情,我甚至不会去尝试,但是这次我没有看到自己有太多的选择。然后我需要做的就是转义HTML标识以确保它不会允许XSS或破坏HTML。在将用户输入添加到使用Javascript的DOM中之前对用户输入进行消毒

下面的代码:

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本身。

+0

到底是什么'id'的功能? – Tgr 2010-05-08 13:17:00

+0

现在id是代表用户的任何字符串。我用它来区分属于不同用户的页面的类似结构。 通过这个逻辑,MD5或base64可能是一个很好的等待,我想。这只是没有内置的东西。 – 2010-05-08 13:40:47

从不使用escape()。这与HTML编码无关。这更像URL编码,但它甚至不适合。这是一个奇怪的非标准编码,只能在JavaScript中使用。

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

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

不过虽然这是足以让你在user_idinput 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框架来减少创建集附加调用的长度。

ETA:

我使用jQuery的那一刻作为一个框架

OK,然后再考虑了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成员名称,混淆。)

+0

我目前使用jQuery作为框架,所以任何与此相关的想法都可能会有帮助。 我现在遇到的问题是,我使用JSONP将元素和事件添加到页面,因此在显示消息之前无法确定元素是否已存在。这让我觉得我不得不使用糟糕的方法,我必须找到要选择的元素,或者如果它们不在那里,请添加它们。因此,我不认为你最后的建议可行,但我可能是错的。 受限制的字符集使我认为该ID的MD5可能是我所能做的。 – 2010-05-08 14:26:42

+0

尽管我可以添加关于用户名接受的字符的假设,但可以使用正则表达式并完成它。 – 2010-05-08 14:34:44

你可以使用一个简单的正则表达式断言ID只包含允许的字符,像这样:

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

我的例子只允许字母数字字符,长度为1的串16,你应该改变它以匹配您使用的ids类型。

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

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

一般而言,关于建立在逃生或消毒功能,不要盲目信任他们。你需要确切地知道他们做了什么,并且你需要确定这实际上是你需要的。如果它不是你所需要的,那么你的代码就是你自己的,大多数情况下,像我给你的简单的白名单正则表达式工作得很好。

在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

另一种方法,我喜欢的是使用原生DOM功能:http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript

+1

@BrandonMintern的要点不适合我。 – cmcculloh 2016-07-05 18:10:12

+1

我写了那篇博文。不幸的是,顶部给出的TL; DR技术不适用于HTML属性。还有一些其他方法可以在文章结尾正确转义HTML属性,即:http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript/#hack-3-more-efficient -catchall – 2016-07-06 23:06:59

既然你是文本转义将出现在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)

欲了解更多信息,请参阅Foolproof HTML escaping in Javascript

你也可以使用这样的:

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

OWASP文档建议马平:https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet