Регистрация  |  Вход

HTML Sanitize в Salesforce

Есть в web dev распространенный тип задачи, как вывод пользовательских данных на странице. И мы не можем гарантировать что пользователь предоставит нам безопасные данные, поэтому должны предусмотреть какой-нибудь механизм защиты от "плохих" данных. Данный тип уязвимости называется XSS - это случай когда при определенных обстоятельствах пользовательские данные, содержащие, например, javascript становятся частью страницы и могут быть выполнены.

Теперь ближе к Salesforce. По умолчанию все данные, выводимые в Visualforce странице с помощью formula expression (конструкция вида {!...}) автоматически экранируются, тем самым превращая все спецсимволы в безопасные html entities, которые воспринимаются браузерами как простой текст. Для большинства задач этого хватает.



Но есть задачи когда надо вывести кусок HTML, который предоставил нам пользователь, как часть страницы. Самый простой и опасный способ вывести переменную как часть HTML это воспользоваться конструкцией:




<apex:outputText value="{!ourVariable}" escape="false" />


Вот в escape="false" кроется вся проблема. Я могу передать в ourVariable следующую строку




Hello Doom <script>alert('Can you see me?')</script>


И получить выполнение кода внутри тега script. Это одна сторона медали. Вторая сторона медали в том, что даже если вы придумаете супер проверку на стороне apex перед выводом переменной, все равно будете долго объяснять Security Team зачем вам нужен escape="false" при прохождении Security Review и большая вероятность что у вас не получится его пройти. 



Недавний практический пример показал, что товарищи из SF крайне против использования escape="false" в пакете и более того не сильно хотят возиться и разбираться в кастомных решениях по фильтрации (sanitize) пользовательского html перед выводом. Но чем они смогли помочь по теме вопроса, так это дали очень интересный рецепт, который для них (Security Team) является приемлемым.



 Решение от Security Team:




<apex:inputTextarea value="{!ourVariable}" richText="true" disabled="true" />


Имеем наш кусок html, который действительно выводится как часть страницы. НО наш html находится внутри salesforce виджета Rich Text Editor. Вторая часть решения - это с помощью css+js скрыть сам виджет но оставить наш кусок html. 



На этом можно было остановиться. Решение рабочее. но как по мне достаточно кастыльное. Проблема в том что чистым css тут не обойтись, потому что наш html выводится внутри iframe, доступ к которому, чтобы применить стили, необходимо получать через js.



Я пошел немного дальше. Сам виджет - это ckEditor. Значит имеем что пользовательский кусок html выводится как и чистится средствами самого ckEditor. У данного редактора очень качественная документация, поэтому найти нужный функционал не составило труда.



Смысл моего решения:




  1. вставляем на страницу виджет <apex:inputTextarea richText="true" id="ckEditorInstance" /> . Заметьте что я ничего не вывожу, просто вставляю пустой виджет. 

  2. получаем экземпляр (instance) редактора. Это необходимо для того чтобы использовать стандартные настройками безопасности, которые использует salesforce для своего виджета.

  3. Самое важное - за очистку html отвечает Advanced Content Filter который мы будем использовать в своих корыстных целях.



По идее я мог бы подключить ckEditor библиотеку напрямую из Static Resources, но по-умолчанию настройки фильтра не пропускают ничего из html разметки и чтобы на выходе получить что-то больше чем просто текст, мне нужно вводить свои правила фильтрации. Вот для этого нам и нужен экземпляр редактора из Rich Textarea виджета.



Реализация




function sanitizeHtml (dirtyHtml) {
var ckInstance = CKEDITOR.instances[jQuery('[id$="ckEditorInstance"]').attr('id')];
var fragment = CKEDITOR.htmlParser.fragment.fromHtml(dirtyHtml);
var writer = new CKEDITOR.htmlParser.basicWriter();
ckInstance.filter.applyTo(fragment);
fragment.writeHtml(writer);
return writer.getHtml();
}


Это минимально рабочий код который можно использовать в сочетании с виджетом <apex:inputTextarea richText="true" id="ckEditorInstance" />



Конечно его можно улучшать, но я не буду здесь выкладывать код полностью, потому что это собственность чужого проекта. Скажу только что можно и нужно дополнить.




  1. у меня, например, этот код и виджет, а также еще дополнительные костыли вынесены в отдельный vf component. Мне достаточно вставить компонент в нужную VF страницу и использовать метод sanitizeHtml().

  2. еще нужен небольшой костыль если нужно использовать метод sanitizeHtml() сразу после загрузки страницы. Дело в том что instance можно получить только после полной инициализации ckEditor, а это событие никак не связано с page onload :).

  3. а также не рассказываю как получить на странице в JS пользовательский html чтобы скормить его функции sanitizeHtml().

  4. можно добавить свои правила фильтрации html - я, например, разрешил тег iframe чтобы можно было вставлять youtube|vimeo видео.



Что в итоге имеем? Избавились от escape="false" и используем стандартные правила очистки html от самого Salesforce. Я думаю Security Team будет довольно таким решением. Плюс моего подхода - все работает на уровне JS - чистим html сколько угодно и когда угодно. Например, делал отличный функционал, где пользователь может набрать свой html и тут же просмотреть его в режиме preview - не надо ничего перезагружать, обрабатывать на бэкенде - все тут же через JS чистится и показывается.