diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java
index 53f884871..6ff74f142 100644
--- a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java
@@ -15,6 +15,7 @@
*/
package com.opensymphony.xwork2.ognl;
+import com.opensymphony.xwork2.util.ProxyUtil;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import ognl.DefaultMemberAccess;
@@ -93,6 +94,11 @@ public class SecurityMemberAccess extends DefaultMemberAccess {
return false;
}
+ if (ProxyUtil.isProxyMember(member, target)) {
+ LOG.warn("Access to proxy [#0] is blocked!", member);
+ return false;
+ }
+
boolean allow = true;
if (!checkStaticMethodAccess(member)) {
if (LOG.isTraceEnabled()) {
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java
new file mode 100644
index 000000000..57eb98b31
--- /dev/null
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2017 The Apache Software Foundation.
+ *
+ * Licensed 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 com.opensymphony.xwork2.util;
+
+import org.apache.commons.lang3.reflect.ConstructorUtils;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.commons.lang3.reflect.MethodUtils;
+
+import java.lang.reflect.*;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * ProxyUtil
+ *
+ * Various utility methods dealing with proxies
+ *
+ *
+ */
+public class ProxyUtil {
+ private static final String SPRING_ADVISED_CLASS_NAME = "org.springframework.aop.framework.Advised";
+ private static final String SPRING_SPRINGPROXY_CLASS_NAME = "org.springframework.aop.SpringProxy";
+ private static final String SPRING_TARGETCLASSAWARE_CLASS_NAME = "org.springframework.aop.TargetClassAware";
+
+ private static final Map, Boolean> isProxyCache =
+ new ConcurrentHashMap, Boolean>(256);
+ private static final Map isProxyMemberCache =
+ new ConcurrentHashMap(256);
+
+ /**
+ * Check whether the given member is a proxy member of a proxy object.
+ * @param member the member to check
+ * @param object the object to check
+ */
+ public static boolean isProxyMember(Member member, Object object) {
+ if (!isProxy(object))
+ return false;
+
+ Boolean flag = isProxyMemberCache.get(member);
+ if (flag != null) {
+ return flag;
+ }
+
+ boolean isProxyMember = isSpringProxyMember(member);
+
+ isProxyMemberCache.put(member, isProxyMember);
+ return isProxyMember;
+ }
+
+ /**
+ * Check whether the given object is a proxy.
+ * @param object the object to check
+ */
+ private static boolean isProxy(Object object) {
+ Class> clazz = object.getClass();
+ Boolean flag = isProxyCache.get(clazz);
+ if (flag != null) {
+ return flag;
+ }
+
+ boolean isProxy = isSpringAopProxy(object);
+
+ isProxyCache.put(clazz, isProxy);
+ return isProxy;
+ }
+
+ /**
+ * Check whether the given object is a Spring proxy.
+ * @param object the object to check
+ */
+ private static boolean isSpringAopProxy(Object object) {
+ Class> clazz = object.getClass();
+ return (implementsInterface(clazz, SPRING_SPRINGPROXY_CLASS_NAME) && (Proxy.isProxyClass(clazz)
+ || isCglibProxyClass(clazz)));
+ }
+
+ /**
+ * Check whether the given member is a member of a spring proxy.
+ * @param member the member to check
+ */
+ private static boolean isSpringProxyMember(Member member) {
+ try {
+ Class> clazz = ClassLoaderUtil.loadClass(SPRING_ADVISED_CLASS_NAME, ProxyUtil.class);
+ if (hasMember(clazz, member))
+ return true;
+ clazz = ClassLoaderUtil.loadClass(SPRING_TARGETCLASSAWARE_CLASS_NAME, ProxyUtil.class);
+ if (hasMember(clazz, member))
+ return true;
+ clazz = ClassLoaderUtil.loadClass(SPRING_SPRINGPROXY_CLASS_NAME, ProxyUtil.class);
+ if (hasMember(clazz, member))
+ return true;
+ } catch (ClassNotFoundException ignored) {
+ }
+
+ return false;
+ }
+
+ /**
+ * Check whether the specified class is a CGLIB-generated class.
+ * @param clazz the class to check
+ */
+ private static boolean isCglibProxyClass(Class> clazz) {
+ return (clazz != null && clazz.getName().contains("$$"));
+ }
+
+ /**
+ * Check whether the given class implements an interface with a given class name.
+ * @param clazz the class to check
+ * @param ifaceClassName the interface class name to check
+ */
+ private static boolean implementsInterface(Class> clazz, String ifaceClassName) {
+ try {
+ Class> ifaceClass = ClassLoaderUtil.loadClass(ifaceClassName, ProxyUtil.class);
+ return ifaceClass.isAssignableFrom(clazz);
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Check whether the given class has a given member.
+ * @param clazz the class to check
+ * @param member the member to check
+ */
+ private static boolean hasMember(Class> clazz, Member member) {
+ if (member instanceof Method) {
+ return null != MethodUtils.getMatchingAccessibleMethod(clazz, member.getName(), ((Method) member).getParameterTypes());
+ }
+ if (member instanceof Field) {
+ return null != FieldUtils.getField(clazz, member.getName(), true);
+ }
+ if (member instanceof Constructor) {
+ return null != ConstructorUtils.getMatchingAccessibleConstructor(clazz, ((Constructor) member).getParameterTypes());
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/xwork-core/src/test/java/com/opensymphony/xwork2/spring/ActionsFromSpringTest.java b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/ActionsFromSpringTest.java
index f6cbecd91..114a87541 100644
--- a/xwork-core/src/test/java/com/opensymphony/xwork2/spring/ActionsFromSpringTest.java
+++ b/xwork-core/src/test/java/com/opensymphony/xwork2/spring/ActionsFromSpringTest.java
@@ -5,8 +5,12 @@ package com.opensymphony.xwork2.spring;
import com.opensymphony.xwork2.*;
import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
+import org.apache.commons.lang3.reflect.MethodUtils;
import org.springframework.context.ApplicationContext;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Test loading actions from the Spring Application Context.
*
@@ -77,4 +81,26 @@ public class ActionsFromSpringTest extends XWorkTestCase {
assertTrue(springResult.isInitialize());
assertNotNull(springResult.getStringParameter());
}
+
+ public void testProxiedActionIsNotAccessible() throws Exception {
+ // given
+ Map params = new HashMap();
+ params.put("exposeProxy", "true");
+ params.put("blah", "S2-047");
+
+ HashMap extraContext = new HashMap();
+ extraContext.put(ActionContext.PARAMETERS, params);
+
+ ActionProxy proxy = actionProxyFactory.createActionProxy(null,
+ "paramsAwareProxiedAction", null, extraContext);
+
+ // when
+ proxy.execute();
+ Object action = proxy.getAction();
+
+ //then
+ assertEquals("S2-047", ((SimpleAction) action).getBlah());
+ assertFalse("proxied action is accessible!",
+ (Boolean) MethodUtils.invokeMethod(action, "isExposeProxy"));
+ }
}
diff --git a/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-xwork.xml b/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-xwork.xml
index 988956f2e..928d37f31 100644
--- a/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-xwork.xml
+++ b/xwork-core/src/test/resources/com/opensymphony/xwork2/spring/actionContext-xwork.xml
@@ -8,6 +8,11 @@
+
+
+
+
@@ -19,5 +24,10 @@
+
+
+
+
+