Экранирование по умолчанию во Freemarker

#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 в свой путь к шаблону и удалить объявление (и все экранирующие директивы, поскольку вы будете экранироваться по умолчанию).