Blob Blame History Raw
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