Repository: PebbleTemplates/pebble
Pull request #329 (2018-04-13 08:07:34)

restricting an access to class/getClass attribute
added restriction so that you can't access class/getClass attribute from an object, e.g.: `"{{ object.class }}"`. By having an access to it you can execute a shell command.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 843cb681..9faced1e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@
 - Fix performance regression issues (#326, #328)
 - Add Reverse filter (#315) 
 - Make "loop.length" and "loop.revindex" be lazy evaluated (#279)
+- Fixed security issue which allowed to execute shell command (by having access to Java's Class object)
 
 ## v2.4.0 (2017-06-04)
 - Add arrays support for iterable test (#254)
diff --git a/src/main/java/com/mitchellbosecke/pebble/error/ClassAccessException.java b/src/main/java/com/mitchellbosecke/pebble/error/ClassAccessException.java
new file mode 100644
index 00000000..f4aa65e8
--- /dev/null
+++ b/src/main/java/com/mitchellbosecke/pebble/error/ClassAccessException.java
@@ -0,0 +1,18 @@
+/*******************************************************************************
+ * This file is part of Pebble.
+ *
+ * Copyright (c) 2014 by Mitchell Bösecke
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ ******************************************************************************/
+package com.mitchellbosecke.pebble.error;
+
+public class ClassAccessException extends PebbleException {
+
+    private static final long serialVersionUID = 5109892021088141417L;
+
+    public ClassAccessException(Integer lineNumber, String filename) {
+        super(null, "For security reasons access to class/getClass attribute is denied.", lineNumber, filename);
+    }
+}
diff --git a/src/main/java/com/mitchellbosecke/pebble/node/expression/GetAttributeExpression.java b/src/main/java/com/mitchellbosecke/pebble/node/expression/GetAttributeExpression.java
index 1501ebb1..55ca9659 100644
--- a/src/main/java/com/mitchellbosecke/pebble/node/expression/GetAttributeExpression.java
+++ b/src/main/java/com/mitchellbosecke/pebble/node/expression/GetAttributeExpression.java
@@ -15,6 +15,7 @@
 package com.mitchellbosecke.pebble.node.expression;
 
 import com.mitchellbosecke.pebble.error.AttributeNotFoundException;
+import com.mitchellbosecke.pebble.error.ClassAccessException;
 import com.mitchellbosecke.pebble.error.PebbleException;
 import com.mitchellbosecke.pebble.error.RootAttributeNotFoundException;
 import com.mitchellbosecke.pebble.extension.DynamicAttributeProvider;
@@ -195,9 +196,13 @@ public class GetAttributeExpression implements Expression<Object> {
                 }
 
             } else {
-                throw new AttributeNotFoundException(null, String.format(
-                        "Attribute [%s] of [%s] does not exist or can not be accessed and strict variables is set to true.",
-                        attributeName, object.getClass().getName()), attributeName, this.lineNumber, this.filename);
+                if (attributeName.equals("class") || attributeName.equals("getClass")) {
+                    throw new ClassAccessException(this.lineNumber, this.filename);
+                } else {
+                    throw new AttributeNotFoundException(null, String.format(
+                            "Attribute [%s] of [%s] does not exist or can not be accessed and strict variables is set to true.",
+                            attributeName, object.getClass().getName()), attributeName, this.lineNumber, this.filename);
+                }
             }
         }
         return result;
@@ -347,6 +352,10 @@ public class GetAttributeExpression implements Expression<Object> {
      * @return
      */
     private Method findMethod(Class<?> clazz, String name, Class<?>[] requiredTypes) {
+        if (name.equals("getClass")) {
+            return null;
+        }
+
         Method result = null;
 
         Method[] candidates = clazz.getMethods();


Test patch:
diff --git a/src/test/java/com/mitchellbosecke/pebble/GetAttributeTest.java b/src/test/java/com/mitchellbosecke/pebble/GetAttributeTest.java
index 1c1deb99..9e9440a5 100644
--- a/src/test/java/com/mitchellbosecke/pebble/GetAttributeTest.java
+++ b/src/test/java/com/mitchellbosecke/pebble/GetAttributeTest.java
@@ -9,6 +9,7 @@
 package com.mitchellbosecke.pebble;
 
 import com.mitchellbosecke.pebble.error.AttributeNotFoundException;
+import com.mitchellbosecke.pebble.error.ClassAccessException;
 import com.mitchellbosecke.pebble.error.PebbleException;
 import com.mitchellbosecke.pebble.error.RootAttributeNotFoundException;
 import com.mitchellbosecke.pebble.extension.DynamicAttributeProvider;
@@ -111,6 +112,32 @@ public class GetAttributeTest extends AbstractTest {
         assertEquals("hello Steve", writer.toString());
     }
 
+    @Test
+    public void testMethodAttributeWhenAccessingClassWithStrictVariableOff() throws PebbleException, IOException {
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();
+
+        PebbleTemplate template = pebble.getTemplate("hello {{ object.class }}");
+        Map<String, Object> context = new HashMap<>();
+        context.put("object", "some string");
+
+        Writer writer = new StringWriter();
+        template.evaluate(writer, context);
+        assertEquals("hello ", writer.toString());
+    }
+
+    @Test(expected = ClassAccessException.class)
+    public void testMethodAttributeWhenAccessingClassWithStrictVariableOn() throws PebbleException, IOException {
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(true).build();
+
+        PebbleTemplate template = pebble.getTemplate("hello {{ object.class }}");
+        Map<String, Object> context = new HashMap<>();
+        context.put("object", "some string");
+
+        Writer writer = new StringWriter();
+        template.evaluate(writer, context);
+        assertEquals("hello ", writer.toString());
+    }
+
     /**
      * The GetAttribute expression involves caching, we test with different
      * objects to make sure that the caching doesnt have any negative side
@@ -557,7 +584,7 @@ public class GetAttributeTest extends AbstractTest {
         PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(true).build();
 
         PebbleTemplate template = pebble.getTemplate("{{ obj.getStringFromLong(1) }} {{ obj.getStringFromLongs(1,2) }}"
-            + " {{ obj.getStringFromBoolean(true) }}");
+                + " {{ obj.getStringFromBoolean(true) }}");
 
         Map<String, Object> context = new HashMap<>();
         context.put("obj", new PrimitiveArguments());