Skip to content

Commit 2a8ef10

Browse files
committed
Don't allow jinjava constructs outside of the AllowlistGroup classes
from being allowlisted
1 parent d287c18 commit 2a8ef10

15 files changed

Lines changed: 148 additions & 117 deletions

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@
189189
<dependency>
190190
<groupId>com.google.errorprone</groupId>
191191
<artifactId>error_prone_annotations</artifactId>
192+
<scope>runtime</scope>
192193
</dependency>
193194

194195
<dependency>

src/main/java/com/hubspot/jinjava/JinjavaConfig.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
import java.time.ZoneOffset;
4949
import java.util.Locale;
5050
import java.util.function.BiConsumer;
51-
import javax.annotation.Nullable;
5251
import javax.el.ELResolver;
5352
import org.immutables.value.Value;
5453

@@ -204,17 +203,11 @@ default boolean isEnableFilterChainOptimization() {
204203
return false;
205204
}
206205

207-
@Nullable
208-
ObjectMapper getObjectMapperOrNull();
209-
210-
@Value.Derived
206+
@Value.Default
211207
default ObjectMapper getObjectMapper() {
212-
ObjectMapper objectMapper = getObjectMapperOrNull();
213-
if (objectMapper == null) {
214-
objectMapper = new ObjectMapper().registerModule(new Jdk8Module());
215-
if (getLegacyOverrides().isUseSnakeCasePropertyNaming()) {
216-
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
217-
}
208+
ObjectMapper objectMapper = new ObjectMapper().registerModule(new Jdk8Module());
209+
if (getLegacyOverrides().isUseSnakeCasePropertyNaming()) {
210+
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
218211
}
219212
return objectMapper;
220213
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
package com.hubspot.jinjava;
22

3+
import com.hubspot.immutable.collection.encoding.ImmutableListEncodingEnabled;
4+
import com.hubspot.immutable.collection.encoding.ImmutableMapEncodingEnabled;
5+
import com.hubspot.immutable.collection.encoding.ImmutableSetEncodingEnabled;
36
import org.immutables.value.Value;
47

58
@Value.Style(
69
init = "set*",
710
get = { "is*", "get*" } // Detect 'get' and 'is' prefixes in accessor methods
811
)
12+
@ImmutableSetEncodingEnabled
13+
@ImmutableListEncodingEnabled
14+
@ImmutableMapEncodingEnabled
915
public @interface JinjavaImmutableStyle {
1016
@Value.Style(
1117
init = "with*",
1218
get = { "is*", "get*" } // Detect 'get' and 'is' prefixes in accessor methods
1319
)
20+
@ImmutableSetEncodingEnabled
21+
@ImmutableListEncodingEnabled
22+
@ImmutableMapEncodingEnabled
1423
@interface WithStyle {
1524
}
1625
}

src/main/java/com/hubspot/jinjava/LegacyOverrides.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
package com.hubspot.jinjava;
22

3-
import com.hubspot.immutable.collection.encoding.ImmutableListEncodingEnabled;
4-
import com.hubspot.immutable.collection.encoding.ImmutableMapEncodingEnabled;
5-
import com.hubspot.immutable.collection.encoding.ImmutableSetEncodingEnabled;
63
import org.immutables.value.Value;
74

85
@Value.Immutable(singleton = true)
9-
@Value.Style(init = "with*", get = { "is*", "get*" })
10-
@ImmutableSetEncodingEnabled
11-
@ImmutableListEncodingEnabled
12-
@ImmutableMapEncodingEnabled
6+
@JinjavaImmutableStyle.WithStyle
137
public interface LegacyOverrides extends WithLegacyOverrides {
148
LegacyOverrides NONE = new Builder().build();
159
LegacyOverrides THREE_POINT_0 = new Builder()

src/main/java/com/hubspot/jinjava/el/ext/AllowlistGroup.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.hubspot.jinjava.util.ForLoop;
2525
import java.lang.reflect.Method;
2626
import java.math.BigDecimal;
27+
import java.time.ZonedDateTime;
2728
import java.util.ArrayList;
2829
import java.util.LinkedHashMap;
2930
import java.util.Map;
@@ -150,7 +151,19 @@ String[] allowedReturnTypeCanonicalClassPrefixes() {
150151
return ARRAY;
151152
}
152153
},
153-
JinjavaFunctions,
154+
JinjavaFunctions {
155+
private static final String[] ARRAY = { ZonedDateTime.class.getCanonicalName() };
156+
157+
@Override
158+
String[] allowedDeclaredMethodsFromClasses() {
159+
return ARRAY;
160+
}
161+
162+
@Override
163+
String[] allowedReturnTypeClasses() {
164+
return ARRAY;
165+
}
166+
},
154167
JinjavaExpTests {
155168
private static final String[] ARRAY = { ExpTest.class.getPackageName() };
156169

src/main/java/com/hubspot/jinjava/el/ext/AllowlistMethodValidator.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.google.common.collect.ImmutableSet;
55
import java.lang.reflect.Method;
66
import java.util.concurrent.ConcurrentHashMap;
7+
import java.util.function.Consumer;
78

89
public final class AllowlistMethodValidator {
910

@@ -14,6 +15,7 @@ public final class AllowlistMethodValidator {
1415
private final ImmutableSet<Method> allowedMethods;
1516
private final ImmutableSet<String> allowedDeclaredMethodsFromCanonicalClassPrefixes;
1617
private final ImmutableSet<String> allowedDeclaredMethodsFromCanonicalClassNames;
18+
private final Consumer<Method> onRejectedMethod;
1719
private final ImmutableList<MethodValidator> additionalValidators;
1820

1921
public static AllowlistMethodValidator create(
@@ -35,6 +37,7 @@ private AllowlistMethodValidator(
3537
methodValidatorConfig.allowedDeclaredMethodsFromCanonicalClassPrefixes();
3638
this.allowedDeclaredMethodsFromCanonicalClassNames =
3739
methodValidatorConfig.allowedDeclaredMethodsFromCanonicalClassNames();
40+
this.onRejectedMethod = methodValidatorConfig.onRejectedMethod();
3841
this.additionalValidators = additionalValidators;
3942
this.allowedMethodsCache = new ConcurrentHashMap<>();
4043
}
@@ -58,11 +61,12 @@ public Method validateMethod(Method m) {
5861
}
5962
);
6063
if (!isAllowedMethod) {
64+
onRejectedMethod.accept(m);
6165
return null;
6266
}
6367
for (MethodValidator v : additionalValidators) {
64-
m = v.validateMethod(m);
65-
if (m == null) {
68+
if (v.validateMethod(m) == null) {
69+
onRejectedMethod.accept(m);
6670
return null;
6771
}
6872
}

src/main/java/com/hubspot/jinjava/el/ext/AllowlistReturnTypeValidator.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.common.collect.ImmutableList;
44
import com.google.common.collect.ImmutableSet;
55
import java.util.concurrent.ConcurrentHashMap;
6+
import java.util.function.Consumer;
67

78
public final class AllowlistReturnTypeValidator {
89

@@ -15,6 +16,7 @@ public final class AllowlistReturnTypeValidator {
1516
private final ImmutableSet<String> allowedCanonicalClassPrefixes;
1617
private final ImmutableSet<String> allowedCanonicalClassNames;
1718
private final boolean allowArrays;
19+
private final Consumer<Class<?>> onRejectedClass;
1820
private final ImmutableList<ReturnTypeValidator> additionalValidators;
1921

2022
public static AllowlistReturnTypeValidator create(
@@ -36,6 +38,7 @@ private AllowlistReturnTypeValidator(
3638
this.allowedCanonicalClassNames =
3739
returnTypeValidatorConfig.allowedCanonicalClassNames();
3840
this.allowArrays = returnTypeValidatorConfig.allowArrays();
41+
this.onRejectedClass = returnTypeValidatorConfig.onRejectedClass();
3942
this.additionalValidators = additionalValidators;
4043
this.allowedReturnTypesCache = new ConcurrentHashMap<>();
4144
}
@@ -56,11 +59,12 @@ public Object validateReturnType(Object o) {
5659
allowedCanonicalClassPrefixes.stream().anyMatch(canonicalClassName::startsWith)
5760
);
5861
if (!isAllowedClassName) {
62+
onRejectedClass.accept(clazz);
5963
return null;
6064
}
6165
for (ReturnTypeValidator v : additionalValidators) {
62-
o = v.validateReturnType(o);
63-
if (o == null) {
66+
if (v.validateReturnType(o) == null) {
67+
onRejectedClass.accept(clazz);
6468
return null;
6569
}
6670
}
@@ -79,10 +83,12 @@ public boolean allowReturnTypeClass(Class<?> clazz) {
7983
allowedCanonicalClassPrefixes.stream().anyMatch(canonicalClassName::startsWith)
8084
);
8185
if (!isAllowedReturnType) {
86+
onRejectedClass.accept(clazz);
8287
return false;
8388
}
8489
for (ReturnTypeValidator v : additionalValidators) {
8590
if (!v.allowReturnTypeClass(clazz)) {
91+
onRejectedClass.accept(clazz);
8692
return false;
8793
}
8894
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.hubspot.jinjava.el.ext;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.google.common.collect.ImmutableSet;
5+
import java.lang.reflect.Method;
6+
import java.util.Arrays;
7+
import java.util.List;
8+
import java.util.Set;
9+
import java.util.stream.Stream;
10+
import org.immutables.value.Value;
11+
12+
public class BannedAllowlistOptions {
13+
14+
// These aren't required, but they prevent someone from misconfiguring Jinjava to allow sandbox bypass unintentionally
15+
private static final String JAVA_LANG_REFLECT_PACKAGE =
16+
Method.class.getPackage().getName(); // java.lang.reflect
17+
private static final String JACKSON_DATABIND_PACKAGE =
18+
ObjectMapper.class.getPackage().getName(); // com.fasterxml.jackson.databind
19+
20+
private static final String[] BANNED_PREFIXES = {
21+
Class.class.getCanonicalName(),
22+
Object.class.getCanonicalName(),
23+
JAVA_LANG_REFLECT_PACKAGE,
24+
JACKSON_DATABIND_PACKAGE,
25+
};
26+
27+
private static final Set<String> ALLOWED_JINJAVA_PREFIXES = Stream
28+
.concat(
29+
Stream.of("com.hubspot.jinjava.testobjects"),
30+
Arrays
31+
.stream(AllowlistGroup.values())
32+
.flatMap(g ->
33+
Stream
34+
.of(
35+
g.allowedDeclaredMethodsFromCanonicalClassPrefixes(),
36+
g.allowedReturnTypeCanonicalClassPrefixes(),
37+
g.allowedDeclaredMethodsFromClasses(),
38+
g.allowedReturnTypeClasses()
39+
)
40+
.flatMap(Arrays::stream)
41+
)
42+
)
43+
.collect(ImmutableSet.toImmutableSet());
44+
45+
@Value.Check
46+
public static List<String> findBannedPrefixes(Stream<String> prefixes) {
47+
return prefixes
48+
.filter(prefixOrName ->
49+
Arrays
50+
.stream(BANNED_PREFIXES)
51+
.anyMatch(banned ->
52+
banned.startsWith(prefixOrName) || prefixOrName.startsWith(banned)
53+
) ||
54+
isIllegalJinjavaClass(prefixOrName)
55+
)
56+
.toList();
57+
}
58+
59+
private static boolean isIllegalJinjavaClass(String prefixOrName) {
60+
if (!prefixOrName.startsWith("com.hubspot.jinjava")) {
61+
return false;
62+
}
63+
// e.g. com.hubspot.jinjava.lib.exptest is allowed, but com.hubspot.jinjava.Jinjava will not be
64+
return ALLOWED_JINJAVA_PREFIXES.stream().noneMatch(prefixOrName::startsWith);
65+
}
66+
}

src/main/java/com/hubspot/jinjava/el/ext/ExtendedParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public AstNode createAstNode(AstNode... children) {
129129
}
130130

131131
protected AstNode interpreter() {
132-
return identifier(INTERPRETER);
132+
return new AstNull();
133133
}
134134

135135
@Override
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.hubspot.jinjava.el.ext;
22

33
import java.lang.reflect.Method;
4+
import javax.annotation.Nullable;
45

56
public interface MethodValidator {
7+
@Nullable
68
Method validateMethod(Method m);
79
}

0 commit comments

Comments
 (0)