| /* |
| * 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 com.opensymphony.xwork2.interceptor; |
| |
| import com.opensymphony.xwork2.Action; |
| import com.opensymphony.xwork2.ActionContext; |
| import com.opensymphony.xwork2.ActionProxy; |
| import com.opensymphony.xwork2.ModelDrivenAction; |
| import com.opensymphony.xwork2.SimpleAction; |
| import com.opensymphony.xwork2.TestBean; |
| import com.opensymphony.xwork2.TextProvider; |
| import com.opensymphony.xwork2.XWorkTestCase; |
| import com.opensymphony.xwork2.config.entities.ActionConfig; |
| import com.opensymphony.xwork2.config.providers.MockConfigurationProvider; |
| import com.opensymphony.xwork2.config.providers.StrutsDefaultConfigurationProvider; |
| import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; |
| import com.opensymphony.xwork2.conversion.impl.XWorkConverter; |
| import com.opensymphony.xwork2.mock.MockActionInvocation; |
| import com.opensymphony.xwork2.ognl.OgnlValueStack; |
| import com.opensymphony.xwork2.ognl.OgnlValueStackFactory; |
| import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor; |
| import com.opensymphony.xwork2.util.CompoundRoot; |
| import com.opensymphony.xwork2.util.ValueStack; |
| import com.opensymphony.xwork2.util.ValueStackFactory; |
| import com.opensymphony.xwork2.util.reflection.ReflectionContextState; |
| import ognl.OgnlContext; |
| import ognl.PropertyAccessor; |
| import org.apache.struts2.config.StrutsXmlConfigurationProvider; |
| import org.apache.struts2.dispatcher.HttpParameters; |
| import org.junit.Assert; |
| |
| import java.io.File; |
| import java.lang.reflect.Field; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| |
| |
| /** |
| * Unit test for {@link ParametersInterceptor}. |
| * |
| * @author Jason Carreira |
| */ |
| public class ParametersInterceptorTest extends XWorkTestCase { |
| |
| public void testParameterNameAware() { |
| ParametersInterceptor pi = createParametersInterceptor(); |
| final Map<String, Object> actual = injectValueStackFactory(pi); |
| ValueStack stack = createStubValueStack(actual); |
| final Map<String, Object> expected = new HashMap<String, Object>() { |
| { |
| put("fooKey", "fooValue"); |
| put("barKey", "barValue"); |
| } |
| }; |
| Object a = new ParameterNameAware() { |
| public boolean acceptableParameterName(String parameterName) { |
| return expected.containsKey(parameterName); |
| } |
| }; |
| final Map<String, Object> parameters = new HashMap<String, Object>() { |
| { |
| put("fooKey", "fooValue"); |
| put("barKey", "barValue"); |
| put("error-key", "error"); |
| put("error key", "error"); |
| put("error:key", "error"); |
| put("error+key", "error"); |
| put("test%test", "test%test"); |
| } |
| }; |
| pi.setParameters(a, stack, HttpParameters.create(parameters).build()); |
| assertEquals(expected, actual); |
| } |
| |
| public void testInsecureParameters() throws Exception { |
| // given |
| loadConfigurationProviders(new StrutsDefaultConfigurationProvider(), new StrutsXmlConfigurationProvider("xwork-param-test.xml")); |
| final Map<String, Object> params = new HashMap<String, Object>() { |
| { |
| put("name", "(#context[\"xwork.MethodAccessor.denyMethodExecution\"]= new " + |
| "java.lang.Boolean(false), #_memberAccess[\"allowStaticMethodAccess\"]= new java.lang.Boolean(true), " + |
| "@java.lang.Runtime@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)"); |
| put("top['name'](0)", "true"); |
| put("expression", "#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),#req=@org.apache.struts2.ServletActionContext@getRequest(),#resp=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#resp.println(#req.getRealPath('/')),#resp.close()"); |
| } |
| }; |
| |
| ParametersInterceptor pi = new ParametersInterceptor(); |
| container.inject(pi); |
| ValueStack vs = ActionContext.getContext().getValueStack(); |
| |
| // when |
| ValidateAction action = new ValidateAction(); |
| pi.setParameters(action, vs, HttpParameters.create(params).build()); |
| |
| // then |
| assertEquals(3, action.getActionMessages().size()); |
| |
| String msg1 = action.getActionMessage(0); |
| String msg2 = action.getActionMessage(1); |
| String msg3 = action.getActionMessage(2); |
| |
| assertEquals("Error setting expression 'expression' with value '#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),#req=@org.apache.struts2.ServletActionContext@getRequest(),#resp=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#resp.println(#req.getRealPath('/')),#resp.close()'", msg1); |
| assertEquals("Error setting expression 'name' with value '(#context[\"xwork.MethodAccessor.denyMethodExecution\"]= new java.lang.Boolean(false), #_memberAccess[\"allowStaticMethodAccess\"]= new java.lang.Boolean(true), @java.lang.Runtime@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)'", msg2); |
| assertEquals("Error setting expression 'top['name'](0)' with value 'true'", msg3); |
| assertNull(action.getName()); |
| } |
| |
| public void testClassPollutionBlockedByPattern() throws Exception { |
| // given |
| final String pollution1 = "class.classLoader.jarPath"; |
| final String pollution2 = "model.class.classLoader.jarPath"; |
| |
| loadConfigurationProviders(new StrutsDefaultConfigurationProvider(), new StrutsXmlConfigurationProvider("xwork-param-test.xml")); |
| final Map<String, Object> params = new HashMap<String, Object>() { |
| { |
| put(pollution1, "bad"); |
| put(pollution2, "very bad"); |
| } |
| }; |
| |
| final Map<String, Boolean> excluded = new HashMap<>(); |
| ParametersInterceptor pi = new ParametersInterceptor() { |
| |
| @Override |
| protected boolean isExcluded(String paramName) { |
| boolean result = super.isExcluded(paramName); |
| excluded.put(paramName, result); |
| return result; |
| } |
| |
| }; |
| |
| container.inject(pi); |
| ValueStack vs = ActionContext.getContext().getValueStack(); |
| |
| // when |
| ValidateAction action = new ValidateAction(); |
| pi.setParameters(action, vs, HttpParameters.create(params).build()); |
| |
| // then |
| assertEquals(0, action.getActionMessages().size()); |
| assertTrue(excluded.get(pollution1)); |
| assertTrue(excluded.get(pollution2)); |
| } |
| |
| public void testClassPollutionBlockedByOgnl() throws Exception { |
| // given |
| final String pollution1 = "class.classLoader.jarPath"; |
| final String pollution2 = "model.class.classLoader.jarPath"; |
| final String pollution3 = "class.classLoader.defaultAssertionStatus"; |
| |
| loadConfigurationProviders(new StrutsDefaultConfigurationProvider(), new StrutsXmlConfigurationProvider("xwork-class-param-test.xml")); |
| final Map<String, Object> params = new HashMap<String, Object>() { |
| { |
| put(pollution1, "bad"); |
| put(pollution2, "very bad"); |
| put(pollution3, true); |
| } |
| }; |
| |
| final Map<String, Boolean> excluded = new HashMap<>(); |
| ParametersInterceptor pi = new ParametersInterceptor() { |
| |
| @Override |
| protected boolean isExcluded(String paramName) { |
| boolean result = super.isExcluded(paramName); |
| excluded.put(paramName, result); |
| return result; |
| } |
| |
| }; |
| |
| container.inject(pi); |
| ValueStack vs = ActionContext.getContext().getValueStack(); |
| |
| // when |
| ValidateAction action = new ValidateAction(); |
| pi.setParameters(action, vs, HttpParameters.create(params).build()); |
| |
| // then |
| assertEquals(3, action.getActionMessages().size()); |
| |
| String msg1 = action.getActionMessage(0); |
| String msg2 = action.getActionMessage(1); |
| String msg3 = action.getActionMessage(2); |
| |
| assertEquals("Error setting expression 'class.classLoader.defaultAssertionStatus' with value 'true'", msg1); |
| assertEquals("Error setting expression 'class.classLoader.jarPath' with value 'bad'", msg2); |
| assertEquals("Error setting expression 'model.class.classLoader.jarPath' with value 'very bad'", msg3); |
| |
| assertFalse(excluded.get(pollution1)); |
| assertFalse(excluded.get(pollution2)); |
| assertFalse(excluded.get(pollution3)); |
| } |
| |
| public void testDoesNotAllowMethodInvocations() throws Exception { |
| Map<String, Object> params = new HashMap<>(); |
| params.put("@java.lang.System@exit(1).dummy", "dumb value"); |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.MODEL_DRIVEN_PARAM_TEST, null, extraContext.getContextMap()); |
| assertEquals(Action.SUCCESS, proxy.execute()); |
| |
| String property = System.getProperty("xwork.security.test"); |
| assertNull(property); |
| } |
| |
| public void testModelDrivenParameters() throws Exception { |
| Map<String, Object> params = new HashMap<>(); |
| final String fooVal = "com.opensymphony.xwork2.interceptor.ParametersInterceptorTest.foo"; |
| params.put("foo", fooVal); |
| |
| final String nameVal = "com.opensymphony.xwork2.interceptor.ParametersInterceptorTest.name"; |
| params.put("name", nameVal); |
| params.put("count", "15"); |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.MODEL_DRIVEN_PARAM_TEST, null, extraContext.getContextMap()); |
| assertEquals(Action.SUCCESS, proxy.execute()); |
| |
| ModelDrivenAction action = (ModelDrivenAction) proxy.getAction(); |
| TestBean model = (TestBean) action.getModel(); |
| assertEquals(nameVal, model.getName()); |
| assertEquals(15, model.getCount()); |
| assertEquals(fooVal, action.getFoo()); |
| } |
| |
| public void testParametersDoesNotAffectSession() throws Exception { |
| Map<String, Object> params = new HashMap<>(); |
| params.put("blah", "This is blah"); |
| params.put("#session.foo", "Foo"); |
| params.put("\u0023session['user']", "0wn3d"); |
| params.put("\\u0023session['user']", "0wn3d"); |
| params.put("\u0023session.user2", "0wn3d"); |
| params.put("\\u0023session.user2", "0wn3d"); |
| params.put("('\u0023'%20%2b%20'session['user3']')(unused)", "0wn3d"); |
| params.put("('\\u0023' + 'session[\\'user4\\']')(unused)", "0wn3d"); |
| params.put("('\u0023'%2b'session['user5']')(unused)", "0wn3d"); |
| params.put("('\\u0023'%2b'session['user5']')(unused)", "0wn3d"); |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap()); |
| ValueStack stack = proxy.getInvocation().getStack(); |
| HashMap<String, Object> session = new HashMap<>(); |
| stack.getContext().put("session", session); |
| proxy.execute(); |
| assertEquals("This is blah", ((SimpleAction) proxy.getAction()).getBlah()); |
| assertNull(session.get("foo")); |
| assertNull(session.get("user")); |
| assertNull(session.get("user2")); |
| assertNull(session.get("user3")); |
| assertNull(session.get("user4")); |
| assertNull(session.get("user5")); |
| } |
| |
| public void testArrayClassPollutionBlockedByPattern() throws Exception { |
| // given |
| final String pollution1 = "model.class.classLoader.jarPath"; |
| final String pollution2 = "model['class']['classLoader']['jarPath']"; |
| final String pollution3 = "model[\"class\"]['classLoader']['jarPath']"; |
| final String pollution4 = "class.classLoader.jarPath"; |
| final String pollution5 = "class['classLoader']['jarPath']"; |
| final String pollution6 = "class[\"classLoader\"]['jarPath']"; |
| |
| loadConfigurationProviders(new StrutsDefaultConfigurationProvider(), new StrutsXmlConfigurationProvider("xwork-param-test.xml")); |
| final Map<String, Object> params = new HashMap<String, Object>() { |
| { |
| put(pollution1, "bad"); |
| put(pollution2, "bad"); |
| put(pollution3, "bad"); |
| put(pollution4, "bad"); |
| put(pollution5, "bad"); |
| put(pollution6, "bad"); |
| } |
| }; |
| |
| final Map<String, Boolean> excluded = new HashMap<String, Boolean>(); |
| ParametersInterceptor pi = new ParametersInterceptor() { |
| |
| @Override |
| protected boolean isExcluded(String paramName) { |
| boolean result = super.isExcluded(paramName); |
| excluded.put(paramName, result); |
| return result; |
| } |
| |
| }; |
| |
| container.inject(pi); |
| ValueStack vs = ActionContext.getContext().getValueStack(); |
| |
| // when |
| ValidateAction action = new ValidateAction(); |
| pi.setParameters(action, vs, HttpParameters.create(params).build()); |
| |
| // then |
| assertEquals(0, action.getActionMessages().size()); |
| assertTrue(excluded.get(pollution1)); |
| assertTrue(excluded.get(pollution2)); |
| assertTrue(excluded.get(pollution3)); |
| assertTrue(excluded.get(pollution4)); |
| assertTrue(excluded.get(pollution5)); |
| assertTrue(excluded.get(pollution6)); |
| } |
| |
| public void testAccessToOgnlInternals() throws Exception { |
| // given |
| Map<String, Object> params = new HashMap<>(); |
| params.put("blah", "This is blah"); |
| params.put("('\\u0023_memberAccess[\\'allowStaticFieldAccess\\']')(meh)", "true"); |
| params.put("('(aaa)(('\\u0023context[\\'xwork.MethodAccessor.denyMethodExecution\\']\\u003d\\u0023foo')(\\u0023foo\\u003dnew java.lang.Boolean(\"true\")))", ""); |
| params.put("(asdf)(('\\u0023rt.exit(1)')(\\u0023rt\\u003d@java.lang.Runtime@getRuntime()))", "1"); |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap()); |
| ValueStack stack = proxy.getInvocation().getStack(); |
| |
| // when |
| proxy.execute(); |
| proxy.getAction(); |
| |
| //then |
| assertEquals("This is blah", ((SimpleAction) proxy.getAction()).getBlah()); |
| Field field = ReflectionContextState.class.getField("DENY_METHOD_EXECUTION"); |
| boolean allowStaticFieldAccess = ((OgnlContext) stack.getContext()).getMemberAccess().isAccessible(stack.getContext(), proxy.getAction(), field, ""); |
| assertFalse(allowStaticFieldAccess); |
| } |
| |
| public void testParameters() throws Exception { |
| Map<String, Object> params = new HashMap<>(); |
| params.put("blah", "This is blah"); |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap()); |
| proxy.execute(); |
| assertEquals("This is blah", ((SimpleAction) proxy.getAction()).getBlah()); |
| } |
| |
| public void testParametersWithSpacesInTheName() throws Exception { |
| Map<String, Object> params = new HashMap<>(); |
| params.put("theProtectedMap['p0 p1']", "test1"); |
| params.put("theProtectedMap['p0p1 ']", "test2"); |
| params.put("theProtectedMap[' p0p1 ']", "test3"); |
| params.put("theProtectedMap[' p0 p1 ']", "test4"); |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap()); |
| proxy.execute(); |
| Map<String, String> existingMap = ((SimpleAction) proxy.getAction()).getTheProtectedMap(); |
| assertEquals(0, existingMap.size()); |
| } |
| |
| public void testParametersWithChineseInTheName() throws Exception { |
| Map<String, Object> params = new HashMap<>(); |
| params.put("theProtectedMap['名字']", "test1"); |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap()); |
| proxy.execute(); |
| Map<String, String> existingMap = ((SimpleAction) proxy.getAction()).getTheProtectedMap(); |
| assertEquals(1, existingMap.size()); |
| } |
| |
| public void testLargeParameterNameWithDefaultLimit() throws Exception { |
| ParametersInterceptor parametersInterceptor = createParametersInterceptor(); |
| doTestParameterNameLengthRestriction(parametersInterceptor, ParametersInterceptor.PARAM_NAME_MAX_LENGTH); |
| } |
| |
| public void testLargeParameterNameWithCustomLimit() throws Exception { |
| ParametersInterceptor parametersInterceptor = createParametersInterceptor(); |
| int limit = 20; |
| parametersInterceptor.setParamNameMaxLength(limit); |
| doTestParameterNameLengthRestriction(parametersInterceptor, limit); |
| } |
| |
| private void doTestParameterNameLengthRestriction(ParametersInterceptor parametersInterceptor, |
| int paramNameMaxLength) { |
| StringBuilder sb = new StringBuilder(); |
| for (int i = 0; i < paramNameMaxLength + 1; i++) { |
| sb.append("x"); |
| } |
| |
| Map<String, Object> actual = new LinkedHashMap<>(); |
| parametersInterceptor.setValueStackFactory(createValueStackFactory(actual)); |
| ValueStack stack = createStubValueStack(actual); |
| |
| Map<String, Object> parameters = new HashMap<>(); |
| parameters.put(sb.toString(), ""); |
| parameters.put("huuhaa", ""); |
| |
| Action action = new SimpleAction(); |
| parametersInterceptor.setParameters(action, stack, HttpParameters.create(parameters).build()); |
| assertEquals(1, actual.size()); |
| } |
| |
| public void testExcludedTrickyParameters() throws Exception { |
| Map<String, Object> params = new HashMap<String, Object>() { |
| { |
| put("blah", "This is blah"); |
| put("name", "try_1"); |
| put("(name)", "try_2"); |
| put("['name']", "try_3"); |
| put("['na' + 'me']", "try_4"); |
| put("{name}[0]", "try_5"); |
| put("(new string{'name'})[0]", "try_6"); |
| put("#{key: 'name'}.key", "try_7"); |
| |
| } |
| }; |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap()); |
| |
| ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME); |
| ParametersInterceptor pi = (ParametersInterceptor) config.getInterceptors().get(0).getInterceptor(); |
| pi.setExcludeParams("name"); |
| |
| proxy.execute(); |
| |
| SimpleAction action = (SimpleAction) proxy.getAction(); |
| assertNull(action.getName()); |
| assertEquals("This is blah", (action).getBlah()); |
| } |
| |
| public void testAcceptedTrickyParameters() throws Exception { |
| Map<String, Object> params = new HashMap<String, Object>() { |
| { |
| put("blah", "This is blah"); |
| put("baz", "123"); |
| put("name", "try_1"); |
| put("(name)", "try_2"); |
| put("['name']", "try_3"); |
| put("['na' + 'me']", "try_4"); |
| put("{name}[0]", "try_5"); |
| put("(new string{'name'})[0]", "try_6"); |
| put("#{key: 'name'}.key", "try_7"); |
| } |
| }; |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap()); |
| |
| ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME); |
| ParametersInterceptor pi = (ParametersInterceptor) config.getInterceptors().get(0).getInterceptor(); |
| pi.setAcceptParamNames("blah, baz"); |
| |
| proxy.execute(); |
| |
| SimpleAction action = (SimpleAction) proxy.getAction(); |
| assertNull("try_1", action.getName()); |
| assertEquals("This is blah", (action).getBlah()); |
| assertEquals(123, action.getBaz()); |
| } |
| |
| |
| public void testParametersNotAccessPrivateVariables() throws Exception { |
| Map<String, Object> params = new HashMap<>(); |
| params.put("protectedMap.foo", "This is blah"); |
| params.put("theProtectedMap.boo", "This is blah"); |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap()); |
| proxy.execute(); |
| SimpleAction action = (SimpleAction) proxy.getAction(); |
| assertEquals(1, action.getTheProtectedMap().size()); |
| assertNotNull(action.getTheProtectedMap().get("boo")); |
| assertNull(action.getTheProtectedMap().get("foo")); |
| } |
| |
| public void testParametersNotAccessProtectedMethods() throws Exception { |
| Map<String, Object> params = new HashMap<>(); |
| params.put("theSemiProtectedMap.foo", "This is blah"); |
| params.put("theProtectedMap.boo", "This is blah"); |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap()); |
| proxy.execute(); |
| SimpleAction action = (SimpleAction) proxy.getAction(); |
| assertEquals(1, action.getTheProtectedMap().size()); |
| assertNotNull(action.getTheProtectedMap().get("boo")); |
| assertNull(action.getTheProtectedMap().get("foo")); |
| } |
| |
| /** |
| * This test demonstrates a vulnerability which allows to execute arbitrary code. |
| * For further details and explanations see https://cwiki.apache.org/confluence/display/WW/S2-009 |
| * |
| * @throws Exception |
| */ |
| public void testEvalExpressionAsParameterName() throws Exception { |
| Map<String, Object> params = new HashMap<>(); |
| params.put("blah", "(#context[\"xwork.MethodAccessor.denyMethodExecution\"]= new " + |
| "java.lang.Boolean(false), #_memberAccess[\"allowStaticMethodAccess\"]= new java.lang.Boolean(true), " + |
| "@java.lang.Runtime@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)"); |
| params.put("top['blah'](0)", "true"); |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap()); |
| proxy.execute(); |
| @SuppressWarnings("unused") |
| SimpleAction action = (SimpleAction) proxy.getAction(); |
| File pwn = new File("/tmp/PWNAGE"); |
| boolean dirExists = pwn.exists(); |
| @SuppressWarnings("unused") |
| boolean deleted = pwn.delete(); |
| Assert.assertFalse("Remote exploit: The PWN folder has been created", dirExists); |
| } |
| |
| public void testParametersOverwriteField() throws Exception { |
| Map<String, Object> params = new LinkedHashMap<>(); |
| params.put("existingMap.boo", "This is blah"); |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap()); |
| proxy.execute(); |
| SimpleAction action = (SimpleAction) proxy.getAction(); |
| assertEquals(1, action.getTheExistingMap().size()); |
| assertNotNull(action.getTheExistingMap().get("boo")); |
| assertNull(action.getTheExistingMap().get("existingKey")); |
| } |
| |
| public void testNonexistentParametersGetLoggedInDevMode() throws Exception { |
| XmlConfigurationProvider provider = new StrutsXmlConfigurationProvider("xwork-test-beans.xml"); |
| container.inject(provider); |
| loadConfigurationProviders(provider, |
| new MockConfigurationProvider(Collections.singletonMap("struts.devMode", "true"))); |
| Map<String, Object> params = new HashMap<>(); |
| params.put("not_a_property", "There is no action property named like this"); |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME); |
| container.inject(config.getInterceptors().get(0).getInterceptor()); |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap()); |
| proxy.execute(); |
| final String actionMessage = "" + ((SimpleAction) proxy.getAction()).getActionMessages().toArray()[0]; |
| assertTrue(actionMessage.contains("Error setting expression 'not_a_property' with value 'There is no action property named like this'")); |
| } |
| |
| public void testNonexistentParametersAreIgnoredInProductionMode() throws Exception { |
| XmlConfigurationProvider provider = new StrutsXmlConfigurationProvider("xwork-test-beans.xml"); |
| container.inject(provider); |
| loadConfigurationProviders(provider, |
| new MockConfigurationProvider(Collections.singletonMap("struts.devMode", "false"))); |
| Map<String, Object> params = new HashMap<>(); |
| params.put("not_a_property", "There is no action property named like this"); |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME); |
| container.inject(config.getInterceptors().get(0).getInterceptor()); |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap()); |
| proxy.execute(); |
| assertTrue(((SimpleAction) proxy.getAction()).getActionMessages().isEmpty()); |
| } |
| |
| public void testNoParametersAction() throws Exception { |
| ParametersInterceptor interceptor = new ParametersInterceptor(); |
| interceptor.init(); |
| |
| MockActionInvocation mai = new MockActionInvocation(); |
| Action action = new NoParametersAction(); |
| mai.setAction(action); |
| |
| interceptor.doIntercept(mai); |
| interceptor.destroy(); |
| } |
| |
| public void testNoOrdered() throws Exception { |
| ParametersInterceptor pi = createParametersInterceptor(); |
| final Map<String, Object> actual = new LinkedHashMap<>(); |
| pi.setValueStackFactory(createValueStackFactory(actual)); |
| ValueStack stack = createStubValueStack(actual); |
| |
| Map<String, Object> parameters = new HashMap<>(); |
| parameters.put("user.address.city", "London"); |
| parameters.put("user.name", "Superman"); |
| |
| Action action = new SimpleAction(); |
| pi.setParameters(action, stack, HttpParameters.create(parameters).build()); |
| |
| assertEquals("ordered should be false by default", false, pi.isOrdered()); |
| assertEquals(2, actual.size()); |
| assertEquals("London", actual.get("user.address.city")); |
| assertEquals("Superman", actual.get("user.name")); |
| |
| // is not ordered |
| List<Object> values = new ArrayList<Object>(actual.values()); |
| assertEquals("London", values.get(0)); |
| assertEquals("Superman", values.get(1)); |
| } |
| |
| public void testOrdered() throws Exception { |
| ParametersInterceptor pi = new ParametersInterceptor(); |
| pi.setOrdered(true); |
| container.inject(pi); |
| final Map<String, Object> actual = new LinkedHashMap<>(); |
| pi.setValueStackFactory(createValueStackFactory(actual)); |
| ValueStack stack = createStubValueStack(actual); |
| |
| Map<String, Object> parameters = new HashMap<>(); |
| parameters.put("user.address.city", "London"); |
| parameters.put("user.address['postal']", "QJR387"); |
| parameters.put("user.name", "Superman"); |
| |
| Action action = new SimpleAction(); |
| pi.setParameters(action, stack, HttpParameters.create(parameters).build()); |
| |
| assertEquals(true, pi.isOrdered()); |
| assertEquals(3, actual.size()); |
| assertEquals("London", actual.get("user.address.city")); |
| assertEquals("QJR387", actual.get("user.address['postal']")); |
| assertEquals("Superman", actual.get("user.name")); |
| |
| // should be ordered so user.name should be first |
| List<Object> values = new ArrayList<Object>(actual.values()); |
| assertEquals("Superman", values.get(0)); |
| assertEquals("London", values.get(1)); |
| assertEquals("QJR387", values.get(2)); |
| } |
| |
| public void testSetOrdered() throws Exception { |
| ParametersInterceptor pi = createParametersInterceptor(); |
| assertEquals("ordered should be false by default", false, pi.isOrdered()); |
| pi.setOrdered(true); |
| assertEquals(true, pi.isOrdered()); |
| } |
| |
| public void testExcludedParametersAreIgnored() throws Exception { |
| ParametersInterceptor pi = createParametersInterceptor(); |
| pi.setExcludeParams("dojo\\..*"); |
| final Map<String, Object> actual = injectValueStackFactory(pi); |
| ValueStack stack = injectValueStack(actual); |
| |
| final Map<String, Object> expected = new HashMap<String, Object>() { |
| { |
| put("fooKey", "fooValue"); |
| } |
| }; |
| |
| Map<String, Object> parameters = new HashMap<String, Object>() { |
| { |
| put("dojo.test", "dojoValue"); |
| put("fooKey", "fooValue"); |
| } |
| }; |
| pi.setParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build()); |
| assertEquals(expected, actual); |
| } |
| |
| public void testInternalParametersAreIgnored() throws Exception { |
| // given |
| ParametersInterceptor interceptor = createParametersInterceptor(); |
| final Map<String, Object> actual = injectValueStackFactory(interceptor); |
| ValueStack stack = injectValueStack(actual); |
| |
| |
| final Map<String, Object> expected = new HashMap<String, Object>() { |
| { |
| put("ordinary.bean", "value"); |
| } |
| }; |
| |
| Map<String, Object> parameters = new HashMap<String, Object>() { |
| { |
| put("ordinary.bean", "value"); |
| put("#some.internal.object", "true"); |
| put("(bla)#some.internal.object", "true"); |
| put("#some.internal.object(bla)#some.internal.object", "true"); |
| put("#_some.internal.object", "true"); |
| put("\u0023_some.internal.object", "true"); |
| put("\u0023_some.internal.object,[dfd],bla(\u0023_some.internal.object)", "true"); |
| put("\\u0023_some.internal.object", "true"); |
| } |
| }; |
| |
| // when |
| interceptor.setParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build()); |
| |
| // then |
| assertEquals(expected, actual); |
| } |
| |
| public void testDMIMethodsAreIgnored() throws Exception { |
| // given |
| ParametersInterceptor interceptor = createParametersInterceptor(); |
| final Map<String, Object> actual = injectValueStackFactory(interceptor); |
| ValueStack stack = injectValueStack(actual); |
| |
| final Map<String, Object> expected = new HashMap<String, Object>() { |
| { |
| put("ordinary.bean", "value"); |
| } |
| }; |
| |
| Map<String, Object> parameters = new HashMap<String, Object>() { |
| { |
| put("ordinary.bean", "value"); |
| put("action:", "myAction"); |
| put("method:", "doExecute"); |
| } |
| }; |
| |
| // when |
| interceptor.setParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build()); |
| |
| // then |
| assertEquals(expected, actual); |
| } |
| |
| public void testBeanListSingleValue() throws Exception { |
| Map<String, Object> params = new HashMap<>(); |
| params.put("beanList.name", new String[]{"Superman"}); |
| |
| ActionContext extraContext = ActionContext.of().withParameters(HttpParameters.create(params).build()); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", |
| MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap()); |
| proxy.execute(); |
| SimpleAction action = (SimpleAction) proxy.getAction(); |
| assertNotNull(action); |
| assertNotNull(action.getBeanList()); |
| assertFalse(action.getBeanList().isEmpty()); |
| } |
| |
| public void testExcludedParametersValuesAreIgnored() throws Exception { |
| ParametersInterceptor pi = createParametersInterceptor(); |
| // Contains (based on pattern) |
| pi.setExcludedValuePatterns(".*\\$\\{.*?\\}.*,.*%\\{.*?\\}.*"); |
| |
| assertTrue("${2*2} was excluded by isParamValueExcluded", pi.isParamValueExcluded("${2*2}")); |
| |
| final Map<String, Object> actual = injectValueStackFactory(pi); |
| ValueStack stack = injectValueStack(actual); |
| |
| final Map<String, Object> expected = new HashMap<String, Object>() { |
| { |
| put("fooKey", "fooValue"); |
| put("fooKey2", ""); |
| } |
| }; |
| |
| Map<String, Object> parameters = new HashMap<String, Object>() { |
| { |
| put("barKey$", "${2+2}"); |
| put("barKey2$", "foo${2+2}"); |
| put("barKey3$", "foo${2+2}foo"); |
| put("barKey%", "%{2+2}"); |
| put("barKey2%", "foo%{2+2}"); |
| put("barKey3%", "foo%{2+2}foo"); |
| put("allowedKey", "${foo}"); |
| put("allowedKey2", "%{bar}"); |
| put("fooKey", "fooValue"); |
| put("fooKey2", ""); |
| } |
| }; |
| pi.setParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build()); |
| assertEquals(expected, actual); |
| } |
| |
| public void testAcceptedParametersValuesAreIgnored() throws Exception { |
| ParametersInterceptor pi = createParametersInterceptor(); |
| // Starts with (based on pattern) |
| pi.setAcceptedValuePatterns("^\\$\\{foo\\}.*,^%\\{bar\\}.*,^fooValue"); |
| |
| assertTrue("${foo} was allowed by isParamValueAccepted", pi.isParamValueAccepted("${foo}")); |
| |
| final Map<String, Object> actual = injectValueStackFactory(pi); |
| ValueStack stack = injectValueStack(actual); |
| |
| final Map<String, Object> expected = new HashMap<String, Object>() { |
| { |
| put("fooKey", "fooValue"); |
| put("fooKey2", ""); |
| put("allowedKey", "${foo}"); |
| put("allowedKey2", "%{bar}"); |
| } |
| }; |
| |
| Map<String, Object> parameters = new HashMap<String, Object>() { |
| { |
| put("barKey$", "${2+2}"); |
| put("barKey2$", "foo${2+2}"); |
| put("barKey3$", "foo${2+2}foo"); |
| put("barKey%", "%{2+2}"); |
| put("barKey2%", "foo%{2+2}"); |
| put("barKey3%", "foo%{2+2}foo"); |
| put("allowedKey", "${foo}"); |
| put("allowedKey2", "%{bar}"); |
| put("fooKey", "fooValue"); |
| put("fooKey2", ""); |
| } |
| }; |
| pi.setParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build()); |
| assertEquals(expected, actual); |
| } |
| |
| public void testAcceptedAndExcludedParametersValuesAreIgnored() throws Exception { |
| ParametersInterceptor pi = createParametersInterceptor(); |
| // Starts with (based on pattern) |
| pi.setAcceptedValuePatterns("^\\$\\{foo\\}.*,^%\\{bar\\}.*,^fooValue"); |
| pi.setExcludedValuePatterns(".*\\$\\{2.*2\\}.*,.*\\%\\{2.*2\\}.*"); |
| |
| assertTrue("${foo} was allowed by isParamValueAccepted", pi.isParamValueAccepted("${foo}")); |
| assertTrue("${2*2} was excluded by isParamValueExcluded", pi.isParamValueExcluded("${2*2}")); |
| |
| final Map<String, Object> actual = injectValueStackFactory(pi); |
| ValueStack stack = injectValueStack(actual); |
| |
| final Map<String, Object> expected = new HashMap<String, Object>() { |
| { |
| put("fooKey", "fooValue"); |
| put("allowedKey", "${foo}"); |
| put("allowedKey2", "%{bar}"); |
| put("fooKey2", ""); |
| } |
| }; |
| |
| Map<String, Object> parameters = new HashMap<String, Object>() { |
| { |
| put("barKey$", "${2+2}"); |
| put("barKey2$", "foo${2+2}"); |
| put("barKey%", "%{2+2}"); |
| put("barKey2%", "foo%{2+2}"); |
| put("barKey3", "nothing"); |
| |
| put("allowedKey", "${foo}"); |
| put("allowedKey2", "%{bar}"); |
| put("fooKey", "fooValue"); |
| put("fooKey2", ""); |
| } |
| }; |
| pi.setParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build()); |
| assertEquals(expected, actual); |
| } |
| |
| public void testExcludedParametersValuesAreIgnoredWithParameterValueAware() throws Exception { |
| ParametersInterceptor pi = createParametersInterceptor(); |
| // Contains (based on pattern) |
| pi.setExcludedValuePatterns(".*\\$\\{.*?\\}.*,.*%\\{.*?\\}.*"); |
| |
| assertTrue("${2*2} was excluded by isParamValueExcluded", pi.isParamValueExcluded("${2*2}")); |
| |
| final Map<String, Object> actual = injectValueStackFactory(pi); |
| ValueStack stack = injectValueStack(actual); |
| |
| final Map<String, Object> expected = new HashMap<String, Object>() { |
| { |
| // acceptableParameterValue only allows fooValue even though fooKey2 and fooKey3 pass the excludeValuePatterns check |
| put("fooKey", "fooValue"); |
| } |
| }; |
| |
| Object a = new ParameterValueAware() { |
| @Override |
| public boolean acceptableParameterValue(String parameterValue) { |
| // Only fooValue will be allowed because the excludeValuePatterns will block ${2+2} |
| return parameterValue.equals("fooValue") || parameterValue.equals("${2+2}"); |
| } |
| }; |
| |
| Map<String, Object> parameters = new HashMap<String, Object>() { |
| { |
| put("barKey$", "${2+2}"); |
| put("barKey2$", "foo${2+2}"); |
| put("barKey3$", "foo${2+2}foo"); |
| put("barKey%", "%{2+2}"); |
| put("barKey2%", "foo%{2+2}"); |
| put("barKey3%", "foo%{2+2}foo"); |
| put("allowedKey", "${foo}"); |
| put("allowedKey2", "%{bar}"); |
| put("fooKey", "fooValue"); |
| put("fooKey2", "fooValue2"); |
| put("fooKey3", ""); |
| } |
| }; |
| pi.setParameters(a, stack, HttpParameters.create(parameters).build()); |
| assertEquals(expected, actual); |
| } |
| |
| |
| private ValueStack injectValueStack(Map<String, Object> actual) { |
| ValueStack stack = createStubValueStack(actual); |
| container.inject(stack); |
| return stack; |
| } |
| |
| private Map<String, Object> injectValueStackFactory(ParametersInterceptor interceptor) { |
| final Map<String, Object> actual = new HashMap<>(); |
| interceptor.setValueStackFactory(createValueStackFactory(actual)); |
| return actual; |
| } |
| |
| private ParametersInterceptor createParametersInterceptor() { |
| ParametersInterceptor pi = new ParametersInterceptor(); |
| container.inject(pi); |
| return pi; |
| } |
| |
| private ValueStackFactory createValueStackFactory(final Map<String, Object> context) { |
| OgnlValueStackFactory factory = new OgnlValueStackFactory() { |
| @Override |
| public ValueStack createValueStack(ValueStack stack) { |
| return createStubValueStack(context); |
| } |
| }; |
| container.inject(factory); |
| return factory; |
| } |
| |
| private ValueStack createStubValueStack(final Map<String, Object> actual) { |
| ValueStack stack = new OgnlValueStack( |
| container.getInstance(XWorkConverter.class), |
| (CompoundRootAccessor) container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()), |
| container.getInstance(TextProvider.class, "system"), true) { |
| @Override |
| public void setValue(String expr, Object value) { |
| actual.put(expr, value); |
| } |
| |
| @Override |
| public void setParameter(String expr, Object value) { |
| actual.put(expr, value); |
| } |
| }; |
| container.inject(stack); |
| return stack; |
| } |
| |
| /* |
| public void testIndexedParameters() throws Exception { |
| Map params = new HashMap(); |
| params.put("indexedProp[33]", "This is blah"); |
| |
| HashMap extraContext = new HashMap(); |
| extraContext.put(ActionContext.PARAMETERS, params); |
| |
| ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, extraContext); |
| proxy.execute(); |
| assertEquals("This is blah", ((SimpleAction) proxy.getAction()).getIndexedProp(33)); |
| } |
| */ |
| |
| |
| private class NoParametersAction implements Action, NoParameters { |
| |
| public String execute() throws Exception { |
| return SUCCESS; |
| } |
| } |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| XmlConfigurationProvider provider = new StrutsXmlConfigurationProvider("xwork-test-beans.xml"); |
| container.inject(provider); |
| loadConfigurationProviders(provider, new MockConfigurationProvider()); |
| |
| ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME); |
| container.inject(config.getInterceptors().get(0).getInterceptor()); |
| } |
| |
| } |
| |
| class ValidateAction implements ValidationAware { |
| |
| private final List<String> messages = new LinkedList<>(); |
| private String name; |
| |
| public void setActionErrors(Collection<String> errorMessages) { |
| } |
| |
| public Collection<String> getActionErrors() { |
| return null; |
| } |
| |
| public void setActionMessages(Collection<String> messages) { |
| } |
| |
| public Collection<String> getActionMessages() { |
| return messages; |
| } |
| |
| public void setFieldErrors(Map<String, List<String>> errorMap) { |
| } |
| |
| public Map<String, List<String>> getFieldErrors() { |
| return null; |
| } |
| |
| public void addActionError(String anErrorMessage) { |
| } |
| |
| public void addActionMessage(String aMessage) { |
| messages.add(aMessage); |
| } |
| |
| public void addFieldError(String fieldName, String errorMessage) { |
| } |
| |
| public boolean hasActionErrors() { |
| return false; |
| } |
| |
| public boolean hasActionMessages() { |
| return messages.size() > 0; |
| } |
| |
| public boolean hasErrors() { |
| return false; |
| } |
| |
| public boolean hasFieldErrors() { |
| return false; |
| } |
| |
| public String getActionMessage(int index) { |
| return messages.get(index); |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| public void setName(String name) { |
| this.name = name; |
| } |
| |
| } |