CVE-2011-4344: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site scripting (XSS) vulnerability in Jenkins Core in Jenkins before 1.438, and 1.409 LTS before 1.409.3 LTS, when a stand-alone container is used, allows remote attackers to inject arbitrary web script or HTML via vectors related to error messages.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Published: 2011-12-01T11:55Z
Repository: https://github.com/jenkinsci/winstone
Language: Java
Files changed: src/java/winstone/ErrorServlet.java, src/java/winstone/URIUtil.java, src/java/winstone/WinstoneResponse.java
Commit date: 2011-11-01 22:27:09 -0700
Commit message: escape error messages which are supposed be plain text and not markup

Diff: diff --git a/src/java/winstone/ErrorServlet.java b/src/java/winstone/ErrorServlet.java
index 1854f8d..35c95ca 100644
--- a/src/java/winstone/ErrorServlet.java
+++ b/src/java/winstone/ErrorServlet.java
@@ -40,11 +40,11 @@ public class ErrorServlet extends HttpServlet {
         }
         pw.flush();
          
         // If we are here there was no error servlet, so show the default error page
         String output = Launcher.RESOURCES.getString("WinstoneResponse.ErrorPage",
-                new String[] { sc + "", (msg == null ? "" : msg), sw.toString(),
+                new String[] { sc + "", URIUtil.htmlEscape(msg == null ? "" : msg), URIUtil.htmlEscape(sw.toString()),
                 Launcher.RESOURCES.getString("ServerVersion"),
                         "" + new Date() });
         response.setContentLength(output.getBytes(response.getCharacterEncoding()).length);
         Writer out = response.getWriter();
         out.write(output);
diff --git a/src/java/winstone/URIUtil.java b/src/java/winstone/URIUtil.java
index a2fba88..97d7fdc 100644
--- a/src/java/winstone/URIUtil.java
+++ b/src/java/winstone/URIUtil.java
@@ -48,6 +48,23 @@ public class URIUtil {
         if (path.endsWith("/") && (buf.length()==0 || buf.charAt(buf.length()-1)!='/'))
             buf.append('/');
         return buf.toString();
     }
 
+    /**
+     * Performs necessary escaping to render arbitrary plain text as plain text without any markup.
+     */
+    public static String htmlEscape(String text) {
+        StringBuilder buf = new StringBuilder(text.length()+64);
+        for( int i=0; i<text.length(); i++ ) {
+            char ch = text.charAt(i);
+            if(ch=='<')
+                buf.append("&lt;");
+            else
+            if(ch=='&')
+                buf.append("&amp;");
+            else
+                buf.append(ch);
+        }
+        return buf.toString();
+    }
 }
diff --git a/src/java/winstone/WinstoneResponse.java b/src/java/winstone/WinstoneResponse.java
index 3746b40..d6b2c95 100644
--- a/src/java/winstone/WinstoneResponse.java
+++ b/src/java/winstone/WinstoneResponse.java
@@ -803,11 +803,11 @@ public class WinstoneResponse implements HttpServletResponse {
         // show the default error page
         if (this.errorStatusCode == null) {
             this.statusCode = sc;
         }
         String output = Launcher.RESOURCES.getString("WinstoneResponse.ErrorPage",
-                new String[] { sc + "", (msg == null ? "" : msg), "",
+                new String[] { sc + "", URIUtil.htmlEscape(msg == null ? "" : msg), "",
                         Launcher.RESOURCES.getString("ServerVersion"),
                         "" + new Date() });
         setContentLength(output.getBytes(getCharacterEncoding()).length);
         Writer out = getWriter();
         out.write(output);

Vulnerable code:                 new String[] { sc + "", (msg == null ? "" : msg), sw.toString(),
                new String[] { sc + "", (msg == null ? "" : msg), "",
Fixed code:                 new String[] { sc + "", URIUtil.htmlEscape(msg == null ? "" : msg), URIUtil.htmlEscape(sw.toString()),
    /**
     * Performs necessary escaping to render arbitrary plain text as plain text without any markup.
     */
    public static String htmlEscape(String text) {
        StringBuilder buf = new StringBuilder(text.length()+64);
        for( int i=0; i<text.length(); i++ ) {
            char ch = text.charAt(i);
            if(ch=='<')
                buf.append("&lt;");
            else
            if(ch=='&')
                buf.append("&amp;");
            else
                buf.append(ch);
        }
        return buf.toString();
    }
                new String[] { sc + "", URIUtil.htmlEscape(msg == null ? "" : msg), "",