View Javadoc

1   package com.atlassian.xwork.interceptors;
2   
3   import com.atlassian.xwork.ParameterSafe;
4   import com.atlassian.xwork.XWorkVersionSupport;
5   import com.opensymphony.xwork.Action;
6   import com.opensymphony.xwork.ActionContext;
7   import com.opensymphony.xwork.ActionInvocation;
8   import com.opensymphony.xwork.interceptor.AroundInterceptor;
9   import com.opensymphony.xwork.interceptor.NoParameters;
10  import com.opensymphony.xwork.util.InstantiatingNullHandler;
11  import com.opensymphony.xwork.util.OgnlValueStack;
12  import com.opensymphony.xwork.util.XWorkConverter;
13  import com.opensymphony.xwork.util.XWorkMethodAccessor;
14  import org.apache.log4j.Logger;
15  
16  import java.beans.BeanInfo;
17  import java.beans.IntrospectionException;
18  import java.beans.Introspector;
19  import java.beans.PropertyDescriptor;
20  import java.lang.reflect.Method;
21  import java.util.HashMap;
22  import java.util.Map;
23  import java.util.regex.Pattern;
24  
25  /**
26   * Injects submitted form parameters into action properties. This implementation performs white-list based
27   * sanity checks on incoming parameters before allowing OGNL to perform any potentially dangerous operations on
28   * an action, closing off an entire category of parameter injection attacks.
29   * <p/>
30   * Parameters that set a value on an action directly will be allowed as will index-based setters for collections
31   * of values. However:
32   * <ol>
33   * <li> To defend against possible OGNL vulnerabilities (especially Unicode attacks), parameter names will be
34   * filtered so only ascii alphanumeric characters (plus the underscore, square brackets and apostrophes) are permitted
35   * <li> If the dot-notation is used to access some property on an action (i.e. a parameter called "search.query")
36   * the type returned from the getter (getSearch()) MUST have the @ParameterSafe annotation for the parameter
37   * to be accepted, <i>or</i> the getter method must have the @ParameterSafe annotation
38   * <li> If the map-notation is used to access some property on an action (i.e. a parameter called "map['key']")
39   * the getter method must have the @ParameterSafe annotation
40   * </ol>
41   * <p/>
42   * These last two checks (@ParameterSafe checks for dot- and map-notation) can be skipped by setting
43   * disableAnnotationChecks. When disabled this interceptor still prevents Unicode-attacks (amoungst other things)
44   * but allows dot/map traversal of any POJO retrievable from an action. To disable, use a param e.g.
45   * <pre>
46   *  &lt;interceptor name="params" class="com.atlassian.xwork12.interceptors.SafeParametersInterceptor">
47   *     &lt;param name="disableAnnotationChecks">true&lt;/param>
48   *  &lt;/interceptor>
49   * </pre>
50   * <p/>
51   * Portions of this class are copied from XWork under the Apache license, Copyright (c) 2002-2003 by OpenSymphony
52   */
53  public abstract class SafeParametersInterceptor extends AroundInterceptor
54  {
55      public static final Logger log = Logger.getLogger(SafeParametersInterceptor.class);
56  
57      private static final Pattern SAFE_PARAMETER_NAME_PATTERN = Pattern.compile("[a-zA-Z0-9\\.\\]\\[_']+");
58      private static final Pattern MAP_PARAMETER_PATTERN = Pattern.compile(".*\\['[a-zA-Z0-9_]+'\\]");
59      private final XWorkVersionSupport versionSupport;
60      private boolean disableAnnotationChecks = false;
61  
62      protected SafeParametersInterceptor(XWorkVersionSupport versionSupport)
63      {
64          this.versionSupport = versionSupport;
65      }
66  
67      protected void after(ActionInvocation dispatcher, String result) throws Exception
68      {
69      }
70  
71      public void setDisableAnnotationChecks(boolean disableAnnotationChecks)
72      {
73          this.disableAnnotationChecks = disableAnnotationChecks;
74      }
75  
76      /**
77       * The implementation of this method should evalutate if the passed in actionInvocation.getAction()
78       * is of a type {@link com.opensymphony.xwork.interceptor.NoParameters} if it is, we should not bother
79       * intercepting.
80       * <p/>
81       * The reason for this abstract class is so we are compatible with both 1.0.3 and 1.2.3 of XWork.
82       *
83       * @param actionInvocation the action invocation being intercepted
84       * @return true if we are not of type {@link com.opensymphony.xwork.interceptor.NoParameters}
85       */
86      protected boolean shouldNotIntercept(ActionInvocation actionInvocation)
87      {
88          return versionSupport.extractAction(actionInvocation) instanceof NoParameters;
89      }
90  
91      protected void before(ActionInvocation invocation) throws Exception
92      {
93  
94          if (shouldNotIntercept(invocation))
95          {
96              return;
97          }
98  
99          Action action = versionSupport.extractAction(invocation);
100 
101         //noinspection unchecked
102         final Map<String, Object> parameters = filterSafeParameters(ActionContext.getContext().getParameters(), action);
103 
104         // Copied from the XWork parameters interceptor:
105 
106         if (log.isDebugEnabled())
107         {
108             log.debug("Setting params " + parameters);
109         }
110 
111         ActionContext invocationContext = invocation.getInvocationContext();
112 
113 
114         try
115         {
116             invocationContext.put(InstantiatingNullHandler.CREATE_NULL_OBJECTS, Boolean.TRUE);
117             invocationContext.put(XWorkMethodAccessor.DENY_METHOD_EXECUTION, Boolean.TRUE);
118             invocationContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE);
119 
120             if (parameters != null)
121             {
122                 final OgnlValueStack stack = ActionContext.getContext().getValueStack();
123 
124                 for (Map.Entry<String, Object> entry : parameters.entrySet())
125                 {
126                     String name = entry.getKey();
127 
128                     stack.setValue(name, entry.getValue());
129                 }
130             }
131         }
132         finally
133         {
134             invocationContext.put(InstantiatingNullHandler.CREATE_NULL_OBJECTS, Boolean.FALSE);
135             invocationContext.put(XWorkMethodAccessor.DENY_METHOD_EXECUTION, Boolean.FALSE);
136             invocationContext.put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.FALSE);
137         }
138     }
139 
140     private Map<String, Object> filterSafeParameters(Map<String, String> parameters, Action action)
141     {
142         Map<String, Object> safeParameters = new HashMap<String, Object>();
143 
144         for (Map.Entry<String, String> entry : parameters.entrySet())
145         {
146             if (isSafeParameterName(entry.getKey(), action, disableAnnotationChecks))
147             {
148                 safeParameters.put(entry.getKey(), entry.getValue());
149             }
150         }
151 
152         return safeParameters;
153     }
154 
155     static boolean isSafeParameterName(String key, Action action)
156     {
157         return isSafeParameterName(key, action, true);
158     }
159 
160     static boolean isSafeParameterName(String key, Action action, boolean disableAnnotationChecks)
161     {
162         if (!SAFE_PARAMETER_NAME_PATTERN.matcher(key).matches())
163         {
164             return false;
165         }
166 
167         if (!disableAnnotationChecks && (key.contains(".") || MAP_PARAMETER_PATTERN.matcher(key).matches())) {
168             return isSafeComplexParameterName(key, action);
169         }
170 
171         return true;
172     }
173 
174     private static boolean isSafeComplexParameterName(String key, Action action)
175     {
176         try
177         {
178             String initialParameterName = extractInitialParameterName(key);
179             BeanInfo info = Introspector.getBeanInfo(action.getClass());
180             PropertyDescriptor[] descs = info.getPropertyDescriptors();
181 
182             for (PropertyDescriptor desc : descs)
183             {
184                 if (desc.getName().equals(initialParameterName))
185                 {
186                     if (isSafeMethod(desc.getReadMethod()))
187                     {
188                         return true;
189                     }
190                     else
191                     {
192                         log.info("Attempt to call unsafe property setter " + key + " on " + action);
193                         return false;
194                     }
195                 }
196             }
197         }
198         catch (IntrospectionException e)
199         {
200             log.warn("Error introspecting action parameter " + key + " for action " + action + ": " + e.getMessage(), e);
201         }
202 
203         return false;
204     }
205 
206     private static String extractInitialParameterName(String key)
207     {
208         if (!key.contains("[") || (key.indexOf(".") > 0 && key.indexOf("[") > key.indexOf(".")))
209         {
210             return key.substring(0, key.indexOf("."));
211         }
212         else
213         {
214             return key.substring(0, key.indexOf("["));
215         }
216     }
217 
218     private static boolean isSafeMethod(Method writeMethod)
219     {
220         return writeMethod.getAnnotation(ParameterSafe.class) != null ||
221                 writeMethod.getReturnType().getAnnotation(ParameterSafe.class) != null;
222     }
223 }