273011 XSS in directory listing

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@166 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Greg Wilkins 2009-04-21 04:34:48 +00:00
parent 17250b81c0
commit 59a80ea017
7 changed files with 146 additions and 18 deletions

View File

@ -5,7 +5,7 @@ jetty-7.0.0.M1-SNAPSHOT
+ JETTY-695 Handler dump
+ Reworked authentication for deferred authentication
+ JETTY-983 DefaultServlet generates accept-ranges for cached/gzip content
+ 273011 JETTY-980 JETTY-992 Security / Directory Listing XSS present
jetty-7.0.0.M0
+ JETTY-496 Support inetd/xinetd through use of System.inheritedChannel()

View File

@ -30,8 +30,11 @@ public interface IdentityService
/* ------------------------------------------------------------ */
/**
* Scope the {@link UserIdentity} to a {@link UserIdentity.Scope}.
* @param user The current user or null for no user associated.
* Associate a user identity with the current thread.
* This is called with as a thread enters the
* {@link SecurityHandler#handle(String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
* method and then again with a null argument as that call exits.
* @param user The current user or null for no user to associated.
*/
void associate(UserIdentity user);

View File

@ -312,10 +312,8 @@ public class ServletHandler extends AbstractHandler
// Get the base requests
final Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest();
final String old_servlet_name=base_request.getServletName();
final String old_servlet_path=base_request.getServletPath();
final String old_path_info=base_request.getPathInfo();
UserIdentity scoped_identity = null;
DispatcherType type = base_request.getDispatcherType();
Object request_listeners=null;

View File

@ -0,0 +1,104 @@
package org.eclipse.jetty.servlet;
import java.io.File;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
public class DefaultServletTest extends TestCase
{
private Server server;
private LocalConnector connector;
private ServletContextHandler context;
protected void setUp() throws Exception
{
super.setUp();
server = new Server();
server.setSendServerVersion(false);
connector = new LocalConnector();
context = new ServletContextHandler();
context.setContextPath("/context");
context.setWelcomeFiles(new String[] {}); // no welcome files
server.setHandler(context);
server.addConnector(connector);
server.start();
}
protected void tearDown() throws Exception
{
super.tearDown();
if (server != null)
{
server.stop();
}
}
public void testListingXSS() throws Exception
{
ServletHolder defholder = context.addServlet(DefaultServlet.class,"/*");
defholder.setInitParameter("dirAllowed","true");
defholder.setInitParameter("redirectWelcome","false");
defholder.setInitParameter("gzip","false");
File resBase = new File("src/test/resources");
assertTrue("resBase.exists",resBase.exists());
assertTrue("resBase.isDirectory",resBase.isDirectory());
String resBasePath = resBase.getAbsolutePath();
defholder.setInitParameter("resourceBase",resBasePath);
StringBuffer req1 = new StringBuffer();
req1.append("GET /context/org/mortbay/resource/;<script>window.alert(\"hi\");</script> HTTP/1.1\n");
req1.append("Host: localhost\n");
req1.append("\n");
String response = connector.getResponses(req1.toString());
assertResponseContains("org/mortbay/resource/one/",response);
assertResponseContains("org/mortbay/resource/two/",response);
assertResponseContains("org/mortbay/resource/three/",response);
assertResponseNotContains("<script>",response);
}
private void assertResponseNotContains(String forbidden, String response)
{
int idx = response.indexOf(forbidden);
if (idx != (-1))
{
// Found (when should not have)
StringBuffer err = new StringBuffer();
err.append("Response contain forbidden string \"").append(forbidden).append("\"");
err.append("\n").append(response);
System.err.println(err);
throw new AssertionFailedError(err.toString());
}
}
private void assertResponseContains(String expected, String response)
{
int idx = response.indexOf(expected);
if (idx == (-1))
{
// Not found
StringBuffer err = new StringBuffer();
err.append("Response does not contain expected string \"").append(expected).append("\"");
err.append("\n").append(response);
System.err.println(err);
throw new AssertionFailedError(err.toString());
}
}
}

View File

@ -75,13 +75,17 @@ public class URIUtil
char c=path.charAt(i);
switch(c)
{
case '%':
case '?':
case ';':
case '#':
case ' ':
buf=new StringBuilder(path.length()<<1);
break loop;
case '%':
case '?':
case ';':
case '#':
case '\'':
case '"':
case '<':
case '>':
case ' ':
buf=new StringBuilder(path.length()<<1);
break loop;
}
}
if (buf==null)
@ -107,6 +111,18 @@ public class URIUtil
case '#':
buf.append("%23");
continue;
case '"':
buf.append("%22");
continue;
case '\'':
buf.append("%27");
continue;
case '<':
buf.append("%3C");
continue;
case '>':
buf.append("%3E");
continue;
case ' ':
buf.append("%20");
continue;

View File

@ -429,7 +429,7 @@ public abstract class Resource implements Serializable
Arrays.sort(ls);
String decodedBase = URIUtil.decodePath(base);
String title = "Directory: "+decodedBase;
String title = "Directory: "+deTag(decodedBase);
StringBuilder buf=new StringBuilder(4096);
buf.append("<HTML><HEAD><TITLE>");
@ -440,9 +440,9 @@ public abstract class Resource implements Serializable
if (parent)
{
buf.append("<TR><TD><A HREF=");
buf.append(URIUtil.addPaths(base,"../"));
buf.append(">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n");
buf.append("<TR><TD><A HREF=\"");
URIUtil.encodePath(buf,URIUtil.addPaths(base,"../"));
buf.append("\">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n");
}
DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
@ -457,9 +457,9 @@ public abstract class Resource implements Serializable
if (item.isDirectory() && !path.endsWith("/"))
path=URIUtil.addPaths(path,URIUtil.SLASH);
buf.append(path);
URIUtil.encodePath(buf,path);
buf.append("\">");
buf.append(StringUtil.replace(StringUtil.replace(ls[i],"<","&lt;"),">","&gt;"));
buf.append(deTag(ls[i]));
buf.append("&nbsp;");
buf.append("</TD><TD ALIGN=right>");
buf.append(item.length());
@ -473,6 +473,10 @@ public abstract class Resource implements Serializable
return buf.toString();
}
private static String deTag(String raw) {
return StringUtil.replace( StringUtil.replace(raw,"<","&lt;"), ">", "&gt;");
}
/* ------------------------------------------------------------ */
/**
* @param out

View File

@ -57,6 +57,9 @@ public class URITest extends junit.framework.TestCase
URIUtil.encodeString(buf,"foo%23;,:=b a r",";,= ");
assertEquals("foo%2523%3b%2c:%3db%20a%20r",buf.toString());
buf.setLength(0);
URIUtil.encodePath(buf,"/context/'list'/\"me\"/;<script>window.alert('xss');</script>");
assertEquals("/context/%27list%27/%22me%22/%3B%3Cscript%3Ewindow.alert(%27xss%27)%3B%3C/script%3E", buf.toString());
}
/* ------------------------------------------------------------ */