diff --git a/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java b/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java index 3e626e664d..4d552d5948 100644 --- a/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java +++ b/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java @@ -393,8 +393,13 @@ public static String toPackageName(Class> clazz) { // call, whereas getPackageName() is computed once and cached on the Class. getPackage() // returns null for exactly arrays, primitives and void, so the guard reproduces the // previous result for every input. Note that void.class.isPrimitive() is true. - // Arrays deliberately keep the empty package here: getPackageName() would resolve them - // to the element type's package, which would loosen the allowlist. See WW-5674. + // Arrays deliberately keep the empty package. WW-5676 weighed resolving them to the element + // type's package and decided against it: that would tighten nothing, because the package + // check is unreachable for array targets -- every reflectively reachable member of an array + // class declares in java.lang.Object, which is permanently excluded -- while it would loosen + // the allowlist, implicitly allowlisting com.app.Thing[] for any application configuring + // struts.allowlist.packageNames=com.app. SecurityMemberAccessArrayTargetTest pins that + // reasoning; reopen WW-5676 if it ever stops holding. if (clazz.isArray() || clazz.isPrimitive()) { return ""; } diff --git a/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessArrayTargetTest.java b/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessArrayTargetTest.java new file mode 100644 index 0000000000..e229145e8e --- /dev/null +++ b/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessArrayTargetTest.java @@ -0,0 +1,151 @@ +/* + * 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.struts2.ognl; + +import ognl.OgnlContext; +import org.apache.struts2.util.StrutsProxyService; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Pins the reasoning behind WW-5676, which decided that {@link SecurityMemberAccess#toPackageName} + * must keep resolving arrays to the empty package rather than to the element type's package. + *
+ * The concern WW-5676 was raised to investigate was that an array of an excluded-package type, say + * {@code java.io.File[]}, resolves to the empty package and so slips past + * {@code struts.excludedPackageNames} even though {@code java.io} is excluded by default. That + * reads like a defensive gap, but the package check is unreachable for array targets, because of + * the two facts pinned below: + *
+ * Resolving arrays to the element package would therefore tighten nothing, while genuinely
+ * loosening the allowlist: {@code struts.allowlist.packageNames=com.app} would begin to allowlist
+ * {@code com.app.Thing[]} implicitly, which today requires an explicit
+ * {@code struts.allowlist.classes} entry. These tests fail loudly if either fact stops holding,
+ * because that is what would turn the decision around.
+ */
+public class SecurityMemberAccessArrayTargetTest {
+
+ private static final List