This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-beanutils.git
The following commit(s) were added to refs/heads/master by this push: new bd20740d Add org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS bd20740d is described below commit bd20740da25b69552ddef8523beec0837297eaf9 Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Sun May 25 09:07:45 2025 -0400 Add org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS --- src/changes/changes.xml | 16 +++ .../commons/beanutils2/PropertyUtilsBean.java | 1 + .../SuppressPropertiesBeanIntrospector.java | 14 ++- .../org/apache/commons/beanutils2/TestEnum.java | 33 +++++++ .../apache/commons/beanutils2/package-info.java | 8 +- .../beanutils2/bugs/EnumDeclaringClassTest.java | 108 +++++++++++++++++++++ 6 files changed, 178 insertions(+), 2 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 4f82d3cc..e0ee838b 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -54,6 +54,7 @@ <action type="fix" dev="ggregory" due-to="Gary Gregory">ResultSetIterator.set(String, Object) now throws IllegalArgumentException instead of RuntimeException to wrap cases of SQLException.</action> <action type="fix" dev="ggregory" due-to="Gary Gregory">ResultSetIterator.set(String, String, Object) now throws IllegalArgumentException instead of RuntimeException to wrap cases of SQLException.</action> <!-- ADD --> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS.</action> <!-- UPDATE --> <action type="update" dev="ggregory" due-to="Gary Gregory, Dependabot">Bump org.apache.commons:commons-parent from 78 to 84 #348.</action> <action type="update" dev="ggregory" due-to="Gary Gregory">Bump commons-logging:commons-logging from 1.3.4 to 1.3.5.</action> @@ -258,6 +259,21 @@ Do not implement Serializable. </action> </release> + <release version="1.11.0" date="YYYY-MM-DD" description="This is a maintenance release and requires Java 8."> + <!-- FIX --> + <action type="fix" dev="ggregory" due-to="Gary Gregory">BeanComparator.compare(T, T) now throws IllegalArgumentException instead of RuntimeException to wrap all cases of ReflectiveOperationException.</action> + <action type="fix" dev="ggregory" due-to="Gary Gregory">MappedMethodReference.get() now throws IllegalStateException instead of RuntimeException to wrap cases of NoSuchMethodException.</action> + <action type="fix" dev="ggregory" due-to="Gary Gregory">ResultSetIterator.get(String) now throws IllegalArgumentException instead of RuntimeException to wrap cases of SQLException.</action> + <action type="fix" dev="ggregory" due-to="Gary Gregory">ResultSetIterator.hasNext() now throws IllegalStateException instead of RuntimeException to wrap cases of SQLException.</action> + <action type="fix" dev="ggregory" due-to="Gary Gregory">ResultSetIterator.next() now throws IllegalStateException instead of RuntimeException to wrap cases of SQLException.</action> + <action type="fix" dev="ggregory" due-to="Gary Gregory">ResultSetIterator.set(String, Object) now throws IllegalArgumentException instead of RuntimeException to wrap cases of SQLException.</action> + <action type="fix" dev="ggregory" due-to="Gary Gregory">ResultSetIterator.set(String, String, Object) now throws IllegalArgumentException instead of RuntimeException to wrap cases of SQLException.</action> + <!-- ADD --> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS.</action> + <!-- UPDATE --> + <action dev="ggregory" type="update" due-to="Gary Gregory">Bump org.apache.commons:commons-parent from 81 to 84.</action> + <action dev="ggregory" type="update" due-to="Gary Gregory">Bump commons-logging:commons-logging from 1.3.4 to 1.3.5.</action> + </release> <release version="1.10.1" date="2025-01-31" description="This is a maintenance release and requires Java 8."> <!-- FIX --> <action type="fix" issue="BEANUTILS-541" dev="ggregory" due-to="Sergey Chernov">FluentPropertyBeanIntrospector concurrency issue (backport to 1.X) #325.</action> diff --git a/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java b/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java index d1fe015b..f16f06e3 100644 --- a/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java +++ b/src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java @@ -1201,6 +1201,7 @@ public class PropertyUtilsBean { introspectors.clear(); introspectors.add(DefaultBeanIntrospector.INSTANCE); introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS); + introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS); } /** diff --git a/src/main/java/org/apache/commons/beanutils2/SuppressPropertiesBeanIntrospector.java b/src/main/java/org/apache/commons/beanutils2/SuppressPropertiesBeanIntrospector.java index bb297ffd..8d9b3562 100644 --- a/src/main/java/org/apache/commons/beanutils2/SuppressPropertiesBeanIntrospector.java +++ b/src/main/java/org/apache/commons/beanutils2/SuppressPropertiesBeanIntrospector.java @@ -35,6 +35,7 @@ import java.util.Set; * @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 @@ -42,7 +43,18 @@ public class SuppressPropertiesBeanIntrospector implements BeanIntrospector { */ public static final SuppressPropertiesBeanIntrospector SUPPRESS_CLASS = new SuppressPropertiesBeanIntrospector(Collections.singleton("class")); - /** A set with the names of the properties to be suppressed. */ + /** + * A specialized instance which is configured to suppress the special {@code class} properties of Java beans. Unintended access to the call for + * {@code declaringClass} (which is common to all Java {@code enum}) 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. + * + * @since 2.0.0-M2 + */ + public static final SuppressPropertiesBeanIntrospector SUPPRESS_DECLARING_CLASS = new SuppressPropertiesBeanIntrospector( + Collections.singleton("declaringClass")); + +/** A set with the names of the properties to be suppressed. */ private final Set<String> propertyNames; /** diff --git a/src/main/java/org/apache/commons/beanutils2/TestEnum.java b/src/main/java/org/apache/commons/beanutils2/TestEnum.java new file mode 100644 index 00000000..a64142a0 --- /dev/null +++ b/src/main/java/org/apache/commons/beanutils2/TestEnum.java @@ -0,0 +1,33 @@ +/* + * 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 + * + * https://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.beanutils2; + +/** + * An {@code enum} test fixture. + */ +public enum TestEnum { + + /** Test fixture. */ + A, + + /** Test fixture. */ + B, + + /** Test fixture. */ + C +} diff --git a/src/main/java/org/apache/commons/beanutils2/package-info.java b/src/main/java/org/apache/commons/beanutils2/package-info.java index a2bd66f2..50b8a996 100644 --- a/src/main/java/org/apache/commons/beanutils2/package-info.java +++ b/src/main/java/org/apache/commons/beanutils2/package-info.java @@ -424,7 +424,7 @@ * * <p>A good use case for suppressing properties is the special {@code class} * property which is per default available for all beans; it is generated from the - * {@code getClass()</code> method inherited from <code>Object} which follows the + * {@code getClass()} method inherited from {@code Object} which follows the * naming conventions for property get methods. Exposing this property in an * uncontrolled way can lead to a security vulnerability as it allows access to * the class loader. More information can be found at @@ -437,6 +437,12 @@ * {@code SUPPRESS_CLASS} constant of * {@code SuppressPropertiesBeanIntrospector}.</p> * + * <p>Another problematic property is the {@code enum} "declaredClass" property, + * through which you can also access that class' class loader. The {@code SuppressPropertiesBeanIntrospector} + * provides {@code SUPPRESS_DECLARING_CLASS} to workaround this issue.</p> + * + * <p>Both {@code SUPPRESS_CLASS} and {@code SUPPRESS_DECLARING_CLASS} are enabled by default.</p> + * * <a id="dynamic"></a> * <h2>3. Dynamic Beans (DynaBeans)</h2> * diff --git a/src/test/java/org/apache/commons/beanutils2/bugs/EnumDeclaringClassTest.java b/src/test/java/org/apache/commons/beanutils2/bugs/EnumDeclaringClassTest.java new file mode 100644 index 00000000..e0d1c229 --- /dev/null +++ b/src/test/java/org/apache/commons/beanutils2/bugs/EnumDeclaringClassTest.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 + * + * https://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.beanutils2.bugs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.commons.beanutils2.BeanUtilsBean; +import org.apache.commons.beanutils2.PropertyUtilsBean; +import org.apache.commons.beanutils2.SuppressPropertiesBeanIntrospector; +import org.apache.commons.beanutils2.TestEnum; +import org.junit.jupiter.api.Test; + +public class EnumDeclaringClassTest { + + public static class Fixture { + + String name = "default"; + TestEnum testEnum = TestEnum.A; + + public String getName() { + return name; + } + + public TestEnum getTestEnum() { + return testEnum; + } + + public void setName(final String name) { + this.name = name; + } + + public void setTestEnum(final TestEnum day) { + this.testEnum = day; + } + } + + /** + * Allow opt-out to make your app less secure but allow access to "declaringClass". + */ + @Test + public void testAllowAccessToClassPropertyFromBeanUtilsBean() throws ReflectiveOperationException { + final BeanUtilsBean bub = new BeanUtilsBean(); + final PropertyUtilsBean propertyUtilsBean = bub.getPropertyUtils(); + propertyUtilsBean.removeBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS); + final Fixture fixture = new Fixture(); + final String string = bub.getProperty(fixture, "testEnum.declaringClass"); + assertEquals(TestEnum.class.getName(), string); + final Class<TestEnum> teClass = assertInstanceOf(Class.class, propertyUtilsBean.getNestedProperty(fixture, "testEnum.declaringClass")); + final ClassLoader classLoader = teClass.getClassLoader(); + assertNotNull(classLoader); + assertNotNull(bub.getProperty(fixture, "testEnum.declaringClass.classLoader")); + assertInstanceOf(ClassLoader.class, propertyUtilsBean.getNestedProperty(fixture, "testEnum.declaringClass.classLoader")); + } + + /** + * Allow opt-out to make your app less secure but allow access to "declaringClass". + */ + @Test + public void testAllowAccessToClassPropertyFromPropertyUtilsBean() throws ReflectiveOperationException { + final PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean(); + propertyUtilsBean.removeBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS); + final Fixture fixture = new Fixture(); + final Object cls = propertyUtilsBean.getNestedProperty(fixture, "testEnum.declaringClass"); + final Class<TestEnum> teClass = assertInstanceOf(Class.class, cls); + final ClassLoader classLoader = teClass.getClassLoader(); + assertNotNull(classLoader); + assertInstanceOf(ClassLoader.class, propertyUtilsBean.getNestedProperty(fixture, "testEnum.declaringClass.classLoader")); + } + + /** + * By default opt-in to security that does not allow access to "declaringClass". + */ + @Test + public void testSuppressClassPropertyByDefaultFromBeanUtilsBean() throws ReflectiveOperationException { + final Fixture fixture = new Fixture(); + final BeanUtilsBean bub = new BeanUtilsBean(); + assertThrows(NoSuchMethodException.class, () -> bub.getProperty(fixture, "testEnum.declaringClass.classLoader")); + assertThrows(NoSuchMethodException.class, () -> bub.getPropertyUtils().getNestedProperty(fixture, "testEnum.declaringClass.classLoader")); + } + + /** + * By default opt-in to security that does not allow access to "declaringClass". + */ + @Test + public void testSuppressClassPropertyByDefaultFromPropertyUtilsBean() throws ReflectiveOperationException { + final Fixture fixture = new Fixture(); + final PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean(); + assertThrows(NoSuchMethodException.class, () -> propertyUtilsBean.getNestedProperty(fixture, "testEnum.declaringClass.classLoader")); + } +}