Skip to content

Commit

Permalink
ISSUE-264: add tests for all the disallowed behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
spullara committed May 11, 2021
1 parent 7efecb8 commit c17bb11
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,98 +11,97 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

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

public class SafeMustacheFactory extends DefaultMustacheFactory {

private final static Set<String> disallowedMethods = new HashSet<>(Arrays.asList(
"getClass",
"hashCode",
"clone",
"toString",
"notify",
"notifyAll",
"finalize",
"wait"
));
private final static Set<String> disallowedMethods = new HashSet<>(Arrays.asList(
"getClass",
"hashCode",
"clone",
"toString",
"notify",
"notifyAll",
"finalize",
"wait"
));

// Only allow public access
public static final SimpleObjectHandler OBJECT_HANDLER = new SimpleObjectHandler() {
@Override
protected void checkMethod(Method member) throws NoSuchMethodException {
if (disallowedMethods.contains(member.getName())) {
throw new MustacheException("Disallowed: method " + member.getName() + " cannot be accessed");
}
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();
// Only allow public access
public static final SimpleObjectHandler OBJECT_HANDLER = new SimpleObjectHandler() {
@Override
protected void checkMethod(Method member) throws NoSuchMethodException {
if (disallowedMethods.contains(member.getName())) {
throw new MustacheException("Disallowed: method " + member.getName() + " cannot be accessed");
}
if ((member.getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC) {
throw new NoSuchMethodException("Only public members allowed");
}
}

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();
@Override
protected void checkField(Field member) throws NoSuchFieldException {
if ((member.getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC) {
throw new NoSuchFieldException("Only public members allowed");
}
}
};

private void setup() {
setObjectHandler(OBJECT_HANDLER);
mc.setAllowChangingDelimeters(false);
}
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();
}

@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");
}
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();
}

@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));
}
};
}
private void setup() {
setObjectHandler(OBJECT_HANDLER);
mc.setAllowChangingDelimeters(false);
}

@Override
public void encode(String value, Writer writer) {
escape(value, writer);
}
@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);
}
}
103 changes: 103 additions & 0 deletions compiler/src/test/java/com/github/mustachejava/InterpreterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,109 @@ public int taxed_value() {
assertEquals(getContents(root, "simple.txt"), sw.toString());
}

public void testSafe() throws MustacheException, IOException, ExecutionException, InterruptedException {
try {
MustacheFactory c = new SafeMustacheFactory(Collections.singleton("notsimple.html"), root);
// Not present in allowed list
Mustache m = c.compile("simple.html");
fail("Should not be allowed");
} catch (MustacheException me) {
// Success
}
MustacheFactory c = new SafeMustacheFactory(Collections.singleton("simple.html"), root);
Mustache m = c.compile("simple.html");
{
StringWriter sw = new StringWriter();
m.execute(sw, new Object() {
// It won't find this since it isn't 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;
});
assertNotSame(getContents(root, "simple.txt"), sw.toString());
}
{
StringWriter sw = new StringWriter();
m.execute(sw, new Object() {
public String name = "Chris";
public int value = 10000;

// It won't find this since it isn't public
int taxed_value() {
return (int) (this.value - (this.value * 0.4));
}

public boolean in_ca = true;
});
assertNotSame(getContents(root, "simple.txt"), sw.toString());
}
{
m = c.compile(new StringReader("{{toString}}"), "test");
StringWriter sw = new StringWriter();
try {
m.execute(sw, new Object() {
public String toString() {
return "";
}
});
fail("Got value for toString");
} catch (MustacheException me) {
// Success
}
}
{
try {
m = c.compile(new StringReader("{{>/etc/passwd}}"), "test");
StringWriter sw = new StringWriter();
m.execute(sw, new Object() {
});
fail("Can't access that partial");
} catch (MustacheException me) {
// Success
}
}
{
try {
m = c.compile(new StringReader("{{%pragma}}"), "test");
StringWriter sw = new StringWriter();
m.execute(sw, new Object() {
});
fail("Can't use pragmas");
} catch (MustacheException me) {
// Success
}
}
{
try {
m = c.compile(new StringReader("{{{rawtext}}}"), "test");
StringWriter sw = new StringWriter();
m.execute(sw, new Object() {
public String rawtext() {
return "";
}
});
fail("Can't use raw text");
} catch (MustacheException me) {
// Success
}
}
{
try {
m = c.compile(new StringReader("{{=[[]]=}}"), "test");
StringWriter sw = new StringWriter();
m.execute(sw, new Object() {
});
fail("Can't change delimiters");
} catch (MustacheException me) {
// Success
}
}
}

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

0 comments on commit c17bb11

Please sign in to comment.