FreshMarker Ausgabeformate und Autoescaping

Jede Form ist ein Kerker.

Friedrich Hebbel

Bei der Verwendung einer Template-Engine ist die größte Herausforderung das Einfügen der variablen Inhalte in das Dokument. Hierbei ist neben der technischen Realisierung auch die Sicherheit zu beachten. Allzu leicht könnten sonst bösartige Inhalte in das generierte Dokument gelangen. Je nach Verwendung als E-Mail, Webseite oder Konfiguration wären so diverse Angriffsszenarien denkbar.

Das Problem der Absicherung gegen jede Art von Injektion wird in FreshMarker durch die Verwendung spezieller Ausgabeformate umgesetzt. Je nach Ausgabeformat werden dann spezielle Zeichen in den Interpolations maskiert.

Es existieren mehrere Möglichkeiten um das Ausgabeformat für ein Template zu setzen. Über die Konfiguration kann das Standardformat für alle Templates vorbelegt werden.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
StringTemplateLoader templateLoader = new StringTemplateLoader();
templateLoader.putTemplate("test", "<P>${content}</P>");
Configuration configuration = new Configuration();
configuration.registerTemplateLoader(templateLoader);
configuration.setOutputFormat("HTML");
Template template = configuration.getTemplate("test");
System.out.println(template.process(Map.of("content", "<br/>")));
StringTemplateLoader templateLoader = new StringTemplateLoader(); templateLoader.putTemplate("test", "<P>${content}</P>"); Configuration configuration = new Configuration(); configuration.registerTemplateLoader(templateLoader); configuration.setOutputFormat("HTML"); Template template = configuration.getTemplate("test"); System.out.println(template.process(Map.of("content", "<br/>")));
StringTemplateLoader templateLoader = new StringTemplateLoader();
templateLoader.putTemplate("test", "<P>${content}</P>");

Configuration configuration = new Configuration();
configuration.registerTemplateLoader(templateLoader);
configuration.setOutputFormat("HTML");

Template template = configuration.getTemplate("test");
System.out.println(template.process(Map.of("content", "<br/>")));

In diesem Beispiel wird das Ausgabeformat

HTML
HTML als Standardformat in der Konfiguration gesetzt. Damit werden die Zeichen
<
<,
>
>,
"
" und
'
' durch
&lt;
&lt;,
&gt;
&gt;,
&quot;
&quot; und
&#39;
&#39; ersetzt. Die Ausgabe des Beispiels lautet
<P>&lt;br/&gt;</P>
<P>&lt;br/&gt;</P>. Neben dem Ausgabeformat
HTML
HTML werden auch
XHTML
XHTML,
XML
XML unterstützt. es können auch
plainText
plainText,
undefined
undefined,
JavaScript
JavaScript,
JSON
JSON und
CSS
CSS angegeben werden. Diese führen jedoch keinerlei Maskierung durch.

Für einen Block innerhalb eines Templates kann das Ausgabeformat über die Direktive

outputformat
outputformat geändert werden.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
${content}
<#outputformat 'HTML'>
${content}
</#outputformat>
${content}
${content} <#outputformat 'HTML'> ${content} </#outputformat> ${content}
${content}
<#outputformat 'HTML'>
${content}
</#outputformat>
${content}

In diesem Beispiel wird nur der Inhalt der Direktive mit dem Ausgabeformat

HTML
HTML maskiert und die Interpolations davor und danach werden mit dem Standardformat eingefügt. Die Ausgabe des Beispiels für das Standardformat
plainText
plainText lautet
<br/>&tl;br/&gt;<br/>
<br/>&tl;br/&gt;<br/>.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<#outputformat 'HTML'>
${content?noEsc}
</#outputformat>
<#outputformat 'HTML'> ${content?noEsc} </#outputformat>
<#outputformat 'HTML'>
${content?noEsc}
</#outputformat>

Soll für eine Interpolation keine Maskierung erfolgen, dann kann dies über das Built-In

noEsc
noEsc gesteuert werden. Obwohl das Ausgabeformat
HTML
HTML gewählt wurde, wird über das Built-In die Maskierung deaktiviert und die Ausgabe des Beispiels ist
<br/>
<br/>.

Das Herzstück der Implementierung besteht aus der Datenmodell Ergänzung

TemplateMarkup
TemplateMarkup. Diese Klasse speichert Text und das dazu gewünschte Ausgabeformat. Bei der Generierung einer
InterpolationFragment
InterpolationFragment Instanz wird der Interpolation Ausdruck von einer
TemplateMarkup
TemplateMarkup Instanz umfasst,

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@Override
public BlockFragment visit(Interpolation ftl, BlockFragment input) {
logger.debug("interpolation: {}", ftl);
TemplateObject interpolation = ftl.getChild(1).accept(interpolationBuilder, null);
input.addFragment(new InterpolationFragment(new TemplateMarkup(interpolation), ftl));
return input;
}
@Override public BlockFragment visit(Interpolation ftl, BlockFragment input) { logger.debug("interpolation: {}", ftl); TemplateObject interpolation = ftl.getChild(1).accept(interpolationBuilder, null); input.addFragment(new InterpolationFragment(new TemplateMarkup(interpolation), ftl)); return input; }
@Override
public BlockFragment visit(Interpolation ftl, BlockFragment input) {
  logger.debug("interpolation: {}", ftl);
  TemplateObject interpolation = ftl.getChild(1).accept(interpolationBuilder, null);
  input.addFragment(new InterpolationFragment(new TemplateMarkup(interpolation), ftl));
  return input;
}

Der Konstruktor mit nur einem Parameter verwendet ein spezielles Ausgabeformat

DelegatingOutputFormat
DelegatingOutputFormat.INSTANCE. Dieses Ausgabeformat delegiert die Maskierung an das aktuell im Environment eingestellte Ausgabeformat. Ist das Standardformat beispielsweise
XML
XML, dann werden solche
TemplateMarkup
TemplateMarkup Instanzen mit dem
HtmlOutputFormat.XML
HtmlOutputFormat.XML maskiert.

Innerhalb der Klasse

TemplateMarkup
TemplateMarkup wird das eingebettete
TemplateObject
TemplateObject ausgewertet und wenn es sich nicht wiederum um ein
TemplateMarkup
TemplateMarkup handelt mit dem entsprechenden
Formatter
Formatter in Text umgewandelt und dann von dem aktuellen Ausgabeformat maskiert.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@Override
public TemplateObject evaluateToObject(ProcessContext context) {
TemplateObject templateObject = getTemplateObject(context);
if (templateObject.isMarkup()) {
return templateObject.evaluate(context, TemplateString.class);
}
Environment environment = context.getEnvironment();
String result = context.getFormatter(templateObject.getClass()).format(templateObject, environment.getLocale());
return outputFormat.escape(environment, result);
}
@Override public TemplateObject evaluateToObject(ProcessContext context) { TemplateObject templateObject = getTemplateObject(context); if (templateObject.isMarkup()) { return templateObject.evaluate(context, TemplateString.class); } Environment environment = context.getEnvironment(); String result = context.getFormatter(templateObject.getClass()).format(templateObject, environment.getLocale()); return outputFormat.escape(environment, result); }
@Override
public TemplateObject evaluateToObject(ProcessContext context) {
  TemplateObject templateObject = getTemplateObject(context);
  if (templateObject.isMarkup()) {
    return templateObject.evaluate(context, TemplateString.class);
  }
  Environment environment = context.getEnvironment();
  String result = context.getFormatter(templateObject.getClass()).format(templateObject, environment.getLocale());
  return outputFormat.escape(environment, result);
}

Handelt es sich bei dem eingebetteten

TemplateObject
TemplateObject um eine
TemplateMarkup
TemplateMarkup Instanz, dann wird diese ausgewertet und ihr Inhalt direkt zurückgegeben. Auf diese Weise werden die
outputformat
outputformat Direktive und das
noEsc
noEsc Built-In realisiert.

Die

outputformat
outputformat Direktive erzeugt ein spezielle
OutputFormatFragment
OutputFormatFragment für das Template, mit dem für das eingebettete
BlockFragment
BlockFragment das AusgabeFormat im
Environment
Environment gewechselt wird.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class OutputFormatFragment implements Fragment {
private final BlockFragment content;
private final String format;
public OutputFormatFragment(BlockFragment content, String format) {
this.content = content;
this.format = format;
}
@Override
public void process(ProcessContext context) {
Environment environment = context.getEnvironment();
context.setEnvironment(new SettingEnvironment(environment, null, context.getOutputFormat(format)));
content.process(context);
context.setEnvironment(environment);
}
}
public class OutputFormatFragment implements Fragment { private final BlockFragment content; private final String format; public OutputFormatFragment(BlockFragment content, String format) { this.content = content; this.format = format; } @Override public void process(ProcessContext context) { Environment environment = context.getEnvironment(); context.setEnvironment(new SettingEnvironment(environment, null, context.getOutputFormat(format))); content.process(context); context.setEnvironment(environment); } }
public class OutputFormatFragment implements Fragment {

  private final BlockFragment content;
  private final String format;

  public OutputFormatFragment(BlockFragment content, String format) {
    this.content = content;
    this.format = format;
  }

  @Override
  public void process(ProcessContext context) {
    Environment environment = context.getEnvironment();
    context.setEnvironment(new SettingEnvironment(environment, null, context.getOutputFormat(format)));
    content.process(context);
    context.setEnvironment(environment);
  }
}

Das Built-In

noEsc
noEsc erzeugt für eine
TemplateString
TemplateString Instanz eine
TemplateMarkup
TemplateMarkup Instanz, die das Ausgabeformat
NoEscapeFormat.INSTANCE
NoEscapeFormat.INSTANCE verwendet.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@BuiltInMethod
public static TemplateMarkup noEsc(TemplateString value) {
return new TemplateMarkup(value, NoEscapeFormat.INSTANCE);
}
@BuiltInMethod public static TemplateMarkup noEsc(TemplateString value) { return new TemplateMarkup(value, NoEscapeFormat.INSTANCE); }
 @BuiltInMethod
public static TemplateMarkup noEsc(TemplateString value) {
  return new TemplateMarkup(value, NoEscapeFormat.INSTANCE);
}

Wie der Name erahnen lässt, nimmt dieses Ausgabeformat keinerlei Maskierungen des Textes vor.

Damit ist der Mechanismus für das Maskieren von Zeichen in der FreshMarker Ausgabe auch schon beschrieben. Momentan existiert noch keine Möglichkeit, zusätzliche Ausgabeformate zu definieren, aber das ist der Inhalt eines zukünftigen Beitrags.

Leave a Comment