From fd220124926ff0c6e760d5331eb62e09b20b46a9 Mon Sep 17 00:00:00 2001
From: Marian Koncek <mkoncek@redhat.com>
Date: Wed, 18 Dec 2019 16:01:16 +0100
Subject: [PATCH] CVE-2014-0114
---
.../beanutils/BeanIntrospectionData.java | 154 +++++++++++++
.../commons/beanutils/BeanIntrospector.java | 53 +++++
.../beanutils/DefaultBeanIntrospector.java | 181 +++++++++++++++
.../DefaultIntrospectionContext.java | 108 +++++++++
.../beanutils/IntrospectionContext.java | 99 ++++++++
.../commons/beanutils/PropertyUtils.java | 15 ++
.../commons/beanutils/PropertyUtilsBean.java | 213 +++++++++---------
.../SuppressPropertiesBeanIntrospector.java | 75 ++++++
...essPropertiesBeanIntrospectorTestCase.java | 127 +++++++++++
.../beanutils/bugs/Jira463TestCase.java | 48 ++++
.../beanutils/bugs/Jira520TestCase.java | 39 ++++
11 files changed, 1000 insertions(+), 112 deletions(-)
create mode 100644 src/main/java/org/apache/commons/beanutils/BeanIntrospectionData.java
create mode 100644 src/main/java/org/apache/commons/beanutils/BeanIntrospector.java
create mode 100644 src/main/java/org/apache/commons/beanutils/DefaultBeanIntrospector.java
create mode 100644 src/main/java/org/apache/commons/beanutils/DefaultIntrospectionContext.java
create mode 100644 src/main/java/org/apache/commons/beanutils/IntrospectionContext.java
create mode 100644 src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java
create mode 100644 src/test/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospectorTestCase.java
create mode 100644 src/test/java/org/apache/commons/beanutils/bugs/Jira463TestCase.java
create mode 100644 src/test/java/org/apache/commons/beanutils/bugs/Jira520TestCase.java
diff --git a/src/main/java/org/apache/commons/beanutils/BeanIntrospectionData.java b/src/main/java/org/apache/commons/beanutils/BeanIntrospectionData.java
new file mode 100644
index 0000000..1ab1d1e
--- /dev/null
+++ b/src/main/java/org/apache/commons/beanutils/BeanIntrospectionData.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils;
+
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <p>
+ * An internally used helper class for storing introspection information about a bean
+ * class.
+ * </p>
+ * <p>
+ * This class is used by {@link PropertyUtilsBean}. When accessing bean properties via
+ * reflection information about the properties available and their types and access
+ * methods must be present. {@code PropertyUtilsBean} stores this information in a cache
+ * so that it can be accessed quickly. The cache stores instances of this class.
+ * </p>
+ * <p>
+ * This class mainly stores information about the properties of a bean class. Per default,
+ * this is contained in {@code PropertyDescriptor} objects. Some additional information
+ * required by the {@code BeanUtils} library is also stored here.
+ * </p>
+ *
+ * @version $Id$
+ * @since 1.9.1
+ */
+class BeanIntrospectionData {
+ /** An array with property descriptors for the managed bean class. */
+ private final PropertyDescriptor[] descriptors;
+
+ /** A map for remembering the write method names for properties. */
+ private final Map writeMethodNames;
+
+ /**
+ * Creates a new instance of {@code BeanIntrospectionData} and initializes its
+ * completely.
+ *
+ * @param descs the array with the descriptors of the available properties
+ */
+ public BeanIntrospectionData(final PropertyDescriptor[] descs) {
+ this(descs, setUpWriteMethodNames(descs));
+ }
+
+ /**
+ * Creates a new instance of {@code BeanIntrospectionData} and allows setting the map
+ * with write method names. This constructor is mainly used for testing purposes.
+ *
+ * @param descs the array with the descriptors of the available properties
+ * @param writeMethNames the map with the names of write methods
+ */
+ BeanIntrospectionData(final PropertyDescriptor[] descs, final Map writeMethNames) {
+ descriptors = descs;
+ writeMethodNames = writeMethNames;
+ }
+
+ /**
+ * Returns the array with property descriptors.
+ *
+ * @return the property descriptors for the associated bean class
+ */
+ public PropertyDescriptor[] getDescriptors() {
+ return descriptors;
+ }
+
+ /**
+ * Returns the {@code PropertyDescriptor} for the property with the specified name. If
+ * this property is unknown, result is <b>null</b>.
+ *
+ * @param name the name of the property in question
+ * @return the {@code PropertyDescriptor} for this property or <b>null</b>
+ */
+ public PropertyDescriptor getDescriptor(final String name) {
+ PropertyDescriptor[] descriptors = getDescriptors();
+ for (int i = 0; i < descriptors.length; ++i) {
+ PropertyDescriptor pd = descriptors[i];
+ if (name.equals(pd.getName())) {
+ return pd;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the write method for the property determined by the given
+ * {@code PropertyDescriptor}. This information is normally available in the
+ * descriptor object itself. However, at least by the ORACLE implementation, the
+ * method is stored as a {@code SoftReference}. If this reference has been freed by
+ * the GC, it may be the case that the method cannot be obtained again. Then,
+ * additional information stored in this object is necessary to obtain the method
+ * again.
+ *
+ * @param beanCls the class of the affected bean
+ * @param desc the {@code PropertyDescriptor} of the desired property
+ * @return the write method for this property or <b>null</b> if there is none
+ */
+ public Method getWriteMethod(final Class beanCls, final PropertyDescriptor desc) {
+ Method method = desc.getWriteMethod();
+ if (method == null) {
+ final String methodName = (String) writeMethodNames.get(desc.getName());
+ if (methodName != null) {
+ method = MethodUtils.getAccessibleMethod(beanCls, methodName,
+ desc.getPropertyType());
+ if (method != null) {
+ try {
+ desc.setWriteMethod(method);
+ } catch (final IntrospectionException e) {
+ // ignore, in this case the method is not cached
+ }
+ }
+ }
+ }
+
+ return method;
+ }
+
+ /**
+ * Initializes the map with the names of the write methods for the supported
+ * properties. The method names - if defined - need to be stored separately because
+ * they may get lost when the GC claims soft references used by the
+ * {@code PropertyDescriptor} objects.
+ *
+ * @param descs the array with the descriptors of the available properties
+ * @return the map with the names of write methods for properties
+ */
+ private static Map setUpWriteMethodNames(final PropertyDescriptor[] descs) {
+ final Map methods = new HashMap();
+ for (int i = 0; i < descs.length; ++i) {
+ PropertyDescriptor pd = descs[i];
+ final Method method = pd.getWriteMethod();
+ if (method != null) {
+ methods.put(pd.getName(), method.getName());
+ }
+ }
+ return methods;
+ }
+}
diff --git a/src/main/java/org/apache/commons/beanutils/BeanIntrospector.java b/src/main/java/org/apache/commons/beanutils/BeanIntrospector.java
new file mode 100644
index 0000000..3f5545e
--- /dev/null
+++ b/src/main/java/org/apache/commons/beanutils/BeanIntrospector.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils;
+
+import java.beans.IntrospectionException;
+
+/**
+ * <p>
+ * Definition of an interface for components that can perform introspection on
+ * bean classes.
+ * </p>
+ * <p>
+ * Before {@link PropertyUtils} can be used for interaction with a specific Java
+ * class, the class's properties have to be determined. This is called
+ * <em>introspection</em> and is initiated automatically on demand.
+ * <code>PropertyUtils</code> does not perform introspection on its own, but
+ * delegates this task to one or more objects implementing this interface. This
+ * makes it possible to customize introspection which may be useful for certain
+ * code bases using non-standard conventions for accessing properties.
+ * </p>
+ *
+ * @version $Id: BeanIntrospector.java 1540359 2013-11-09 18:10:52Z oheger $
+ * @since 1.9
+ */
+public interface BeanIntrospector {
+ /**
+ * Performs introspection on a Java class. The current class to be inspected
+ * can be queried from the passed in <code>IntrospectionContext</code>
+ * object. A typical implementation has to obtain this class, determine its
+ * properties according to the rules it implements, and add them to the
+ * passed in context object.
+ *
+ * @param icontext the context object for interaction with the initiator of
+ * the introspection request
+ * @throws IntrospectionException if an error occurs during introspection
+ */
+ void introspect(IntrospectionContext icontext)
+ throws IntrospectionException;
+}
diff --git a/src/main/java/org/apache/commons/beanutils/DefaultBeanIntrospector.java b/src/main/java/org/apache/commons/beanutils/DefaultBeanIntrospector.java
new file mode 100644
index 0000000..60657d0
--- /dev/null
+++ b/src/main/java/org/apache/commons/beanutils/DefaultBeanIntrospector.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils;
+
+import java.beans.BeanInfo;
+import java.beans.IndexedPropertyDescriptor;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <p>
+ * The default {@link BeanIntrospector} implementation.
+ * </p>
+ * <p>
+ * This class implements a default bean introspection algorithm based on the JDK
+ * classes in the <code>java.beans</code> package. It discovers properties
+ * conforming to the Java Beans specification.
+ * </p>
+ * <p>
+ * This class is a singleton. The single instance can be obtained using the
+ * {@code INSTANCE} field. It does not define any state and thus can be
+ * shared by arbitrary clients. {@link PropertyUtils} per default uses this
+ * instance as its only {@code BeanIntrospector} object.
+ * </p>
+ *
+ * @version $Id$
+ * @since 1.9
+ */
+public class DefaultBeanIntrospector implements BeanIntrospector {
+ /** The singleton instance of this class. */
+ public static final BeanIntrospector INSTANCE = new DefaultBeanIntrospector();
+
+ /** Constant for argument types of a method that expects no arguments. */
+ private static final Class<?>[] EMPTY_CLASS_PARAMETERS = new Class[0];
+
+ /** Constant for arguments types of a method that expects a list argument. */
+ private static final Class<?>[] LIST_CLASS_PARAMETER = new Class[] { java.util.List.class };
+
+ /** Log instance */
+ private final Log log = LogFactory.getLog(getClass());
+
+ /**
+ * Private constructor so that no instances can be created.
+ */
+ private DefaultBeanIntrospector() {
+ }
+
+ /**
+ * Performs introspection of a specific Java class. This implementation uses
+ * the {@code java.beans.Introspector.getBeanInfo()} method to obtain
+ * all property descriptors for the current class and adds them to the
+ * passed in introspection context.
+ *
+ * @param icontext the introspection context
+ */
+ public void introspect(final IntrospectionContext icontext) {
+ BeanInfo beanInfo = null;
+ try {
+ beanInfo = Introspector.getBeanInfo(icontext.getTargetClass());
+ } catch (final IntrospectionException e) {
+ // no descriptors are added to the context
+ log.error(
+ "Error when inspecting class " + icontext.getTargetClass(),
+ e);
+ return;
+ }
+
+ PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
+ if (descriptors == null) {
+ descriptors = new PropertyDescriptor[0];
+ }
+
+ handleIndexedPropertyDescriptors(icontext.getTargetClass(),
+ descriptors);
+ icontext.addPropertyDescriptors(descriptors);
+ }
+
+ /**
+ * This method fixes an issue where IndexedPropertyDescriptor behaves
+ * differently in different versions of the JDK for 'indexed' properties
+ * which use java.util.List (rather than an array). It implements a
+ * workaround for Bug 28358. If you have a Bean with the following
+ * getters/setters for an indexed property:
+ *
+ * <pre>
+ * public List getFoo()
+ * public Object getFoo(int index)
+ * public void setFoo(List foo)
+ * public void setFoo(int index, Object foo)
+ * </pre>
+ *
+ * then the IndexedPropertyDescriptor's getReadMethod() and getWriteMethod()
+ * behave as follows:
+ * <ul>
+ * <li>JDK 1.3.1_04: returns valid Method objects from these methods.</li>
+ * <li>JDK 1.4.2_05: returns null from these methods.</li>
+ * </ul>
+ *
+ * @param beanClass the current class to be inspected
+ * @param descriptors the array with property descriptors
+ */
+ private void handleIndexedPropertyDescriptors(final Class<?> beanClass,
+ final PropertyDescriptor[] descriptors) {
+ for (final PropertyDescriptor pd : descriptors) {
+ if (pd instanceof IndexedPropertyDescriptor) {
+ final IndexedPropertyDescriptor descriptor = (IndexedPropertyDescriptor) pd;
+ final String propName = descriptor.getName().substring(0, 1)
+ .toUpperCase()
+ + descriptor.getName().substring(1);
+
+ if (descriptor.getReadMethod() == null) {
+ final String methodName = descriptor.getIndexedReadMethod() != null ? descriptor
+ .getIndexedReadMethod().getName() : "get"
+ + propName;
+ final Method readMethod = MethodUtils
+ .getMatchingAccessibleMethod(beanClass, methodName,
+ EMPTY_CLASS_PARAMETERS);
+ if (readMethod != null) {
+ try {
+ descriptor.setReadMethod(readMethod);
+ } catch (final Exception e) {
+ log.error(
+ "Error setting indexed property read method",
+ e);
+ }
+ }
+ }
+ if (descriptor.getWriteMethod() == null) {
+ final String methodName = descriptor.getIndexedWriteMethod() != null ? descriptor
+ .getIndexedWriteMethod().getName() : "set"
+ + propName;
+ Method writeMethod = MethodUtils
+ .getMatchingAccessibleMethod(beanClass, methodName,
+ LIST_CLASS_PARAMETER);
+ if (writeMethod == null) {
+ for (final Method m : beanClass.getMethods()) {
+ if (m.getName().equals(methodName)) {
+ final Class<?>[] parameterTypes = m.getParameterTypes();
+ if (parameterTypes.length == 1
+ && List.class
+ .isAssignableFrom(parameterTypes[0])) {
+ writeMethod = m;
+ break;
+ }
+ }
+ }
+ }
+ if (writeMethod != null) {
+ try {
+ descriptor.setWriteMethod(writeMethod);
+ } catch (final Exception e) {
+ log.error(
+ "Error setting indexed property write method",
+ e);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/beanutils/DefaultIntrospectionContext.java b/src/main/java/org/apache/commons/beanutils/DefaultIntrospectionContext.java
new file mode 100644
index 0000000..9171198
--- /dev/null
+++ b/src/main/java/org/apache/commons/beanutils/DefaultIntrospectionContext.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils;
+
+import java.beans.PropertyDescriptor;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>
+ * An implementation of the {@code IntrospectionContext} interface used by
+ * {@link PropertyUtilsBean} when doing introspection of a bean class.
+ * </p>
+ * <p>
+ * This class implements the methods required by the
+ * {@code IntrospectionContext} interface in a straight-forward manner
+ * based on a map. It is used internally only. It is not thread-safe.
+ * </p>
+ *
+ * @version $Id$
+ * @since 1.9
+ */
+class DefaultIntrospectionContext implements IntrospectionContext {
+ /** Constant for an empty array of property descriptors. */
+ private static final PropertyDescriptor[] EMPTY_DESCRIPTORS = new PropertyDescriptor[0];
+
+ /** The current class for introspection. */
+ private final Class currentClass;
+
+ /** A map for storing the already added property descriptors. */
+ private final Map descriptors;
+
+ /**
+ *
+ * Creates a new instance of <code>DefaultIntrospectionContext</code> and sets
+ * the current class for introspection.
+ *
+ * @param cls the current class
+ */
+ public DefaultIntrospectionContext(final Class cls) {
+ currentClass = cls;
+ descriptors = new HashMap();
+ }
+
+ public Class getTargetClass() {
+ return currentClass;
+ }
+
+ public void addPropertyDescriptor(final PropertyDescriptor desc) {
+ if (desc == null) {
+ throw new IllegalArgumentException(
+ "Property descriptor must not be null!");
+ }
+ descriptors.put(desc.getName(), desc);
+ }
+
+ public void addPropertyDescriptors(final PropertyDescriptor[] descs) {
+ if (descs == null) {
+ throw new IllegalArgumentException(
+ "Array with descriptors must not be null!");
+ }
+
+ for (int i = 0; i < descs.length; ++i) {
+ addPropertyDescriptor(descs[i]);
+ }
+ }
+
+ public boolean hasProperty(final String name) {
+ return descriptors.containsKey(name);
+ }
+
+ public PropertyDescriptor getPropertyDescriptor(final String name) {
+ return (PropertyDescriptor) descriptors.get(name);
+ }
+
+ public void removePropertyDescriptor(final String name) {
+ descriptors.remove(name);
+ }
+
+ public Set propertyNames() {
+ return descriptors.keySet();
+ }
+
+ /**
+ * Returns an array with all descriptors added to this context. This method
+ * is used to obtain the results of introspection.
+ *
+ * @return an array with all known property descriptors
+ */
+ public PropertyDescriptor[] getPropertyDescriptors() {
+ return (PropertyDescriptor[]) descriptors.values().toArray(EMPTY_DESCRIPTORS);
+ }
+}
diff --git a/src/main/java/org/apache/commons/beanutils/IntrospectionContext.java b/src/main/java/org/apache/commons/beanutils/IntrospectionContext.java
new file mode 100644
index 0000000..c48f186
--- /dev/null
+++ b/src/main/java/org/apache/commons/beanutils/IntrospectionContext.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils;
+
+import java.beans.PropertyDescriptor;
+import java.util.Set;
+
+/**
+ * <p>
+ * A context interface used during introspection for querying and setting
+ * property descriptors.
+ * </p>
+ * <p>
+ * An implementation of this interface is passed to {@link BeanIntrospector}
+ * objects during processing of a bean class. It allows the
+ * {@code BeanIntrospector} to deliver descriptors for properties it has
+ * detected. It is also possible to find out which properties have already been
+ * found by another {@code BeanIntrospector}; this allows multiple
+ * {@code BeanIntrospector} instances to collaborate.
+ * </p>
+ *
+ * @version $Id: IntrospectionContext.java 1540359 2013-11-09 18:10:52Z oheger $
+ * @since 1.9
+ */
+public interface IntrospectionContext {
+ /**
+ * Returns the class that is subject of introspection.
+ *
+ * @return the current class
+ */
+ Class getTargetClass();
+
+ /**
+ * Adds the given property descriptor to this context. This method is called
+ * by a {@code BeanIntrospector} during introspection for each detected
+ * property. If this context already contains a descriptor for the affected
+ * property, it is overridden.
+ *
+ * @param desc the property descriptor
+ */
+ void addPropertyDescriptor(PropertyDescriptor desc);
+
+ /**
+ * Adds an array of property descriptors to this context. Using this method
+ * multiple descriptors can be added at once.
+ *
+ * @param descriptors the array of descriptors to be added
+ */
+ void addPropertyDescriptors(PropertyDescriptor[] descriptors);
+
+ /**
+ * Tests whether a descriptor for the property with the given name is
+ * already contained in this context. This method can be used for instance
+ * to prevent that an already existing property descriptor is overridden.
+ *
+ * @param name the name of the property in question
+ * @return <b>true</b> if a descriptor for this property has already been
+ * added, <b>false</b> otherwise
+ */
+ boolean hasProperty(String name);
+
+ /**
+ * Returns the descriptor for the property with the given name or
+ * <b>null</b> if this property is unknown.
+ *
+ * @param name the name of the property in question
+ * @return the descriptor for this property or <b>null</b> if this property
+ * is unknown
+ */
+ PropertyDescriptor getPropertyDescriptor(String name);
+
+ /**
+ * Removes the descriptor for the property with the given name.
+ *
+ * @param name the name of the affected property
+ */
+ void removePropertyDescriptor(String name);
+
+ /**
+ * Returns a set with the names of all properties known to this context.
+ *
+ * @return a set with the known property names
+ */
+ Set propertyNames();
+}
diff --git a/src/main/java/org/apache/commons/beanutils/PropertyUtils.java b/src/main/java/org/apache/commons/beanutils/PropertyUtils.java
index 6344b4f..b3ce0e6 100644
--- a/src/main/java/org/apache/commons/beanutils/PropertyUtils.java
+++ b/src/main/java/org/apache/commons/beanutils/PropertyUtils.java
@@ -890,5 +890,20 @@ public class PropertyUtils {
PropertyUtilsBean.getInstance().setSimpleProperty(bean, name, value);
}
+ public static void addBeanIntrospector(final BeanIntrospector introspector) {
+ PropertyUtilsBean.getInstance().addBeanIntrospector(introspector);
+ }
+ /**
+ * Removes the specified <code>BeanIntrospector</code>.
+ *
+ * @param introspector the <code>BeanIntrospector</code> to be removed
+ * @return <b>true</b> if the <code>BeanIntrospector</code> existed and
+ * could be removed, <b>false</b> otherwise
+ * @since 1.9
+ */
+ public static boolean removeBeanIntrospector(final BeanIntrospector introspector) {
+ return PropertyUtilsBean.getInstance().removeBeanIntrospector(
+ introspector);
+ }
}
diff --git a/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java b/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java
index 1894f21..c7817cd 100644
--- a/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java
+++ b/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java
@@ -37,6 +37,7 @@ import org.apache.commons.collections.FastHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* Utility methods for using Java Reflection APIs to facilitate generic
@@ -130,7 +131,9 @@ public class PropertyUtilsBean {
/** Log instance */
private Log log = LogFactory.getLog(PropertyUtils.class);
-
+
+ /** The list with BeanIntrospector objects. */
+ private final List introspectors;
// ---------------------------------------------------------- Constructors
/** Base constructor */
@@ -139,6 +142,8 @@ public class PropertyUtilsBean {
descriptorsCache.setFast(true);
mappedDescriptorsCache = new WeakFastHashMap();
mappedDescriptorsCache.setFast(true);
+ introspectors = new CopyOnWriteArrayList();
+ resetBeanIntrospectors();
}
@@ -183,6 +188,47 @@ public class PropertyUtilsBean {
}
}
+ /**
+ * Resets the {@link BeanIntrospector} objects registered at this instance. After this
+ * method was called, only the default {@code BeanIntrospector} is registered.
+ *
+ * @since 1.9
+ */
+ public final void resetBeanIntrospectors() {
+ introspectors.clear();
+ introspectors.add(DefaultBeanIntrospector.INSTANCE);
+ introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
+ }
+
+ /**
+ * Adds a <code>BeanIntrospector</code>. This object is invoked when the
+ * property descriptors of a class need to be obtained.
+ *
+ * @param introspector the <code>BeanIntrospector</code> to be added (must
+ * not be <b>null</b>
+ * @throws IllegalArgumentException if the argument is <b>null</b>
+ * @since 1.9
+ */
+ public void addBeanIntrospector(final BeanIntrospector introspector) {
+ if (introspector == null) {
+ throw new IllegalArgumentException(
+ "BeanIntrospector must not be null!");
+ }
+ introspectors.add(introspector);
+ }
+
+ /**
+ * Removes the specified <code>BeanIntrospector</code>.
+ *
+ * @param introspector the <code>BeanIntrospector</code> to be removed
+ * @return <b>true</b> if the <code>BeanIntrospector</code> existed and
+ * could be removed, <b>false</b> otherwise
+ * @since 1.9
+ */
+ public boolean removeBeanIntrospector(final BeanIntrospector introspector) {
+ return introspectors.remove(introspector);
+ }
+
/**
* Clear any cached property descriptors information for all classes
* loaded by any class loaders. This is useful in cases where class
@@ -909,17 +955,12 @@ public class PropertyUtilsBean {
return (null);
}
- PropertyDescriptor[] descriptors = getPropertyDescriptors(bean);
- if (descriptors != null) {
-
- for (int i = 0; i < descriptors.length; i++) {
- if (name.equals(descriptors[i].getName())) {
- return (descriptors[i]);
- }
- }
+ final BeanIntrospectionData data = getIntrospectionData(bean.getClass());
+ PropertyDescriptor result = data.getDescriptor(name);
+ if (result != null) {
+ return result;
}
- PropertyDescriptor result = null;
FastHashMap mappedDescriptors =
getMappedPropertyDescriptors(bean);
if (mappedDescriptors == null) {
@@ -960,110 +1001,10 @@ public class PropertyUtilsBean {
* @exception IllegalArgumentException if <code>beanClass</code> is null
*/
public PropertyDescriptor[]
- getPropertyDescriptors(Class beanClass) {
-
- if (beanClass == null) {
- throw new IllegalArgumentException("No bean class specified");
- }
-
- // Look up any cached descriptors for this bean class
- PropertyDescriptor[] descriptors = null;
- descriptors =
- (PropertyDescriptor[]) descriptorsCache.get(beanClass);
- if (descriptors != null) {
- return (descriptors);
- }
-
- // Introspect the bean and cache the generated descriptors
- BeanInfo beanInfo = null;
- try {
- beanInfo = Introspector.getBeanInfo(beanClass);
- } catch (IntrospectionException e) {
- return (new PropertyDescriptor[0]);
- }
- descriptors = beanInfo.getPropertyDescriptors();
- if (descriptors == null) {
- descriptors = new PropertyDescriptor[0];
- }
-
- // ----------------- Workaround for Bug 28358 --------- START ------------------
- //
- // The following code fixes an issue where IndexedPropertyDescriptor behaves
- // Differently in different versions of the JDK for 'indexed' properties which
- // use java.util.List (rather than an array).
- //
- // If you have a Bean with the following getters/setters for an indexed property:
- //
- // public List getFoo()
- // public Object getFoo(int index)
- // public void setFoo(List foo)
- // public void setFoo(int index, Object foo)
- //
- // then the IndexedPropertyDescriptor's getReadMethod() and getWriteMethod()
- // behave as follows:
- //
- // JDK 1.3.1_04: returns valid Method objects from these methods.
- // JDK 1.4.2_05: returns null from these methods.
- //
- for (int i = 0; i < descriptors.length; i++) {
- if (descriptors[i] instanceof IndexedPropertyDescriptor) {
- IndexedPropertyDescriptor descriptor = (IndexedPropertyDescriptor)descriptors[i];
- String propName = descriptor.getName().substring(0, 1).toUpperCase() +
- descriptor.getName().substring(1);
-
- if (descriptor.getReadMethod() == null) {
- String methodName = descriptor.getIndexedReadMethod() != null
- ? descriptor.getIndexedReadMethod().getName()
- : "get" + propName;
- Method readMethod = MethodUtils.getMatchingAccessibleMethod(beanClass,
- methodName,
- EMPTY_CLASS_PARAMETERS);
- if (readMethod != null) {
- try {
- descriptor.setReadMethod(readMethod);
- } catch(Exception e) {
- log.error("Error setting indexed property read method", e);
- }
- }
- }
- if (descriptor.getWriteMethod() == null) {
- String methodName = descriptor.getIndexedWriteMethod() != null
- ? descriptor.getIndexedWriteMethod().getName()
- : "set" + propName;
- Method writeMethod = MethodUtils.getMatchingAccessibleMethod(beanClass,
- methodName,
- LIST_CLASS_PARAMETER);
- if (writeMethod == null) {
- Method[] methods = beanClass.getMethods();
- for (int j = 0; j < methods.length; j++) {
- if (methods[j].getName().equals(methodName)) {
- Class[] parameterTypes = methods[j].getParameterTypes();
- if (parameterTypes.length == 1 &&
- List.class.isAssignableFrom(parameterTypes[0])) {
- writeMethod = methods[j];
- break;
- }
- }
- }
- }
- if (writeMethod != null) {
- try {
- descriptor.setWriteMethod(writeMethod);
- } catch(Exception e) {
- log.error("Error setting indexed property write method", e);
- }
- }
- }
- }
- }
- // ----------------- Workaround for Bug 28358 ---------- END -------------------
-
- descriptorsCache.put(beanClass, descriptors);
- return (descriptors);
-
+ getPropertyDescriptors(Class beanClass) {
+ return getIntrospectionData(beanClass).getDescriptors();
}
-
/**
* <p>Retrieve the property descriptors for the specified bean,
* introspecting and caching them the first time a particular bean class
@@ -2248,4 +2189,52 @@ public class PropertyUtilsBean {
}
}
+
+ /**
+ * Obtains the {@code BeanIntrospectionData} object describing the specified bean
+ * class. This object is looked up in the internal cache. If necessary, introspection
+ * is performed now on the affected bean class, and the results object is created.
+ *
+ * @param beanClass the bean class in question
+ * @return the {@code BeanIntrospectionData} object for this class
+ * @throws IllegalArgumentException if the bean class is <b>null</b>
+ */
+ private BeanIntrospectionData getIntrospectionData(final Class beanClass) {
+ if (beanClass == null) {
+ throw new IllegalArgumentException("No bean class specified");
+ }
+
+ // Look up any cached information for this bean class
+ BeanIntrospectionData data = (BeanIntrospectionData) descriptorsCache.get(beanClass);
+ if (data == null) {
+ data = fetchIntrospectionData(beanClass);
+ descriptorsCache.put(beanClass, data);
+ }
+
+ return data;
+ }
+
+ /**
+ * Performs introspection on the specified class. This method invokes all {@code BeanIntrospector} objects that were
+ * added to this instance.
+ *
+ * @param beanClass the class to be inspected
+ * @return a data object with the results of introspection
+ */
+ private BeanIntrospectionData fetchIntrospectionData(final Class beanClass) {
+ final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);
+
+ Iterator it = introspectors.iterator();
+ while (it.hasNext()) {
+ final BeanIntrospector bi = (BeanIntrospector) it.next();
+
+ try {
+ bi.introspect(ictx);
+ } catch (final IntrospectionException iex) {
+ log.error("Exception during introspection", iex);
+ }
+ }
+
+ return new BeanIntrospectionData(ictx.getPropertyDescriptors());
+ }
}
diff --git a/src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java b/src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java
new file mode 100644
index 0000000..9d4e3b3
--- /dev/null
+++ b/src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java
@@ -0,0 +1,75 @@
+package org.apache.commons.beanutils;
+
+import java.beans.IntrospectionException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <p>
+ * A specialized {@code BeanIntrospector} implementation which suppresses some properties.
+ * </p>
+ * <p>
+ * An instance of this class is passed a set with the names of the properties it should
+ * process. During introspection of a bean class it removes all these properties from the
+ * {@link IntrospectionContext}. So effectively, properties added by a different
+ * {@code BeanIntrospector} are removed again.
+ * </p>
+ *
+ * @version $Id$
+ * @since 1.9.2
+ */
+public class SuppressPropertiesBeanIntrospector implements BeanIntrospector {
+ /**
+ * A specialized instance which is configured to suppress the special {@code class}
+ * properties of Java beans. Unintended access to the property {@code class} (which is
+ * common to all Java objects) can be a security risk because it also allows access to
+ * the class loader. Adding this instance as {@code BeanIntrospector} to an instance
+ * of {@code PropertyUtilsBean} suppresses the {@code class} property; it can then no
+ * longer be accessed.
+ */
+ public static final SuppressPropertiesBeanIntrospector SUPPRESS_CLASS =
+ new SuppressPropertiesBeanIntrospector(Collections.singleton("class"));
+
+ /** A set with the names of the properties to be suppressed. */
+ private final Set<String> propertyNames;
+
+ /**
+ * Creates a new instance of {@code SuppressPropertiesBeanIntrospector} and sets the
+ * names of the properties to be suppressed.
+ *
+ * @param propertiesToSuppress the names of the properties to be suppressed (must not
+ * be <b>null</b>)
+ * @throws IllegalArgumentException if the collection with property names is
+ * <b>null</b>
+ */
+ public SuppressPropertiesBeanIntrospector(Collection<String> propertiesToSuppress) {
+ if (propertiesToSuppress == null) {
+ throw new IllegalArgumentException("Property names must not be null!");
+ }
+
+ propertyNames = Collections.unmodifiableSet(new HashSet<String>(
+ propertiesToSuppress));
+ }
+
+ /**
+ * Returns a (unmodifiable) set with the names of the properties which are suppressed
+ * by this {@code BeanIntrospector}.
+ *
+ * @return a set with the names of the suppressed properties
+ */
+ public Set<String> getSuppressedProperties() {
+ return propertyNames;
+ }
+
+ /**
+ * {@inheritDoc} This implementation removes all properties from the given context it
+ * is configured for.
+ */
+ public void introspect(IntrospectionContext icontext) throws IntrospectionException {
+ for (String property : getSuppressedProperties()) {
+ icontext.removePropertyDescriptor(property);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospectorTestCase.java b/src/test/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospectorTestCase.java
new file mode 100644
index 0000000..ed93e70
--- /dev/null
+++ b/src/test/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospectorTestCase.java
@@ -0,0 +1,127 @@
+package org.apache.commons.beanutils;
+
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+/**
+ * Test class for {@code SuppressPropertiesBeanIntrospector}.
+ *
+ * @version $Id$
+ */
+public class SuppressPropertiesBeanIntrospectorTestCase extends TestCase {
+ /**
+ * Tries to create an instance without properties.
+ */
+ public void testInitNoPropertyNames() {
+ try {
+ new SuppressPropertiesBeanIntrospector(null);
+ fail("Missing properties not detected!");
+ } catch (IllegalArgumentException iaex) {
+ // ok
+ }
+ }
+
+ /**
+ * Tests whether the expected properties have been removed during introspection.
+ */
+ public void testRemovePropertiesDuringIntrospection() throws IntrospectionException {
+ String[] properties = { "test", "other", "oneMore" };
+ SuppressPropertiesBeanIntrospector introspector = new SuppressPropertiesBeanIntrospector(
+ Arrays.asList(properties));
+ IntrospectionContextTestImpl context = new IntrospectionContextTestImpl();
+
+ introspector.introspect(context);
+ assertEquals("Wrong number of removed properties", properties.length, context
+ .getRemovedProperties().size());
+ for (String property : properties) {
+ assertTrue("Property not removed: " + property, context
+ .getRemovedProperties().contains(property));
+ }
+ }
+
+ /**
+ * Tests that a defensive copy is created from the collection with properties to be
+ * removed.
+ */
+ public void testPropertyNamesDefensiveCopy() throws IntrospectionException {
+ Collection<String> properties = new HashSet<String>();
+ properties.add("prop1");
+ SuppressPropertiesBeanIntrospector introspector = new SuppressPropertiesBeanIntrospector(
+ properties);
+ properties.add("prop2");
+ IntrospectionContextTestImpl context = new IntrospectionContextTestImpl();
+
+ introspector.introspect(context);
+ assertEquals("Wrong number of removed properties", 1, context
+ .getRemovedProperties().size());
+ assertTrue("Wrong removed property",
+ context.getRemovedProperties().contains("prop1"));
+ }
+
+ /**
+ * Tests that the set with properties to be removed cannot be modified.
+ */
+ public void testGetSuppressedPropertiesModify() {
+ SuppressPropertiesBeanIntrospector introspector = new SuppressPropertiesBeanIntrospector(
+ Arrays.asList("p1", "p2"));
+ Set<String> properties = introspector.getSuppressedProperties();
+ try {
+ properties.add("anotherProperty");
+ fail("Could modify properties");
+ } catch (UnsupportedOperationException uoex) {
+ // ok
+ }
+ }
+
+ /**
+ * A test implementation of IntrospectionContext which collects the properties which
+ * have been removed.
+ */
+ private static class IntrospectionContextTestImpl implements IntrospectionContext {
+ /** Stores the names of properties which have been removed. */
+ private final Set<String> removedProperties = new HashSet<String>();
+
+ /**
+ * Returns the names of properties which have been removed.
+ *
+ * @return the set with removed properties
+ */
+ public Set<String> getRemovedProperties() {
+ return removedProperties;
+ }
+
+ public Class<?> getTargetClass() {
+ throw new UnsupportedOperationException("Unexpected method call!");
+ }
+
+ public void addPropertyDescriptor(PropertyDescriptor desc) {
+ throw new UnsupportedOperationException("Unexpected method call!");
+ }
+
+ public void addPropertyDescriptors(PropertyDescriptor[] descriptors) {
+ throw new UnsupportedOperationException("Unexpected method call!");
+ }
+
+ public boolean hasProperty(String name) {
+ throw new UnsupportedOperationException("Unexpected method call!");
+ }
+
+ public PropertyDescriptor getPropertyDescriptor(String name) {
+ throw new UnsupportedOperationException("Unexpected method call!");
+ }
+
+ public void removePropertyDescriptor(String name) {
+ removedProperties.add(name);
+ }
+
+ public Set<String> propertyNames() {
+ throw new UnsupportedOperationException("Unexpected method call!");
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/beanutils/bugs/Jira463TestCase.java b/src/test/java/org/apache/commons/beanutils/bugs/Jira463TestCase.java
new file mode 100644
index 0000000..31ac31c
--- /dev/null
+++ b/src/test/java/org/apache/commons/beanutils/bugs/Jira463TestCase.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils.bugs;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.beanutils.AlphaBean;
+import org.apache.commons.beanutils.BeanUtilsBean;
+import org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector;
+
+/**
+ * Class loader vulnerability in DefaultResolver
+ *
+ * @version $Id$
+ * @see <a href="https://issues.apache.org/jira/browse/BEANUTILS-463">https://issues.apache.org/jira/browse/BEANUTILS-463</a>
+ */
+public class Jira463TestCase extends TestCase {
+ /**
+ * Tests that with a specialized {@code BeanIntrospector} implementation the class
+ * property can be suppressed.
+ */
+ public void testSuppressClassProperty() throws Exception {
+ BeanUtilsBean bub = new BeanUtilsBean();
+ bub.getPropertyUtils().addBeanIntrospector(
+ SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
+ AlphaBean bean = new AlphaBean();
+ try {
+ bub.getProperty(bean, "class");
+ fail("Could access class property!");
+ } catch (NoSuchMethodException ex) {
+ // ok
+ }
+ }
+}
diff --git a/src/test/java/org/apache/commons/beanutils/bugs/Jira520TestCase.java b/src/test/java/org/apache/commons/beanutils/bugs/Jira520TestCase.java
new file mode 100644
index 0000000..2a70811
--- /dev/null
+++ b/src/test/java/org/apache/commons/beanutils/bugs/Jira520TestCase.java
@@ -0,0 +1,39 @@
+package org.apache.commons.beanutils.bugs;
+
+import org.apache.commons.beanutils.AlphaBean;
+import org.apache.commons.beanutils.BeanUtilsBean;
+import org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector;
+
+import junit.framework.TestCase;
+
+/**
+ * Fix CVE: https://nvd.nist.gov/vuln/detail/CVE-2014-0114
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/BEANUTILS-520">https://issues.apache.org/jira/browse/BEANUTILS-520</a>
+ */
+public class Jira520TestCase extends TestCase {
+ /**
+ * By default opt-in to security that does not allow access to "class".
+ */
+ public void testSuppressClassPropertyByDefault() throws Exception {
+ final BeanUtilsBean bub = new BeanUtilsBean();
+ final AlphaBean bean = new AlphaBean();
+ try {
+ bub.getProperty(bean, "class");
+ fail("Could access class property!");
+ } catch (final NoSuchMethodException ex) {
+ // ok
+ }
+ }
+
+ /**
+ * Allow opt-out to make your app less secure but allow access to "class".
+ */
+ public void testAllowAccessToClassProperty() throws Exception {
+ final BeanUtilsBean bub = new BeanUtilsBean();
+ bub.getPropertyUtils().removeBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
+ final AlphaBean bean = new AlphaBean();
+ String result = bub.getProperty(bean, "class");
+ assertEquals("Class property should have been accessed", "class org.apache.commons.beanutils.AlphaBean", result);
+ }
+}
\ No newline at end of file
--
2.21.0