Skip to content

Commit

Permalink
[SECURITY-1774]
Browse files Browse the repository at this point in the history
  • Loading branch information
jvz authored and daniel-beck committed Mar 11, 2020
1 parent f479652 commit f7cf283
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 1 deletion.
47 changes: 47 additions & 0 deletions core/src/main/java/jenkins/security/SuspiciousRequestFilter.java
@@ -0,0 +1,47 @@
package jenkins.security;

import jenkins.util.SystemProperties;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.logging.Logger;

@Restricted(NoExternalUse.class)
public class SuspiciousRequestFilter implements Filter {

/** System property name set to true or false to indicate whether or not semicolons should be allowed in URL paths. */
public static final String ALLOW_SEMICOLONS_IN_PATH = SuspiciousRequestFilter.class.getName() + ".allowSemicolonsInPath";
public static boolean allowSemicolonsInPath = SystemProperties.getBoolean(ALLOW_SEMICOLONS_IN_PATH, false);
private static final Logger LOGGER = Logger.getLogger(SuspiciousRequestFilter.class.getName());

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
if (!allowSemicolonsInPath && httpRequest.getRequestURI().contains(";")) {
LOGGER.warning(() -> "Denying HTTP " + httpRequest.getMethod() + " to " + httpRequest.getRequestURI() +
" as it has an illegal semicolon in the path. This behavior can be overridden by setting the system property " +
ALLOW_SEMICOLONS_IN_PATH + " to true. For more information, see https://jenkins.io/redirect/semicolons-in-urls");
httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "Semicolons are not allowed in the request URI");
} else {
chain.doFilter(request, response);
}
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void destroy() {
}
}
13 changes: 13 additions & 0 deletions test/src/test/java/hudson/security/csrf/CrumbExclusionTest.java
Expand Up @@ -36,7 +36,10 @@
import jenkins.model.Jenkins;
import static org.hamcrest.Matchers.containsString;

import jenkins.security.SuspiciousRequestFilter;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Rule;
Expand All @@ -57,6 +60,16 @@ public class CrumbExclusionTest {
@Rule
public JenkinsRule r = new JenkinsRule();

@BeforeClass
public static void prepare() {
SuspiciousRequestFilter.allowSemicolonsInPath = true;
}

@AfterClass
public static void cleanup() {
SuspiciousRequestFilter.allowSemicolonsInPath = false;
}

@Issue("SECURITY-1774")
@Test
public void pathInfo() throws Exception {
Expand Down
102 changes: 102 additions & 0 deletions test/src/test/java/jenkins/security/SuspiciousRequestFilterTest.java
@@ -0,0 +1,102 @@
package jenkins.security;

import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import hudson.ExtensionList;
import hudson.model.UnprotectedRootAction;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.verb.GET;

import javax.annotation.CheckForNull;
import javax.servlet.http.HttpServletResponse;
import java.net.URL;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;

@Issue("SECURITY-1774")
public class SuspiciousRequestFilterTest {

@Rule
public JenkinsRule j = new JenkinsRule();

private WebResponse get(String path) throws Exception {
return j.createWebClient()
.withThrowExceptionOnFailingStatusCode(false)
.getPage(new WebRequest(new URL(j.getURL(), path)))
.getWebResponse();
}

@Test
public void denySemicolonInRequestPathByDefault() throws Exception {
WebResponse response = get("foo/bar/..;/?baz=bruh");
assertThat(Foo.getInstance().baz, is(nullValue()));
assertThat(response.getStatusCode(), is(HttpServletResponse.SC_BAD_REQUEST));
assertThat(response.getContentAsString(), containsString("Semicolons are not allowed in the request URI"));
}

@Test
public void allowSemicolonsInRequestPathWhenEscapeHatchEnabled() throws Exception {
SuspiciousRequestFilter.allowSemicolonsInPath = true;
try {
WebResponse response = get("foo/bar/..;/..;/cli?baz=bruh");
assertThat(Foo.getInstance().baz, is("bruh"));
assertThat(response.getStatusCode(), is(HttpServletResponse.SC_OK));
} finally {
SuspiciousRequestFilter.allowSemicolonsInPath = false;
}
}

@Test
public void allowSemicolonsInQueryParameters() throws Exception {
WebResponse response = get("foo/bar?baz=foo;bar=baz");
assertThat(Foo.getInstance().baz, is("foo;bar=baz"));
assertThat(response.getStatusCode(), is(HttpServletResponse.SC_OK));
}

@TestExtension
public static class Foo implements UnprotectedRootAction {

private static Foo getInstance() {
return ExtensionList.lookupSingleton(Foo.class);
}

private String baz;

@CheckForNull
@Override
public String getIconFileName() {
return null;
}

@CheckForNull
@Override
public String getDisplayName() {
return "Pitied Foos";
}

@CheckForNull
@Override
public String getUrlName() {
return "foo";
}

@GET
public void doBar(@QueryParameter String baz) {
this.baz = baz;
}

@GET
public void doIndex(@QueryParameter String baz) {
this.baz = "index: " + baz;
}
}

}
11 changes: 10 additions & 1 deletion war/src/main/webapp/WEB-INF/web.xml
Expand Up @@ -50,6 +50,11 @@ THE SOFTWARE.
<url-pattern>/*</url-pattern>
</servlet-mapping>

<filter>
<filter-name>suspicious-request-filter</filter-name>
<filter-class>jenkins.security.SuspiciousRequestFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter>
<filter-name>diagnostic-name-filter</filter-name>
<filter-class>org.kohsuke.stapler.DiagnosticThreadNameFilter</filter-class>
Expand Down Expand Up @@ -125,7 +130,11 @@ THE SOFTWARE.
<url-pattern>*.png</url-pattern>
</filter-mapping>
-->


<filter-mapping>
<filter-name>suspicious-request-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>diagnostic-name-filter</filter-name>
<url-pattern>/*</url-pattern>
Expand Down

0 comments on commit f7cf283

Please sign in to comment.