Skip to content

Commit 25e449d

Browse files
committed
Fixed: Prevent deserialization of untrusted data in JMS listener
- Restrict XStream allowlist in UtilXml to the exact types handled by XmlSerializer.serializeSingle - Replace the broad java..* and org.apache.ofbiz..* wildcards in SafeObjectInputStream with an explicit minimal allowlist of the same safe types - Move the isExport() authorization check before XmlSerializer.deserialize in AbstractJmsListener.runService so gadget chains cannot fire for non-exported or unknown services
1 parent 2a8fd65 commit 25e449d

4 files changed

Lines changed: 57 additions & 22 deletions

File tree

framework/base/src/main/java/org/apache/ofbiz/base/util/SafeObjectInputStream.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,35 @@
3838
*/
3939
public final class SafeObjectInputStream extends ObjectInputStream {
4040
private static final String[] DEFAULT_ALLOWLIST_PATTERN = {
41-
"byte\\[\\]", "foo", "SerializationInjector",
41+
"byte\\[\\]",
4242
"\\[Z", "\\[B", "\\[S", "\\[I", "\\[J", "\\[F", "\\[D", "\\[C",
43-
"java..*", "sun.util.calendar..*", "org.apache.ofbiz..*",
44-
"org.codehaus.groovy.runtime.GStringImpl", "groovy.lang.GString"};
43+
"java\\.lang\\.String",
44+
"java\\.lang\\.Number",
45+
"java\\.lang\\.Integer",
46+
"java\\.lang\\.Long",
47+
"java\\.lang\\.Float",
48+
"java\\.lang\\.Double",
49+
"java\\.lang\\.Boolean",
50+
"java\\.util\\.Locale",
51+
"java\\.math\\.BigDecimal",
52+
"java\\.sql\\.Timestamp",
53+
"java\\.sql\\.Date",
54+
"java\\.sql\\.Time",
55+
"java\\.util\\.Date",
56+
"java\\.util\\.ArrayList",
57+
"java\\.util\\.LinkedList",
58+
"java\\.util\\.Stack",
59+
"java\\.util\\.Vector",
60+
"java\\.util\\.TreeSet",
61+
"java\\.util\\.HashSet",
62+
"java\\.util\\.HashMap",
63+
"java\\.util\\.Properties",
64+
"java\\.util\\.Hashtable",
65+
"java\\.util\\.WeakHashMap",
66+
"java\\.util\\.TreeMap",
67+
"java\\.util\\.Arrays\\$ArrayList",
68+
"org\\.apache\\.ofbiz\\.entity\\.GenericValue",
69+
"org\\.apache\\.ofbiz\\.entity\\.GenericPK"};
4570
private static final String[] DEFAULT_DENYLIST = {"rmi", "<"};
4671

4772
/** The regular expression used to match serialized types. */

framework/base/src/main/java/org/apache/ofbiz/base/util/UtilXml.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,21 @@ private UtilXml() { }
9191

9292
private static XStream createXStream() {
9393
XStream xstream = new XStream();
94-
/* This method is a pure helper method for XStream 1.4.x.
95-
* It initializes an XStream instance with a white list of well-known and simple types of the Java runtime
96-
* as it is done in XStream 1.5.x by default. This method will do therefore nothing in XStream 1.5
97-
* and could be removed them
98-
*/
99-
// XStream.setupDefaultSecurity(xstream);
100-
/* You may want to enhance the white list created by XStream::setupDefaultSecurity (or by default with XStream 1.5)
101-
* using xstream::allowTypesByWildcard with your own classes
102-
*/
94+
// Allow only the concrete types that XmlSerializer.serializeSingle handles explicitly.
95+
// All other types are blocked to prevent deserialization gadget chain attacks.
96+
// Class names are used as strings to avoid a compile-time dependency on framework/entity.
97+
xstream.allowTypes(new String[]{
98+
"java.lang.String", "java.lang.Integer", "java.lang.Long",
99+
"java.lang.Float", "java.lang.Double", "java.lang.Boolean",
100+
"java.util.Locale", "java.math.BigDecimal",
101+
"java.sql.Timestamp", "java.sql.Date", "java.sql.Time", "java.util.Date",
102+
"java.util.ArrayList", "java.util.LinkedList", "java.util.Stack",
103+
"java.util.Vector", "java.util.TreeSet", "java.util.HashSet",
104+
"java.util.HashMap", "java.util.Properties", "java.util.Hashtable",
105+
"java.util.WeakHashMap", "java.util.TreeMap",
106+
"org.apache.ofbiz.entity.GenericValue",
107+
"org.apache.ofbiz.entity.GenericPK"
108+
});
103109
return xstream;
104110
}
105111

framework/base/src/test/java/org/apache/ofbiz/base/util/UtilObjectTests.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,11 @@ public void testGetObject() {
216216
assertNotNull("groovySerializableBytes", groovySerializableBytes);
217217
assertNull("groovyDeserializable", UtilObject.getObject(groovySerializableBytes));
218218

219+
// SerializationInjector is a test-only class; allow it explicitly for this assertion.
220+
// Note: the allowList value is treated as a regex, so '.' is used instead of '$'
221+
// (dot matches any character including the inner-class separator '$').
222+
UtilProperties.setPropertyValueInMemory("SafeObjectInputStream", "allowList",
223+
"org.apache.ofbiz.base.util.UtilObjectTests.SerializationInjector");
219224
byte[] injectorBytes = UtilObject.getBytes(new SerializationInjector(false, false));
220225
assertNotNull("injectorBytes good", injectorBytes);
221226
assertNotNull("injector good", UtilObject.getObject(injectorBytes));

framework/service/src/main/java/org/apache/ofbiz/service/jms/AbstractJmsListener.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ protected Map<String, Object> runService(MapMessage message) {
7171
return null;
7272
}
7373

74+
// Authorization check BEFORE deserialization to prevent gadget chain attacks.
75+
ModelService model = dispatcher.getDispatchContext().getModelService(serviceName);
76+
if (!model.isExport()) {
77+
Debug.logWarning("Attempt to invoke a non-exported service: " + serviceName, MODULE);
78+
return null;
79+
}
80+
7481
Object o = XmlSerializer.deserialize(xmlContext, dispatcher.getDelegator());
7582

7683
if (Debug.verboseOn()) {
@@ -81,20 +88,12 @@ protected Map<String, Object> runService(MapMessage message) {
8188
}
8289
} catch (JMSException je) {
8390
Debug.logError(je, "Problems reading message.", MODULE);
91+
} catch (GenericServiceException e) {
92+
Debug.logError(e, "Unable to get ModelService for service: " + serviceName, MODULE);
8493
} catch (Exception e) {
8594
Debug.logError(e, "Problems deserializing the service context.", MODULE);
8695
}
8796

88-
try {
89-
ModelService model = dispatcher.getDispatchContext().getModelService(serviceName);
90-
if (!model.isExport()) {
91-
Debug.logWarning("Attempt to invoke a non-exported service: " + serviceName, MODULE);
92-
return null;
93-
}
94-
} catch (GenericServiceException e) {
95-
Debug.logError(e, "Unable to get ModelService for service : " + serviceName, MODULE);
96-
}
97-
9897
if (Debug.verboseOn()) {
9998
Debug.logVerbose("Running service: " + serviceName, MODULE);
10099
}

0 commit comments

Comments
 (0)