#java #xss #escaping #freemarker
#java #xss #экранирование #freemarker
Вопрос:
В шаблонах Freemarker мы можем использовать директиву escape для автоматического применения экранирования ко всем интерполяциям внутри включенного блока:
<#escape x as x?html>
<#-- name is escaped as html -->
Hallo, ${name}
</#escape>
Есть ли способ программно добиться аналогичного эффекта, определив экранирование по умолчанию, применяемое ко всем интерполяциям в шаблоне, включая внешние экранирующие директивы?
Спасибо.
Ответ №1:
Чтобы уточнить ответ Attila: вы можете использовать класс, подобный этому, а затем обернуть свой загрузчик шаблонов следующим образом:
final TemplateLoader templateLoader = new ClassTemplateLoader(this.getClass(), templatePath) {
/**
* Replaces the normal template reader with something that changes the default
* escaping to HTML as to avoid XSS attacks.
*/
@Override
public Reader getReader(Object templateSource, String encoding) throws IOException {
return new WrappingReader(super.getReader(templateSource, encoding), "<#escape x as x?html>", "</#escape>");
}
};
Если вы не включаете разрывы строк в добавленные части, у вас не возникает проблемы с нумерацией строк. Однако вы не можете использовать <#ftl>/[#ftl] при таком подходе.
Комментарии:
1. Ссылка Питера на класс WrappingReader больше не работает. Новое местоположение: sourceforge.net/p/metadata-net/code/HEAD/tree/shared/trunk/util /…
Ответ №2:
Начиная с 2.3.24, с каждым шаблоном связан freemarker.core.OutputFormat
объект, который указывает, выполняется ли экранирование ${...}
(и #{...}
) ли оно и как. OuputFormat
для HTML, XML и RTF предоставляются «из коробки», но вы также можете определить свои собственные форматы. Когда выбранное OutputFormat
экранирование выполняется по умолчанию, вы можете явно запретить экранирование, например ${foo?no_esc}
.
Существует несколько способов связать шаблоны с OutputFormat
тем, который вы хотите. Для экранирования HTML и XML рекомендуемый способ — установить для recognize_standard_file_extensions
параметра конфигурации значение true
, затем использовать ftlh
расширение файла для HTML и ftlx
расширение файла для шаблонов XML. Вы также можете связать OutputFormat
-s с шаблонами на основе шаблонов произвольного имени шаблона (пути к шаблону), используя template_configurers
параметр. И последнее, что не менее важно, вы можете просто установить формат вывода по умолчанию глобально, например configuration.setOutputFormat(HTMLOutputFormat.INSTANCE)
. Вы также можете переопределить формат вывода в верхней части шаблона как <#ftl output_format='HTML'>
, хотя его следует использовать редко.
Страницы документации по теме: http://freemarker.org/docs/dgui_misc_autoescaping.html, http://freemarker.org/docs/pgui_config_outputformatsautoesc.html
Ответ №3:
Решение есть, хотя оно и не совсем тривиальное. Вы можете создать специальный загрузчик шаблонов, который оборачивает другие загрузчики шаблонов и вставляет <#escape x как x?html> в пролог исходного текста шаблона и добавляет его в качестве эпилога.
Очевидные недостатки: — номера столбцов в первой строке будут удалены — если ваш шаблон начинается с объявления <#ftl>, вам нужно вставить <#escape> после него.
Ответ №4:
Предлагаемые загрузчики шаблонов в ссылках нуждаются в небольшой настройке, если вы используете <#include parse=false …/> для включения, например, HTML в свои шаблоны.
Кроме того, вам нужно скопировать spring.ftl и использовать свою собственную копию с удаленной директивой <#ftl ..> сверху, как сказал Том.
Следующее работает хорошо, хотя и немного грубо (с использованием guava поверх commons-io)
@Override
public Reader getReader(Object pTemplateSource, String pEncoding) throws IOException {
Reader tReader = delegate.getReader(pTemplateSource, pEncoding);
try {
String tTemplateText = CharStreams.toString(tReader);
//only include files ending with "ftl", as we may have some parse=false on included html files
if (pTemplateSource.toString().endsWith("ftl")) {
return new StringReader(ESCAPE_PREFIX tTemplateText ESCAPE_SUFFIX);
}
return new StringReader(tTemplateText);
} finally {
Closeables.closeQuietly(tReader);
}
}
Ответ №5:
На самом деле вам не нужен WrappingReader для добавления экранирований. Вы можете просто создать декоратор вокруг любого TemplateLoader, прочитать в шаблоне строку, обернуть текст шаблона в escapes и затем вернуть StringReader, который считывает результирующую строку. Чтобы увидеть, как это делается, взгляните сюда. Единственная ошибка, которую я обнаружил, заключается в том, что если вы используете этот подход и включаете макросы spring.ftl из classpath, они взорвутся, поскольку в самом верху у них есть объявление <#ftl>. Однако вы можете просто скопировать spring.ftl в свой путь к шаблону и удалить объявление (и все экранирующие директивы, поскольку вы будете экранироваться по умолчанию).