Очистка ввода пользователя перед добавлением его в 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()
функции, но я не уверен, насколько хорошо это должно быть по сравнению с другими альтернативами. В основном я привык дезинфицировать входные данные до того, как они попадут в текстовый узел, а не сам идентификатор.
8 ответов
Никогда не используйте escape()
, Это никак не связано с HTML-кодированием. Это больше похоже на URL-кодирование, но даже не совсем так. Это странная нестандартная кодировка, доступная только в JavaScript.
Если вам нужен HTML-кодировщик, вам придется написать его самостоятельно, так как JavaScript не дает его. Например:
function encodeHTML(s) {
return s.replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
}
Однако, хотя этого достаточно, чтобы поставить user_id
в таких местах, как input value
не достаточно для id
потому что идентификаторы могут использовать только ограниченный выбор символов. (А также %
не среди них, так escape()
или даже encodeURIComponent()
не хорошо.)
Вы можете изобрести свою собственную схему кодирования, чтобы поместить любые символы в идентификатор, например:
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-append.
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);
}
(The _map_
Префикс заключается в том, что объекты JavaScript не работают как отображение произвольных строк. Пустая строка и, в IE, некоторые Object
имена членов, запутайте это.)
Вы также можете использовать это:
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
Другой подход, который мне нравится, заключается в использовании собственных возможностей DOM: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript
Вы можете использовать простое регулярное выражение, чтобы утверждать, что идентификатор содержит только разрешенные символы, например так:
if(id.match(/^[0-9a-zA-Z]{1,16}$/)){
//The id is fine
}
else{
//The id is illegal
}
Мой пример допускает только буквенно-цифровые символы и строки длиной от 1 до 16, вы должны изменить его в соответствии с типом идентификаторов, которые вы используете.
Кстати, в строке 6 в свойстве value отсутствует пара кавычек, что является легкой ошибкой, когда вы цитируете на двух уровнях.
Я не вижу фактического потока данных, в зависимости от контекста эта проверка может вообще не понадобиться или может быть недостаточной. Для того, чтобы сделать правильный обзор безопасности, нам нужно больше информации.
В общем, о встроенных функциях escape или sanitize, не доверяйте им вслепую. Вам нужно точно знать, что они делают, и вам нужно установить, что это именно то, что вам нужно. Если это не то, что вам нужно, используйте свой собственный код, в большинстве случаев просто регулярное выражение белого списка, подобное тому, которое я вам дал, работает отлично.
Поскольку экранируемый текст будет отображаться в атрибуте 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.
Вы должны принять дополнительные меры предосторожности при использовании предоставленных пользователем данных в атрибутах HTML. Потому что атрибуты имеют гораздо больше векторов атаки, чем вывод в тегах HTML.
Единственный способ избежать атак XSS - это кодировать все, кроме буквенно-цифровых символов. Сбросьте все символы со значениями ASCII меньше 256 с помощью & # xHH; формат. К сожалению, это может вызвать проблемы в вашем сценарии, если вы используете CSS-классы и JavaScript для получения этих элементов.
OWASP имеет хорошее описание того, как смягчить атрибут HTML XSS:
http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
Просто чтобы добавить к комментарию @SilentImp. если вам нужна версия typeScript...
Следующий подход для предотвращения XSS выглядит хорошим решением.
var sanitizeHTML = function (str) {
return str.replace(/[^\w. ]/gi, function (c) {
return '&#' + c.charCodeAt(0) + ';';
});
};
Вот рабочий пример:
Это решение было предоставлено здесь .