Санизируя ввод пользователя перед добавлением его в DOM в Javascript
Я пишу JS для приложения чата, над которым я работаю в свободное время, и мне нужно иметь идентификаторы HTML, которые меняются в соответствии с представленными пользователем данными. Это обычно что-то концептуально шаткий, что я бы даже не попытался, но на этот раз я не вижу, чтобы у меня был большой выбор. Мне нужно сделать это, чтобы избежать идентификатора HTML, чтобы убедиться, что он не позволит 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()
, но я не уверен, насколько хорошо это должно сравниться с другими альтернативами. В основном я использую для дезинфекции входных данных до того, как он войдет в текст node, а не сам идентификатор.
Ответы
Ответ 1
Никогда не используйте escape()
. Это не связано с HTML-кодированием. Это больше похоже на URL-кодирование, но это даже не так. Это странная нестандартная кодировка, доступная только в JavaScript.
Если вы хотите кодировщик HTML, вам придется писать его самостоятельно, так как JavaScript не дает вам одного. Например:
function encodeHTML(s) {
return s.replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
}
Однако, пока этого достаточно, чтобы разместить user_id
в таких местах, как input value
, этого недостаточно для 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
или беспокоиться о том, как вставляются произвольные строки в идентификаторы.
например:.
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-addends.
ETA:
Я использую 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
путают ее.)
Ответ 2
Другим подходом, который мне нравится, является использование собственных возможностей DOM: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript
Ответ 3
Вы можете использовать простое регулярное выражение, чтобы утверждать, что идентификатор содержит только допустимые символы:
if(id.match(/^[0-9a-zA-Z]{1,16}$/)){
//The id is fine
}
else{
//The id is illegal
}
В моем примере допускаются только буквенно-цифровые символы и строки длиной от 1 до 16, вы должны изменить его в соответствии с типом используемых вами идентификаторов.
Кстати, в строке 6 свойство value отсутствует пара кавычек, что легко сделать при цитировании на двух уровнях.
Я не вижу ваш фактический поток данных, в зависимости от контекста эта проверка может вообще не понадобиться, или ее может быть недостаточно. Чтобы сделать правильный обзор безопасности, нам потребуется дополнительная информация.
В целом, о встроенных средствах спасения или дезинфекции, не доверяйте им слепо. Вам нужно точно знать, что они делают, и вам нужно установить, что это на самом деле то, что вам нужно. Если это не то, что вам нужно, код ваш собственный, в большинстве случаев простой белый список регулярных выражений, как тот, который я дал вам работает просто отлично.
Ответ 4
Вы также можете использовать это:
function sanitarize(string) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
"/": '/',
};
const reg = /[&<>"'/]/ig;
return string.replace(reg, (match)=>(map[match]));
}
Документация OWASP предполагает сопоставление: https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
Ответ 5
При использовании пользовательских данных в атрибутах HTML вам необходимо принять дополнительные меры предосторожности. Поскольку атрибуты имеют гораздо больше векторов атак, чем вывод внутри HTML-тегов.
Единственный способ избежать атак XSS - это кодировать все, кроме буквенно-цифровых символов. Удалите все символы с значениями ASCII менее 256 с помощью & #xHH; формат. К сожалению, это может вызвать проблемы в вашем сценарии, если вы используете классы 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
Ответ 6
Так как текст, который вы экранизируете, появится в атрибуте 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)
.
Для получения дополнительной информации см. Неверное HTML-экранирование в Javascript.