public MessageHello getMessageHello() { returnthis.messageHello; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
//HelloWorld.jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Hello World</title> </head> <body> <h3>This is HelloWorld page.</h3><br> Use <s:property value="messageStore.message"/> to call getMessageStore().getMessage() to get messageStore.message from ValueStack(值栈) of the action. <br>The message is :<br> <h3><s:property value="messageHello.message"/></h3> </body> </html>
1 2 3 4 5 6 7 8 9 10 11 12
//index.jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Hello</title> </head> <body> <h1>Welcome to struts2 learning! It's an index page.</h1><br> Clike <a href="<s:url action='hello'/> ">HERE</a> to Enter HelloWorld page! </body> </html>
/** * Wraps the request with the Struts wrapper that handles multipart requests better * @return The new request, if there is one * @throws ServletException */ public HttpServletRequest wrapRequest(HttpServletRequest oldRequest)throws ServletException { HttpServletRequestrequest= oldRequest; try { // Wrap request first, just in case it is multipart/form-data // parameters might not be accessible through before encoding (ww-1278) request = dispatcher.wrapRequest(request); } catch (IOException e) { thrownewServletException("Could not wrap servlet request with MultipartRequestWrapper!", e); } return request; }
/** * Wrap and return the given request or return the original request object. * </p> * This method transparently handles multipart data as a wrapped class around the given request. * Override this method to handle multipart requests in a special way or to handle other types of requests. * Note, {@link org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper} is * flexible - look first to that object before overriding this method to handle multipart data. * * @param request the HttpServletRequest object. * @return a wrapped request or original request. * @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper * @throws java.io.IOException on any error. * * @since 2.3.17 */ public HttpServletRequest wrapRequest(HttpServletRequest request)throws IOException { // don't wrap more than once if (request instanceof StrutsRequestWrapper) { return request; }
/** * Creates a new request wrapper to handle multi-part data using methods adapted from Jason Pell's * multipart classes (see class description). * * @param saveDir the directory to save off the file * @param request the request containing the multipart * @throws java.io.IOException is thrown if encoding fails. */ publicvoidparse(HttpServletRequest request, String saveDir)throws IOException { try { setLocale(request); processUpload(request, saveDir); } catch (FileUploadBase.SizeLimitExceededException e) { if (LOG.isWarnEnabled()) { LOG.warn("Request exceeded size limit!", e); } StringerrorMessage= buildErrorMessage(e, newObject[]{e.getPermittedSize(), e.getActualSize()}); //跟进buildErrorMessage函数 if (!errors.contains(errorMessage)) { errors.add(errorMessage); } } catch (Exception e) { if (LOG.isWarnEnabled()) { LOG.warn("Unable to parse request", e); } StringerrorMessage= buildErrorMessage(e, newObject[]{}); if (!errors.contains(errorMessage)) { errors.add(errorMessage); } } }
跟进catch体的buildErrorMessage方法:
1 2 3 4 5 6 7
protected String buildErrorMessage(Throwable e, Object[] args) { StringerrorKey="struts.messages.upload.error." + e.getClass().getSimpleName(); if (LOG.isDebugEnabled()) { LOG.debug("Preparing error message for key: [#0]", errorKey); } return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, e.getMessage(), args); }
跟进一下,findText()函数中注释说明是这样的:If a message is found, it will also be interpolated. Anything within ${...} will be treated as an OGNL expression and evaluated as such.。在查找message(第二个参数aTextName)对应的键值失败后,会将其视为OGNL表达式来计算,导致了最终的RCE。
可以看到传入的参数中会先进行TextParseUtil.translateVariables()方法,重点就在这里了,这个方法的描述是这样的:Converts all instances of ${...}, and %{...} in <code>expression</code> to the value returned。显然这是一个计算OGNL表达式的函数,步入进行查看:
// Some code to save the gangster to the db as necessary GangsterFormgform= (GangsterForm) form; ActionMessagesmessages=newActionMessages(); messages.add("msg", newActionMessage("Gangster " + gform.getName() + " added successfully")); //注意这里 addMessages(request, messages);
/** * Get a text from the resource bundles associated with this action. * The resource bundles are searched, starting with the one associated * with this particular action, and testing all its superclasses' bundles. * It will stop once a bundle is found that contains the given text. This gives * a cascading style that allow global texts to be defined for an application base * class. If no text is found for this text name, the default value is returned. * * @param key name of text to be found * @param defaultValue the default value which will be returned if no text is found * @param args a List of args to be used in a MessageFormat message * @return value of named text or the provided defaultValue if no value is found */ public String getText(String key, String defaultValue, List<?> args) { Object[] argsArray = ((args != null && !args.equals(Collections.emptyList())) ? args.toArray() : null); if (clazz != null) { return LocalizedTextUtil.findText(clazz, key, getLocale(), defaultValue, argsArray); } else { return LocalizedTextUtil.findText(bundle, key, getLocale(), defaultValue, argsArray); } }
通过ActionMapper.getUriFromActionMapping()重组namespace和name后,由setLocation()
将带namespace的location放入父类的StrutsResultSupport中,父类的作用是:A base class for all Struts action execution results. The "location" param is the default parameter。
publicstatic Object translateVariables(char[] openChars, String expression, final ValueStack stack, final Class asType, final ParsedValueEvaluator evaluator, int maxLoopCount) {
ParsedValueEvaluatorognlEval=newParsedValueEvaluator() { public Object evaluate(String parsedValue) { Objecto= stack.findValue(parsedValue, asType); if (evaluator != null && o != null) { o = evaluator.evaluate(o.toString()); } return o; } };