[JENKINS-75388] Remove `?path` and `?pattern` support from DBS (#10650)

Co-authored-by: Daniel Beck <daniel-beck@users.noreply.github.com>
This commit is contained in:
Daniel Beck 2025-05-21 23:03:59 +02:00 committed by GitHub
parent 36dba29e91
commit 15c512cbe0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 88 additions and 21 deletions

View File

@ -203,15 +203,6 @@ public final class DirectoryBrowserSupport implements HttpResponse {
}
private void serveFile(StaplerRequest2 req, StaplerResponse2 rsp, VirtualFile root, String icon, boolean serveDirIndex) throws IOException, ServletException, InterruptedException {
// handle form submission
String pattern = req.getParameter("pattern");
if (pattern == null)
pattern = req.getParameter("path"); // compatibility with Hudson<1.129
if (pattern != null && Util.isSafeToRedirectTo(pattern)) { // avoid open redirect
rsp.sendRedirect2(pattern);
return;
}
String path = getPath(req);
if (path.replace('\\', '/').contains("/../")) {
// don't serve anything other than files in the artifacts dir

View File

@ -33,17 +33,18 @@ THE SOFTWARE.
<div class="dirTree">
<!-- parent path -->
<div class="parentPath">
<form action="${backPath}" method="get">
<form>
<a href="${topPath}">${it.owner.name}</a> /
<j:forEach var="p" items="${parentPath}">
<a href="${p.href}">${p.title}</a>
/
</j:forEach>
<input type="text" name="pattern" value="${pattern}" class="jenkins-input" />
<button class="jenkins-button">
<input type="text" name="pattern" value="${pattern}" class="jenkins-input" id="pattern" data-restofpath="${request2.restOfPath}" data-backpath="${backPath}" />
<button class="jenkins-button" id="pattern-submit">
<l:icon src="symbol-arrow-right" />
</button>
</form>
<st:adjunct includes="hudson.model.DirectoryBrowserSupport.pattern" />
</div>
<j:choose>
<j:when test="${empty(files)}">

View File

@ -0,0 +1,18 @@
document.addEventListener("DOMContentLoaded", function () {
document
.getElementById("pattern-submit")
.addEventListener("click", function (ev) {
ev.preventDefault();
let input = document.getElementById("pattern");
let pattern = input.value;
let back = input.dataset.backpath;
let baseurl = back;
if (!baseurl.endsWith("/")) {
baseurl = baseurl + "/";
}
baseurl = baseurl + pattern;
document.location.href = baseurl;
});
});

View File

@ -4,29 +4,56 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import java.util.List;
import jenkins.security.security3501Test.Security3501RootAction;
import org.htmlunit.FailingHttpStatusCodeException;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.RealJenkinsRule;
@RunWith(Parameterized.class)
public class Security3501Test {
// Workaround for https://github.com/jenkinsci/jenkins-test-harness/issues/933
private final String contextPath;
@Rule
public RealJenkinsRule jj = new RealJenkinsRule();
public RealJenkinsRule jj = new RealJenkinsRule().addSyntheticPlugin(new RealJenkinsRule.SyntheticPlugin(Security3501RootAction.class.getPackage()).shortName("Security3501RootAction"));
@Parameterized.Parameters
public static List<String> contexts() {
return List.of("/jenkins", "");
}
public Security3501Test(String contextPath) {
jj.withPrefix(contextPath);
this.contextPath = contextPath;
}
@Test
public void testRedirects() throws Throwable {
jj.then(Security3501Test::_testRedirects);
jj.then(new TestRedirectsStep(contextPath));
}
public static void _testRedirects(JenkinsRule j) throws Exception {
List<String> prohibitedPaths = List.of("%5C%5Cexample.org", "%5C/example.org", "/%5Cexample.org", "//example.org", "https://example.org", "\\example.org");
for (String path : prohibitedPaths) {
try (JenkinsRule.WebClient wc = j.createWebClient().withRedirectEnabled(false)) {
final FailingHttpStatusCodeException fhsce = Assert.assertThrows(FailingHttpStatusCodeException.class, () -> wc.goTo("userContent?path=" + path));
assertThat(fhsce.getStatusCode(), is(302));
assertThat(fhsce.getResponse().getResponseHeaderValue("Location"), is(j.getURL().toExternalForm() + "userContent/"));
private record TestRedirectsStep(String context) implements RealJenkinsRule.Step {
public void run(JenkinsRule j) throws Exception {
List<String> prohibitedPaths = List.of("%5C%5Cexample.org", "%5C/example.org", "/%5Cexample.org", "//example.org", "https://example.org", "\\example.org");
for (String path : prohibitedPaths) {
try (JenkinsRule.WebClient wc = j.createWebClient().withRedirectEnabled(false)) {
final FailingHttpStatusCodeException fhsce = Assert.assertThrows(FailingHttpStatusCodeException.class, () -> wc.goTo("redirects/content?path=" + path));
assertThat(fhsce.getStatusCode(), is(404));
}
}
List<String> allowedPaths = List.of("foo", "foo/bar");
for (String path : allowedPaths) {
try (JenkinsRule.WebClient wc = j.createWebClient().withRedirectEnabled(false)) {
final FailingHttpStatusCodeException fhsce = Assert.assertThrows(FailingHttpStatusCodeException.class, () -> wc.goTo("redirects/content?path=" + path));
assertThat(fhsce.getStatusCode(), is(302));
assertThat(fhsce.getResponse().getResponseHeaderValue("Location"), is(context + "/redirects/" + path));
}
}
}
}

View File

@ -0,0 +1,26 @@
package jenkins.security.security3501Test;
import hudson.Util;
import hudson.model.InvisibleAction;
import hudson.model.RootAction;
import java.io.IOException;
import org.jenkinsci.plugins.variant.OptionalExtension;
import org.kohsuke.stapler.StaplerRequest2;
import org.kohsuke.stapler.StaplerResponse2;
@OptionalExtension
public class Security3501RootAction extends InvisibleAction implements RootAction {
@Override
public String getUrlName() {
return "redirects";
}
public void doContent(StaplerRequest2 req, StaplerResponse2 rsp) throws IOException {
final String path = req.getParameter("path");
if (path != null && Util.isSafeToRedirectTo(path)) {
rsp.sendRedirect2(path);
return;
}
rsp.setStatus(404);
}
}

View File

@ -0,0 +1,4 @@
@OptionalPackage(requirePlugins = "Security3501RootAction")
package jenkins.security.security3501Test;
import org.jenkinsci.plugins.variant.OptionalPackage;