Last active
August 29, 2015 14:00
-
-
Save kawasima/c22a1c706656e4004d41 to your computer and use it in GitHub Desktop.
A workaround for CVE-2014-0114
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package example; | |
| import org.apache.commons.beanutils.BeanUtils; | |
| import org.apache.struts.Globals; | |
| import org.apache.struts.action.ActionForm; | |
| import org.apache.struts.action.ActionMapping; | |
| import org.apache.struts.action.ActionServletWrapper; | |
| import org.apache.struts.action.RequestProcessor; | |
| import org.apache.struts.config.ModuleConfig; | |
| import org.apache.struts.taglib.html.Constants; | |
| import org.apache.struts.upload.MultipartRequestHandler; | |
| import org.apache.struts.upload.MultipartRequestWrapper; | |
| import org.apache.struts.util.ModuleUtils; | |
| import org.apache.struts.util.RequestUtils; | |
| import javax.servlet.ServletException; | |
| import javax.servlet.http.HttpServletRequest; | |
| import javax.servlet.http.HttpServletResponse; | |
| import java.util.*; | |
| import java.util.regex.Pattern; | |
| /** | |
| * @author kawasima | |
| */ | |
| public class SafeRequestProcessor extends RequestProcessor { | |
| private static Pattern EXLUDE_PARAMS = Pattern.compile("(^|\\W)[cC]lass\\W"); | |
| @Override | |
| protected void processPopulate(HttpServletRequest request, | |
| HttpServletResponse response, | |
| ActionForm form, | |
| ActionMapping mapping) throws ServletException { | |
| if (form == null) { | |
| return; | |
| } | |
| // Populate the bean properties of this ActionForm instance | |
| if (log.isDebugEnabled()) { | |
| log.debug(" Populating bean properties from this request"); | |
| } | |
| form.setServlet(this.servlet); | |
| form.reset(mapping, request); | |
| if (mapping.getMultipartClass() != null) { | |
| request.setAttribute(Globals.MULTIPART_KEY, | |
| mapping.getMultipartClass()); | |
| } | |
| HashMap properties = parseParams(form, mapping.getPrefix(), mapping.getSuffix(), request); | |
| Enumeration<?> params = request.getParameterNames(); | |
| while (params.hasMoreElements()) { | |
| String paramName = (String) params.nextElement(); | |
| if (isAttack(paramName)) { | |
| throw new ServletException("Attack: " + paramName); | |
| } | |
| } | |
| try { | |
| BeanUtils.populate(form, properties); | |
| } catch(Exception e) { | |
| throw new ServletException("BeanUtils.populate", e); | |
| } | |
| if ((request.getParameter(Constants.CANCEL_PROPERTY) != null) || | |
| (request.getParameter(Constants.CANCEL_PROPERTY_X) != null)) { | |
| request.setAttribute(Globals.CANCEL_KEY, Boolean.TRUE); | |
| } | |
| } | |
| private static boolean isAttack(String target) { | |
| return EXLUDE_PARAMS.matcher(target).find(); | |
| } | |
| public static HashMap parseParams(ActionForm form, | |
| String prefix, | |
| String suffix, | |
| HttpServletRequest request) throws ServletException { | |
| // Build a list of relevant request parameters from this request | |
| HashMap properties = new HashMap(); | |
| // Iterator of parameter names | |
| Enumeration names = null; | |
| // Map for multipart parameters | |
| Map multipartParameters = null; | |
| String contentType = request.getContentType(); | |
| String method = request.getMethod(); | |
| boolean isMultipart = false; | |
| form.setMultipartRequestHandler(null); | |
| MultipartRequestHandler multipartHandler = null; | |
| if ((contentType != null) | |
| && (contentType.startsWith("multipart/form-data")) | |
| && (method.equalsIgnoreCase("POST"))) { | |
| // Get the ActionServletWrapper from the form bean | |
| ActionServletWrapper servlet = form.getServletWrapper(); | |
| // Obtain a MultipartRequestHandler | |
| multipartHandler = getMultipartHandler(request); | |
| if (multipartHandler != null) { | |
| isMultipart = true; | |
| // Set servlet and mapping info | |
| servlet.setServletFor(multipartHandler); | |
| multipartHandler.setMapping( | |
| (ActionMapping) request.getAttribute(Globals.MAPPING_KEY)); | |
| // Initialize multipart request class handler | |
| multipartHandler.handleRequest(request); | |
| //stop here if the maximum length has been exceeded | |
| Boolean maxLengthExceeded = | |
| (Boolean) request.getAttribute( | |
| MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED); | |
| if ((maxLengthExceeded != null) && (maxLengthExceeded.booleanValue())) { | |
| form.setMultipartRequestHandler(multipartHandler); | |
| return null; | |
| } | |
| //retrieve form values and put into properties | |
| multipartParameters = getAllParametersForMultipartRequest( | |
| request, multipartHandler); | |
| names = Collections.enumeration(multipartParameters.keySet()); | |
| } | |
| } | |
| if (!isMultipart) { | |
| names = request.getParameterNames(); | |
| } | |
| while (names.hasMoreElements()) { | |
| String name = (String) names.nextElement(); | |
| String stripped = name; | |
| if (prefix != null) { | |
| if (!stripped.startsWith(prefix)) { | |
| continue; | |
| } | |
| stripped = stripped.substring(prefix.length()); | |
| } | |
| if (suffix != null) { | |
| if (!stripped.endsWith(suffix)) { | |
| continue; | |
| } | |
| stripped = stripped.substring(0, stripped.length() - suffix.length()); | |
| } | |
| Object parameterValue = null; | |
| if (isMultipart) { | |
| parameterValue = multipartParameters.get(name); | |
| } else { | |
| parameterValue = request.getParameterValues(name); | |
| } | |
| // Populate parameters, except "standard" struts attributes | |
| // such as 'org.apache.struts.action.CANCEL' | |
| if (!(stripped.startsWith("org.apache.struts."))) { | |
| properties.put(stripped, parameterValue); | |
| } | |
| } | |
| form.setMultipartRequestHandler(multipartHandler); | |
| return properties; | |
| } | |
| private static MultipartRequestHandler getMultipartHandler(HttpServletRequest request) | |
| throws ServletException { | |
| MultipartRequestHandler multipartHandler = null; | |
| String multipartClass = (String) request.getAttribute(Globals.MULTIPART_KEY); | |
| request.removeAttribute(Globals.MULTIPART_KEY); | |
| // Try to initialize the mapping specific request handler | |
| if (multipartClass != null) { | |
| try { | |
| multipartHandler = (MultipartRequestHandler) RequestUtils.applicationInstance(multipartClass); | |
| } catch(ClassNotFoundException cnfe) { | |
| log.error( | |
| "MultipartRequestHandler class \"" | |
| + multipartClass | |
| + "\" in mapping class not found, " | |
| + "defaulting to global multipart class"); | |
| } catch(InstantiationException ie) { | |
| log.error( | |
| "InstantiationException when instantiating " | |
| + "MultipartRequestHandler \"" | |
| + multipartClass | |
| + "\", " | |
| + "defaulting to global multipart class, exception: " | |
| + ie.getMessage()); | |
| } catch(IllegalAccessException iae) { | |
| log.error( | |
| "IllegalAccessException when instantiating " | |
| + "MultipartRequestHandler \"" | |
| + multipartClass | |
| + "\", " | |
| + "defaulting to global multipart class, exception: " | |
| + iae.getMessage()); | |
| } | |
| if (multipartHandler != null) { | |
| return multipartHandler; | |
| } | |
| } | |
| ModuleConfig moduleConfig = | |
| ModuleUtils.getInstance().getModuleConfig(request); | |
| multipartClass = moduleConfig.getControllerConfig().getMultipartClass(); | |
| // Try to initialize the global request handler | |
| if (multipartClass != null) { | |
| try { | |
| multipartHandler = (MultipartRequestHandler) RequestUtils.applicationInstance(multipartClass); | |
| } catch(ClassNotFoundException cnfe) { | |
| throw new ServletException( | |
| "Cannot find multipart class \"" | |
| + multipartClass | |
| + "\"" | |
| + ", exception: " | |
| + cnfe.getMessage()); | |
| } catch(InstantiationException ie) { | |
| throw new ServletException( | |
| "InstantiationException when instantiating " | |
| + "multipart class \"" | |
| + multipartClass | |
| + "\", exception: " | |
| + ie.getMessage()); | |
| } catch(IllegalAccessException iae) { | |
| throw new ServletException( | |
| "IllegalAccessException when instantiating " | |
| + "multipart class \"" | |
| + multipartClass | |
| + "\", exception: " | |
| + iae.getMessage()); | |
| } | |
| if (multipartHandler != null) { | |
| return multipartHandler; | |
| } | |
| } | |
| return multipartHandler; | |
| } | |
| private static Map getAllParametersForMultipartRequest( | |
| HttpServletRequest request, | |
| MultipartRequestHandler multipartHandler) { | |
| Map parameters = new HashMap(); | |
| Hashtable elements = multipartHandler.getAllElements(); | |
| Enumeration e = elements.keys(); | |
| while (e.hasMoreElements()) { | |
| String key = (String) e.nextElement(); | |
| parameters.put(key, elements.get(key)); | |
| } | |
| if (request instanceof MultipartRequestWrapper) { | |
| request = ((MultipartRequestWrapper) request).getRequest(); | |
| e = request.getParameterNames(); | |
| while (e.hasMoreElements()) { | |
| String key = (String) e.nextElement(); | |
| parameters.put(key, request.getParameterValues(key)); | |
| } | |
| } else { | |
| log.debug("Gathering multipart parameters for unwrapped request"); | |
| } | |
| return parameters; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?xml version="1.0" encoding="utf-8"?> | |
| <!DOCTYPE struts-config PUBLIC | |
| "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN" | |
| "http://struts.apache.org/dtds/struts-config_1_2.dtd"> | |
| <struts-config> | |
| <controller processorClass="example.SafeRequestProcessor"/> | |
| </struts-config> |
52行目、params->properties?
@tomoZdime
私は、検証はしていませんが、params であっていると思います。
RequestProcessor#process の最初で、processMultipart によりマルチパートリクエストの場合は、MultipartRequestWrapper で request がラップされますので、processPopulate に渡される request も MultipartRequestWrapper になっています。
L50 の parseParams の中で L112 の multipartHandler#handleRequest が呼ばれることで、MultipartRequestWrapper#setParameter が呼ばれます。
これ以降に、MultipartRequestWrapper#getParameterNames を呼べば、通常のリクエストのパラメータ + マルチパートリクエストのパラメータが返るようになっていますので。
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
"foo.bar.class"のように、"class"で終わるパラメータははじいてないようですが、これは
obj.getFoo().getBar().getClass()
自体は実行されても特に問題はなく、その先の「getClassLoader()」等にアクセスされて初めて脆弱性になるから、という解釈で良いでしょうか?