CVE-2018-21234 (published 2020-05-21T23:15Z)

Jodd before 5.0.4 performs Deserialization of Untrusted JSON Data when setClassMetadataName is set.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-502
cwe_name: Deserialization of Untrusted Data
description: The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
extended_description: 
            It is often convenient to serialize objects for communication or to save them for later use. However, deserialized data or code can often be modified without using the provided accessor functions if it does not use cryptography to protect itself. Furthermore, any cryptography would still be client-side security -- which is a dangerous security assumption.
            Data that is untrusted can not be trusted to be well-formed.
            When developers place no restrictions on "gadget chains," or series of instances and method invocations that can self-execute during the deserialization process (i.e., before the object is returned to the caller), it is sometimes possible for attackers to leverage them to perform unauthorized actions, like generating a shell.
         
         

Fix information:
repo_url: https://github.com/oblac/jodd
commit_details: repo_url: https://github.com/oblac/jodd
author: Igor Spasić
author_date: 2018-08-17 18:32:20+02:00
msg: Added `allowClass` (closes #628)
repository: repo_url: https://github.com/oblac/jodd
repo_name: oblac/jodd
description: Jodd! Lightweight. Java. Zero dependencies. Use what you like.
repo_language: Java
owner: oblac
file_changes: filename: CHANGELOG.md
change_type: ModificationType.MODIFY
diff: @@ -24,6 +24,7 @@ All notable changes to Jodd project are documented here.
 + **mail** - added custom properties.
 + **json** - added `onValue` callback for JSON serializer.
 + **json** - added `excludeEmpty` flag for JSON serializer.
++ **json** - added `allowClass` for whitelisting class names.
 + **petite** - allow injection in the private fields of super types.
 
 

programming_language: Markdown
filename: JsonParser.java
change_type: ModificationType.MODIFY
diff: @@ -36,6 +36,7 @@ import jodd.util.StringPool;
 import jodd.util.UnsafeUtil;
 
 import java.math.BigInteger;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -307,6 +308,26 @@ public class JsonParser extends JsonParserBase {
 		return this;
 	}
 
+	/**
+	 * Adds a {@link jodd.util.Wildcard wildcard} pattern for white-listing classes.
+	 * @see #setClassMetadataName(String)
+	 */
+	public JsonParser allowClass(final String classPattern) {
+		if (super.classnameWhitelist == null) {
+			super.classnameWhitelist = new ArrayList<>();
+		}
+		classnameWhitelist.add(classPattern);
+		return this;
+	}
+
+	/**
+	 * Removes the whitelist of allowed classes.
+	 * @see #setClassMetadataName(String)
+	 */
+	public JsonParser allowAllClasses() {
+		classnameWhitelist = null;
+		return this;
+	}
 
 	// ---------------------------------------------------------------- parse
 

programming_language: Java
filename: JsonParserBase.java
change_type: ModificationType.MODIFY
diff: @@ -55,6 +55,7 @@ public abstract class JsonParserBase {
 
 	protected Supplier<Map> mapSupplier = HASMAP_SUPPLIER;
 	protected Supplier<List> listSupplier = ARRAYLIST_SUPPLIER;
+	protected List<String> classnameWhitelist;
 
 	/**
 	 * Creates new instance of {@link jodd.json.MapToBean}.

programming_language: Java
filename: MapToBean.java
change_type: ModificationType.MODIFY
diff: @@ -32,6 +32,7 @@ import jodd.introspector.Setter;
 import jodd.typeconverter.TypeConverterManager;
 import jodd.util.ClassLoaderUtil;
 import jodd.util.ClassUtil;
+import jodd.util.Wildcard;
 
 import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
@@ -69,6 +70,8 @@ public class MapToBean {
 			}
 		}
 		else {
+			checkClassName(jsonParser.classnameWhitelist, className);
+
 			try {
 				targetType = ClassLoaderUtil.loadClass(className);
 			} catch (ClassNotFoundException cnfex) {
@@ -145,6 +148,17 @@ public class MapToBean {
 		return target;
 	}
 
+	private void checkClassName(final List<String> classnameWhitelist, final String className) {
+		if (classnameWhitelist == null) {
+			return;
+		}
+		classnameWhitelist.forEach(pattern -> {
+			if (!Wildcard.equalsOrMatch(className, pattern)) {
+				throw new JsonException("Class can't be loaded as it is not whitelisted: " + className);
+			}
+		});
+	}
+
 	/**
 	 * Converts type of all list elements to match the component type.
 	 */

programming_language: Java
filename: JSONDeserializerTest.java
change_type: ModificationType.MODIFY
diff: @@ -62,6 +62,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
@@ -709,6 +710,37 @@ class JSONDeserializerTest {
 		});
 	}
 
+	@Test
+	void testPointWithException() {
+		JsonParser.Defaults.classMetadataName = "__class";
+		JsonSerializer.Defaults.classMetadataName = "__class";
+
+		JsonParsers.forEachParser(jsonParser -> {
+			jsonParser.allowClass("notAllowed");
+			final String json = new JsonSerializer().serialize(new Point2D.Float(1.0f, 2.0f));
+			assertThrows(JsonException.class, () -> {
+				jsonParser.parse(json);
+			});
+			jsonParser.allowAllClasses();
+		});
+	}
+
+	@Test
+	void testPointWithoutExceptionWhitelisted() {
+		JsonParser.Defaults.classMetadataName = "__class";
+		JsonSerializer.Defaults.classMetadataName = "__class";
+
+		JsonParsers.forEachParser(jsonParser -> {
+			jsonParser.allowClass("*.Point?D*");
+			String json = new JsonSerializer().serialize(new Point2D.Float(1.0f, 2.0f));
+			Point2D.Float point = jsonParser.parse(json);
+			assertEquals(1.0f, point.x, DELTA);
+			assertEquals(2.0f, point.y, DELTA);
+			jsonParser.allowAllClasses();
+		});
+	}
+
+
 	@Test
 	void testUnixEpoch() {
 		JsonParsers.forEachParser(jsonParser -> {

programming_language: Java