This is an automated email from the ASF dual-hosted git repository. kusal pushed a commit to branch WW-5525-proxyutil-npe-67 in repository https://gitbox.apache.org/repos/asf/struts.git
commit 14254e602bb17c64af7e92576c777842feefce3b Author: Kusal Kithul-Godage <g...@kusal.io> AuthorDate: Thu Feb 6 12:02:40 2025 +1100 WW-5525 Fix NPE in ProxyUtil for SecurityMemberAccess originating static members --- .../xwork2/ognl/SecurityMemberAccess.java | 5 +++- .../com/opensymphony/xwork2/util/ProxyUtil.java | 3 ++- .../xwork2/ognl/OgnlValueStackTest.java | 28 ++++++++++++++++++++++ .../xwork2/spring/SpringProxyUtilTest.java | 2 ++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java b/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java index f15b50af1..3963ac730 100644 --- a/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java +++ b/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java @@ -160,6 +160,9 @@ public class SecurityMemberAccess implements MemberAccess { public boolean isAccessible(Map context, Object target, Member member, String propertyName) { LOG.debug("Checking access for [target: {}, member: {}, property: {}]", target, member, propertyName); + if (member == null) { + throw new IllegalArgumentException("Member cannot be null!"); + } if (target != null) { // Special case: Target is a Class object but not Class.class if (Class.class.equals(target.getClass()) && !Class.class.equals(target)) { @@ -228,7 +231,7 @@ public class SecurityMemberAccess implements MemberAccess { return true; } - if (!disallowProxyObjectAccess && target != null && ProxyUtil.isProxy(target)) { + if (!disallowProxyObjectAccess && ProxyUtil.isProxy(target)) { // If `disallowProxyObjectAccess` is not set, allow resolving Hibernate entities to their underlying // classes/members. This allows the allowlist capability to continue working and offer some level of // protection in applications where the developer has accepted the risk of allowing OGNL access to Hibernate diff --git a/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java b/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java index 895cfb7ee..22c344446 100644 --- a/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java +++ b/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java @@ -81,6 +81,7 @@ public class ProxyUtil { * @param object the object to check */ public static boolean isProxy(Object object) { + if (object == null) return false; Class<?> clazz = object.getClass(); Boolean flag = isProxyCache.get(clazz); if (flag != null) { @@ -121,7 +122,7 @@ public class ProxyUtil { */ public static boolean isHibernateProxy(Object object) { try { - return HibernateProxy.class.isAssignableFrom(object.getClass()); + return object != null && HibernateProxy.class.isAssignableFrom(object.getClass()); } catch (NoClassDefFoundError ignored) { return false; } diff --git a/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java b/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java index 7fb560c5b..86a83cd8c 100644 --- a/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java @@ -1233,6 +1233,34 @@ public class OgnlValueStackTest extends XWorkTestCase { assertNull("accessed private field (result not null) ?", accessedValue); } + public void testFindValueWithConstructorAndProxyChecks() { + Map<String, String> properties = new HashMap<>(); + properties.put(StrutsConstants.STRUTS_DISALLOW_PROXY_OBJECT_ACCESS, Boolean.TRUE.toString()); + properties.put(StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS, Boolean.TRUE.toString()); + loadButSet(properties); + refreshContainerFields(); + + String value = "test"; + String ognlResult = (String) vs.findValue( + "new org.apache.struts2.ognl.OgnlValueStackTest$ValueHolder('" + value + "').value", String.class); + + assertEquals(value, ognlResult); + } + + @SuppressWarnings({"unused"}) + public static class ValueHolder { + // See testFindValueWithConstructorAndProxyChecks + private final String value; + + public ValueHolder(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + static class BadJavaBean { private int count; private int count2; diff --git a/plugins/spring/src/test/java/com/opensymphony/xwork2/spring/SpringProxyUtilTest.java b/plugins/spring/src/test/java/com/opensymphony/xwork2/spring/SpringProxyUtilTest.java index 7b77294e5..dc6e58cb3 100644 --- a/plugins/spring/src/test/java/com/opensymphony/xwork2/spring/SpringProxyUtilTest.java +++ b/plugins/spring/src/test/java/com/opensymphony/xwork2/spring/SpringProxyUtilTest.java @@ -46,6 +46,8 @@ public class SpringProxyUtilTest extends XWorkTestCase { } public void testIsProxy() throws Exception { + assertFalse(ProxyUtil.isProxy(null)); + Object simpleAction = appContext.getBean("simple-action"); assertFalse(ProxyUtil.isProxy(simpleAction));