Skip to content

Commit

Permalink
ISSUE-264: introduction of a SafeMustacheFactory for use when using u…
Browse files Browse the repository at this point in the history
…ntrusted templates
  • Loading branch information
spullara committed May 11, 2021
1 parent 28d8a4a commit 7d9c237
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 5 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ Mustache.java [![Build Status](https://travis-ci.org/spullara/mustache.java.svg?
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fspullara%2Fmustache.java.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fspullara%2Fmustache.java?ref=badge_shield)
=============

Mustache.java is not designed to allow untrusted parties to provide templates. It may be possible to lock it down to provide that safely,
but by default it is UNSAFE.
**Mustache.java is not designed to allow untrusted parties to provide templates. It may be possible to lock it down to provide that safely,
but by default it is UNSAFE. Use the SafeMustacheFactory and whitelist all templates and partials.**

As of release 0.9.0 mustache.java is now Java 8 only. For Java 6/7 support use 0.8.x.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ public int getRecursionLimit() {
return recursionLimit;
}

private final ThreadLocal<Map<String, Mustache>> partialCache = ThreadLocal.withInitial(() -> new HashMap<>());
private final ThreadLocal<Map<String, Mustache>> partialCache = ThreadLocal.withInitial(HashMap::new);

/**
* In order to handle recursion, we need a temporary thread local cache during compilation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public class MustacheParser {
public static final String DEFAULT_SM = "{{";
public static final String DEFAULT_EM = "}}";
private final boolean specConformWhitespace;
private MustacheFactory mf;
private final MustacheFactory mf;
private boolean allowChangingDelimeters = true;

protected MustacheParser(MustacheFactory mf, boolean specConformWhitespace) {
this.mf = mf;
Expand Down Expand Up @@ -246,6 +247,9 @@ protected Mustache compile(final Reader reader, String tag, final AtomicInteger
out = write(mv, out, file, currentLine.intValue(), startOfLine);
break;
case '=':
if (!allowChangingDelimeters) {
throw new MustacheException("Disallowed: changing defaul delimiters");
}
// Change delimiters
out = write(mv, out, file, currentLine.intValue(), startOfLine);
String trimmed = command.substring(1).trim();
Expand Down Expand Up @@ -314,4 +318,7 @@ private StringBuilder write(MustacheVisitor mv, StringBuilder out, String file,
return new StringBuilder();
}

public void setAllowChangingDelimeters(boolean allowChangingDelimeters) {
this.allowChangingDelimeters = allowChangingDelimeters;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.github.mustachejava;

import com.github.mustachejava.codes.ValueCode;
import com.github.mustachejava.reflect.SimpleObjectHandler;
import com.github.mustachejava.resolver.DefaultResolver;

import java.io.File;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Set;

import static com.github.mustachejava.util.HtmlEscaper.escape;

public class SafeMustacheFactory extends DefaultMustacheFactory {

// Only allow public access
public static final SimpleObjectHandler OBJECT_HANDLER = new SimpleObjectHandler() {
@Override
protected void checkMethod(Method member) throws NoSuchMethodException {
if ((member.getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC) {
throw new NoSuchMethodException("Only public members allowed");
}
}

@Override
protected void checkField(Field member) throws NoSuchFieldException {
if ((member.getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC) {
throw new NoSuchFieldException("Only public members allowed");
}
}
};

public SafeMustacheFactory(Set<String> allowedResourceNames, String resourceRoot) {
super(new DefaultResolver(resourceRoot) {
@Override
public Reader getReader(String resourceName) {
// Only allow allowed resources
if (allowedResourceNames.contains(resourceName)) {
return super.getReader(resourceName);
}
throw new MustacheException("Disallowed: resource requested");
}
});
setup();
}

public SafeMustacheFactory(Set<String> allowedResourceNames, File fileRoot) {
super(new DefaultResolver(fileRoot) {
@Override
public Reader getReader(String resourceName) {
// Only allow allowed resources
if (allowedResourceNames.contains(resourceName)) {
return super.getReader(resourceName);
}
throw new MustacheException("Disallowed: resource requested");
}
});
setup();
}

private void setup() {
setObjectHandler(OBJECT_HANDLER);
mc.setAllowChangingDelimeters(false);
}

@Override
public MustacheVisitor createMustacheVisitor() {
return new DefaultMustacheVisitor(this) {
@Override
public void pragma(TemplateContext tc, String pragma, String args) {
throw new MustacheException("Disallowed: pragmas in templates");
}

@Override
public void value(TemplateContext tc, String variable, boolean encoded) {
if (!encoded) {
throw new MustacheException("Disallowed: non-encoded text in templates");
}
list.add(new ValueCode(tc, df, variable, encoded));
}
};
}

@Override
public void encode(String value, Writer writer) {
escape(value, writer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ protected void checkField(Field member) throws NoSuchFieldException {
}

// We default to not allowing private classes
private boolean checkClass(Class sClass) {
protected boolean checkClass(Class sClass) {
return (sClass.getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,24 @@ int taxed_value() {
assertEquals(getContents(root, "simple.txt"), sw.toString());
}

public void testSafeSimple() throws MustacheException, IOException, ExecutionException, InterruptedException {
MustacheFactory c = new SafeMustacheFactory(Collections.singleton("simple.html"), root);
Mustache m = c.compile("simple.html");
StringWriter sw = new StringWriter();
m.execute(sw, new Object() {
public String name = "Chris";
public int value = 10000;

public int taxed_value() {
return (int) (this.value - (this.value * 0.4));
}

public boolean in_ca = true;
});
assertEquals(getContents(root, "simple.txt"), sw.toString());
}


private static class LocalizedMustacheResolver extends DefaultResolver {
private final Locale locale;

Expand Down

0 comments on commit 7d9c237

Please sign in to comment.