Skip to content

Instantly share code, notes, and snippets.

@kawasima
Last active August 29, 2015 14:00
Show Gist options
  • Select an option

  • Save kawasima/c22a1c706656e4004d41 to your computer and use it in GitHub Desktop.

Select an option

Save kawasima/c22a1c706656e4004d41 to your computer and use it in GitHub Desktop.
A workaround for CVE-2014-0114
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;
}
}
<?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>
@tauty
Copy link

tauty commented May 1, 2014

"foo.bar.class"のように、"class"で終わるパラメータははじいてないようですが、これは
  obj.getFoo().getBar().getClass()
自体は実行されても特に問題はなく、その先の「getClassLoader()」等にアクセスされて初めて脆弱性になるから、という解釈で良いでしょうか?

@tomoZdime
Copy link

52行目、params->properties?

@learnin
Copy link

learnin commented May 8, 2014

@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