[ 
https://issues.apache.org/jira/browse/VELTOOLS-126?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13224527#comment-13224527
 ] 

Christopher Schultz commented on VELTOOLS-126:
----------------------------------------------

Here's a stab at a patch. I'm not sure exactly at which level a configuration 
option should be put (ErrorsTool, ActionMessagesTool, or MessageResourcesTool), 
so any suggestions would be appreciated.

Here's the meat of the patch (against 2.0.x, but I think trunk is identical):

Index: src/main/java/org/apache/velocity/tools/struts/StrutsUtils.java
===================================================================
--- src/main/java/org/apache/velocity/tools/struts/StrutsUtils.java     
(revision 1238717)
+++ src/main/java/org/apache/velocity/tools/struts/StrutsUtils.java     
(working copy)
@@ -26,6 +26,7 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 
+import org.apache.commons.lang.StringEscapeUtils;
 import org.apache.struts.Globals;
 import org.apache.struts.action.ActionForm;
 import org.apache.struts.action.ActionMessage;
@@ -464,7 +465,7 @@
                                      HttpSession session,
                                      ServletContext application)
     {
-        return errorMarkup(property, null, request, session, application);
+        return errorMarkup(property, null, "HTML", request, session, 
application);
     }
 
 
@@ -483,6 +484,7 @@
      * @since VelocityTools 1.1
      * @return The formatted error message. If no error messages are queued,
      * an empty string is returned.
+     * @deprecated Use {@link 
#errorMarkup(String,String,String,HttpServletRequest,HttpSession,ServletContext)}
 instead
      */
     public static String errorMarkup(String property,
                                      String bundle,
@@ -490,6 +492,38 @@
                                      HttpSession session,
                                      ServletContext application)
     {
+        return errorMarkup(property, bundle, "HTML", request, session,
+                           application);
+    }
+
+    /**
+     * Returns a formatted error message. The error message is assembled from
+     * the following three pieces: First, value of message resource
+     * "errors.header" is prepended. Then, the list of error messages is
+     * rendered. Finally, the value of message resource "errors.footer"
+     * is appended.
+     *
+     * @param property the category of errors to markup and return
+     * @param bundle the message resource bundle to use
+     * @param escape A string describing the type of escaping to apply to each
+     *               error message's parameters (if any). Valid values are
+     *               "HTML", "XML", and "ECMAScript" (case-insensitive).
+     *               Note that the message itself is not escaped, only the
+     *               parameters for replacements such as "{0}", etc. are
+     *               escaped.
+     * @param request the servlet request
+     * @param session the HTTP session
+     * @param application the servlet context
+     * @since VelocityTools 1.1
+     * @return The formatted error message. If no error messages are queued,
+     * an empty string is returned.
+     */
+    public static String errorMarkup(String property,
+                                     String bundle,
+                                     String escape,
+                                     HttpServletRequest request,
+                                     HttpSession session, ServletContext 
application)
+    {
         ActionMessages errors = getErrors(request);
         if (errors == null)
         {
@@ -550,6 +584,8 @@
         results.append(header);
         results.append("\r\n");
 
+        Escaper escaper = null;
+
         String message;
         while (reports.hasNext())
         {
@@ -557,9 +593,35 @@
             ActionMessage report = (ActionMessage)reports.next();
             if (resources != null && report.isResource())
             {
+                Object[] parameters = report.getValues();
+                
+                // Check to see if we need to escape the parameter values
+                // for this message. See VELTOOLS-126.
+                boolean copied = false;
+                if(null != parameters && 0 < parameters.length)
+                {
+                    if(null == escaper)
+                        escaper = getEscaper(escape);
+
+                    for(int i=0; i<parameters.length; ++i)
+                    {
+                        // Only have to escape string-like values
+                        if(parameters[i] instanceof CharSequence)
+                        {
+                            // Only duplicate the array if necessary
+                            if(!copied)
+                            {
+                                parameters = (Object[])parameters.clone();
+                                copied = true;
+                            }
+                            parameters[i] = 
escaper.escape((CharSequence)parameters[i]);
+                        }
+                    }
+                }
+
                 message = resources.getMessage(locale,
                                                report.getKey(),
-                                               report.getValues());
+                                               parameters);
             }
 
             results.append(prefix);
@@ -570,7 +632,15 @@
             }
             else
             {
-                results.append(report.getKey());
+                // VELTOOLS-126: escape the key
+                CharSequence key = report.getKey();
+                
+                if(null == escaper)
+                    escaper = getEscaper(escape);
+
+                key = escaper.escape(key);
+
+                results.append(key);
             }
 
             results.append(suffix);
@@ -584,4 +654,61 @@
         return results.toString();
     }
 
+    static interface Escaper
+    {
+        public CharSequence escape(CharSequence cs);
+    }
+
+    private static class NOPEscaper
+        implements Escaper
+    {
+        public CharSequence escape(CharSequence cs)
+        {
+            return cs;
+        }
+    }
+
+    private static class HTMLEscaper
+        implements Escaper
+    {
+        public CharSequence escape(CharSequence cs)
+        {
+            return StringEscapeUtils.escapeHtml(cs.toString());
+        }
+    }
+
+    private static class XMLEscaper
+        implements Escaper
+    {
+        public CharSequence escape(CharSequence cs)
+        {
+            return StringEscapeUtils.escapeXml(cs.toString());
+        }
+    }
+
+    private static class ECMAScriptEscaper
+        implements Escaper
+    {
+        public CharSequence escape(CharSequence cs)
+        {
+            return StringEscapeUtils.escapeJavaScript(cs.toString());
+        }
+    }
+    private static final NOPEscaper NOP_ESCAPER = new NOPEscaper();
+    private static final HTMLEscaper HTML_ESCAPER = new HTMLEscaper();
+    private static final XMLEscaper XML_ESCAPER = new XMLEscaper();
+    private static final ECMAScriptEscaper ECMASCRIPT_ESCAPER = new 
ECMAScriptEscaper();
+    private static Escaper getEscaper(String escape)
+    {
+        if("html".equalsIgnoreCase(escape))
+            return HTML_ESCAPER;
+        else if("xml".equalsIgnoreCase(escape))
+            return XML_ESCAPER;
+        else if ("ecmascript".equalsIgnoreCase(escape))
+            return ECMASCRIPT_ESCAPER;
+        else if(null != escape)
+            throw new IllegalArgumentException("Unrecognized escape: '" + 
escape + "'");
+        else
+            return NOP_ESCAPER;
+    }
 }

                
> XSS Vulnerability when using struts/ErrorsTool.getMsgs
> ------------------------------------------------------
>
>                 Key: VELTOOLS-126
>                 URL: https://issues.apache.org/jira/browse/VELTOOLS-126
>             Project: Velocity Tools
>          Issue Type: Bug
>          Components: VelocityStruts
>    Affects Versions: 1.4, 2.x
>         Environment: Identified in velocity-tools 1.4, verified by reading 
> code in trunk.
>            Reporter: Christopher Schultz
>
> The code for ErrorsTool.getMsgs goes roughly like this:
> String message = message("errors.header");
> foreach(error) {
>   message += message("errors.prefix") + error + message("errors.suffix")
> message += message("errors.footer")
> return message;
> This is easily open to an XSS attack when an error message contains user 
> input.
> Honestly, I'm not entirely sure if we should even do anything about this, 
> because the ErrorsTool is not strictly for use in an HTML context, so 
> escaping the error message itself may not be appropriate. Also, the message 
> itself may contain markup which the developer wants to remain, while the user 
> input should be escaped.
> It's possible that the solution to this problem is to put a big warning on 
> the tool that XSS attacks are very easy using this tool.
> I've been running with it for years, and didn't notice until today. I 
> replaced my use of errors.getMsgs with this:
>     $!msg.errors.header
>     #foreach($error in $errors.get($fieldName))
>     $!msg.errors.prefix#htmlEscape($error)$!msg.errors.suffix
>     #end
>     $!msg.errors.header
> ...which is appropriate for my uses, but might not work for everyone.

--
This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators: 
https://issues.apache.org/jira/secure/ContactAdministrators!default.jspa
For more information on JIRA, see: http://www.atlassian.com/software/jira

        

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to