diff --git a/core/src/main/java/hudson/Plugin.java b/core/src/main/java/hudson/Plugin.java
index bfe8e0fbe4..b955a6d3ad 100644
--- a/core/src/main/java/hudson/Plugin.java
+++ b/core/src/main/java/hudson/Plugin.java
@@ -35,6 +35,7 @@ import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.File;
@@ -42,10 +43,9 @@ import net.sf.json.JSONObject;
import com.thoughtworks.xstream.XStream;
import hudson.init.Initializer;
import hudson.init.Terminator;
-import java.net.URI;
-import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.logging.Logger;
import jenkins.model.GlobalConfiguration;
-import org.kohsuke.stapler.HttpResponses;
/**
* Base class of Hudson plugin.
@@ -81,6 +81,8 @@ import org.kohsuke.stapler.HttpResponses;
*/
public abstract class Plugin implements Saveable {
+ private static final Logger LOGGER = Logger.getLogger(Plugin.class.getName());
+
/**
* You do not need to create custom subtypes:
*
@@ -224,13 +226,12 @@ public abstract class Plugin implements Saveable {
public void doDynamic(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
String path = req.getRestOfPath();
- if (path.startsWith("/META-INF/") || path.startsWith("/WEB-INF/")) {
- throw HttpResponses.notFound();
+ if (path.isEmpty() || path.contains("..") || path.contains("%") || path.contains("META-INF") || path.contains("WEB-INF")) {
+ LOGGER.warning("rejecting possibly malicious " + req.getRequestURIWithQueryString());
+ rsp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
}
- if(path.length()==0)
- path = "/";
-
// Stapler routes requests like the "/static/.../foo/bar/zot" to be treated like "/foo/bar/zot"
// and this is used to serve long expiration header, by using Jenkins.VERSION_HASH as "..."
// to create unique URLs. Recognize that and set a long expiration header.
@@ -240,11 +241,7 @@ public abstract class Plugin implements Saveable {
long expires = staticLink ? TimeUnit2.DAYS.toMillis(365) : -1;
// use serveLocalizedFile to support automatic locale selection
- try {
- rsp.serveLocalizedFile(req, wrapper.baseResourceURL.toURI().resolve(new URI(null, '.' + path, null)).toURL(), expires);
- } catch (URISyntaxException x) {
- throw new IOException(x);
- }
+ rsp.serveLocalizedFile(req, new URL(wrapper.baseResourceURL, '.' + path), expires);
}
//
diff --git a/test/src/test/java/hudson/PluginTest.java b/test/src/test/java/hudson/PluginTest.java
index 19cd52bac7..dd7026406b 100644
--- a/test/src/test/java/hudson/PluginTest.java
+++ b/test/src/test/java/hudson/PluginTest.java
@@ -34,18 +34,23 @@ public class PluginTest {
@Rule public JenkinsRule r = new JenkinsRule();
- @Issue({"SECURITY-131", "SECURITY-155"})
+ @Issue({"SECURITY-131", "SECURITY-155", "SECURITY-705"})
@Test public void doDynamic() throws Exception {
r.createWebClient().goTo("plugin/credentials/images/24x24/credentials.png", "image/png");
/* Collapsed somewhere before it winds up in restOfPath:
r.createWebClient().assertFails("plugin/credentials/images/../images/24x24/credentials.png", HttpServletResponse.SC_BAD_REQUEST);
*/
r.createWebClient().assertFails("plugin/credentials/images/%2E%2E/images/24x24/credentials.png", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // IAE from TokenList.
- r.createWebClient().assertFails("plugin/credentials/images/%252E%252E/images/24x24/credentials.png", HttpServletResponse.SC_NOT_FOUND); // SECURITY-131
- r.createWebClient().assertFails("plugin/credentials/images/%25252E%25252E/images/24x24/credentials.png", HttpServletResponse.SC_NOT_FOUND); // just checking
+ r.createWebClient().assertFails("plugin/credentials/images/%252E%252E/images/24x24/credentials.png", HttpServletResponse.SC_BAD_REQUEST); // SECURITY-131
+ r.createWebClient().assertFails("plugin/credentials/images/%25252E%25252E/images/24x24/credentials.png", HttpServletResponse.SC_BAD_REQUEST); // just checking
+ // SECURITY-705:
+ r.createWebClient().assertFails("plugin/credentials/images/..%2fWEB-INF/licenses.xml", HttpServletResponse.SC_BAD_REQUEST);
+ r.createWebClient().assertFails("plugin/credentials/images/%2e%2e%2fWEB-INF/licenses.xml", HttpServletResponse.SC_BAD_REQUEST);
+ r.createWebClient().assertFails("plugin/credentials/images/%2e.%2fWEB-INF/licenses.xml", HttpServletResponse.SC_BAD_REQUEST);
+ r.createWebClient().assertFails("plugin/credentials/images/..%2f..%2f..%2f" + r.jenkins.getRootDir().getName() + "%2fsecrets%2fmaster.key", HttpServletResponse.SC_BAD_REQUEST);
// SECURITY-155:
- r.createWebClient().assertFails("plugin/credentials/WEB-INF/licenses.xml", HttpServletResponse.SC_NOT_FOUND);
- r.createWebClient().assertFails("plugin/credentials/META-INF/MANIFEST.MF", HttpServletResponse.SC_NOT_FOUND);
+ r.createWebClient().assertFails("plugin/credentials/WEB-INF/licenses.xml", HttpServletResponse.SC_BAD_REQUEST);
+ r.createWebClient().assertFails("plugin/credentials/META-INF/MANIFEST.MF", HttpServletResponse.SC_BAD_REQUEST);
}
}