我正在为我在空闲时间工作的聊天应用程序编写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本身。
答案 0 :(得分:38)
从不使用escape()
。这与HTML编码无关。它更像是URL编码,但它甚至都不正确。这是一种奇怪的非标准编码,只能在JavaScript中使用。
如果你想要一个HTML编码器,你必须自己编写,因为JavaScript没有给你一个。例如:
function encodeHTML(s) {
return s.replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
}
然而,虽然这足以将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调用的长度。
ETA:
我目前正在使用jQuery作为框架
好的,然后考虑jQuery 1.4创建快捷方式,例如:
var log= $('<div>', {className: 'log'});
var input= $('<input>', {readOnly: true, val: user_id});
...
我现在遇到的问题是我使用JSONP向页面添加元素和事件,因此在显示消息之前我无法知道元素是否已存在。
您可以在JavaScript中查找user_id
到元素节点(或包装器对象),以便将该信息保存在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
成员名称,混淆它。)
答案 1 :(得分:17)
我喜欢的另一种方法是使用原生DOM功能:http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript
答案 2 :(得分:9)
你可以使用一个简单的正则表达式断言id只包含允许的字符,如下所示:
if(id.match(/^[0-9a-zA-Z]{1,16}$/)){
//The id is fine
}
else{
//The id is illegal
}
我的示例仅允许使用字母数字字符和长度为1到16的字符串,您应该更改它以匹配您使用的ID类型。
顺便说一句,在第6行,value属性缺少一对引号,当你引用两个级别时,这是一个很容易犯的错误。
我无法看到您的实际数据流,具体取决于上下文可能根本不需要此检查,或者可能还不够。为了进行适当的安全审查,我们需要更多信息。
一般来说,关于内置的逃生或消毒功能,不要盲目相信它们。你需要确切地知道他们做了什么,你需要确定这实际上是你需要的。如果它不是你需要的,你自己的代码,大多数时候一个像我给你的那个简单的白名单正则表达式就可以了。
答案 3 :(得分:8)
您也可以使用:
function sanitarize(string) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
"/": '/',
};
const reg = /[&<>"'/]/ig;
return string.replace(reg, (match)=>(map[match]));
}
OWASP文档建议maping:https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
答案 4 :(得分:2)
由于您要转义的文本将出现在HTML属性中,因此您必须确保不仅要转义HTML实体,还要转义HTML属性:
var ESC_MAP = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
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。
答案 5 :(得分:1)
在HTML属性中使用用户提供的数据时,您需要采取额外的预防措施。因为属性比HTML标记内的输出具有更多的攻击向量。
避免XSS攻击的唯一方法是编码除字母数字字符以外的所有内容。使用&amp; #xHH转义ASCII值小于256的所有字符;格式。不幸的是,如果您使用CSS类和javascript来获取这些元素,那么在您的场景中可能会出现问题。
OWASP很好地描述了如何缓解HTML属性XSS: