mirror of https://github.com/jenkinsci/jenkins.git
[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:
parent
36dba29e91
commit
15c512cbe0
|
@ -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 {
|
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);
|
String path = getPath(req);
|
||||||
if (path.replace('\\', '/').contains("/../")) {
|
if (path.replace('\\', '/').contains("/../")) {
|
||||||
// don't serve anything other than files in the artifacts dir
|
// don't serve anything other than files in the artifacts dir
|
||||||
|
|
|
@ -33,17 +33,18 @@ THE SOFTWARE.
|
||||||
<div class="dirTree">
|
<div class="dirTree">
|
||||||
<!-- parent path -->
|
<!-- parent path -->
|
||||||
<div class="parentPath">
|
<div class="parentPath">
|
||||||
<form action="${backPath}" method="get">
|
<form>
|
||||||
<a href="${topPath}">${it.owner.name}</a> /
|
<a href="${topPath}">${it.owner.name}</a> /
|
||||||
<j:forEach var="p" items="${parentPath}">
|
<j:forEach var="p" items="${parentPath}">
|
||||||
<a href="${p.href}">${p.title}</a>
|
<a href="${p.href}">${p.title}</a>
|
||||||
/
|
/
|
||||||
</j:forEach>
|
</j:forEach>
|
||||||
<input type="text" name="pattern" value="${pattern}" class="jenkins-input" />
|
<input type="text" name="pattern" value="${pattern}" class="jenkins-input" id="pattern" data-restofpath="${request2.restOfPath}" data-backpath="${backPath}" />
|
||||||
<button class="jenkins-button">
|
<button class="jenkins-button" id="pattern-submit">
|
||||||
<l:icon src="symbol-arrow-right" />
|
<l:icon src="symbol-arrow-right" />
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
<st:adjunct includes="hudson.model.DirectoryBrowserSupport.pattern" />
|
||||||
</div>
|
</div>
|
||||||
<j:choose>
|
<j:choose>
|
||||||
<j:when test="${empty(files)}">
|
<j:when test="${empty(files)}">
|
||||||
|
|
|
@ -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;
|
||||||
|
});
|
||||||
|
});
|
|
@ -4,29 +4,56 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import jenkins.security.security3501Test.Security3501RootAction;
|
||||||
import org.htmlunit.FailingHttpStatusCodeException;
|
import org.htmlunit.FailingHttpStatusCodeException;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
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.JenkinsRule;
|
||||||
import org.jvnet.hudson.test.RealJenkinsRule;
|
import org.jvnet.hudson.test.RealJenkinsRule;
|
||||||
|
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
public class Security3501Test {
|
public class Security3501Test {
|
||||||
|
// Workaround for https://github.com/jenkinsci/jenkins-test-harness/issues/933
|
||||||
|
private final String contextPath;
|
||||||
|
|
||||||
@Rule
|
@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
|
@Test
|
||||||
public void testRedirects() throws Throwable {
|
public void testRedirects() throws Throwable {
|
||||||
jj.then(Security3501Test::_testRedirects);
|
jj.then(new TestRedirectsStep(contextPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void _testRedirects(JenkinsRule j) throws Exception {
|
private record TestRedirectsStep(String context) implements RealJenkinsRule.Step {
|
||||||
List<String> prohibitedPaths = List.of("%5C%5Cexample.org", "%5C/example.org", "/%5Cexample.org", "//example.org", "https://example.org", "\\example.org");
|
public void run(JenkinsRule j) throws Exception {
|
||||||
for (String path : prohibitedPaths) {
|
List<String> prohibitedPaths = List.of("%5C%5Cexample.org", "%5C/example.org", "/%5Cexample.org", "//example.org", "https://example.org", "\\example.org");
|
||||||
try (JenkinsRule.WebClient wc = j.createWebClient().withRedirectEnabled(false)) {
|
for (String path : prohibitedPaths) {
|
||||||
final FailingHttpStatusCodeException fhsce = Assert.assertThrows(FailingHttpStatusCodeException.class, () -> wc.goTo("userContent?path=" + path));
|
try (JenkinsRule.WebClient wc = j.createWebClient().withRedirectEnabled(false)) {
|
||||||
assertThat(fhsce.getStatusCode(), is(302));
|
final FailingHttpStatusCodeException fhsce = Assert.assertThrows(FailingHttpStatusCodeException.class, () -> wc.goTo("redirects/content?path=" + path));
|
||||||
assertThat(fhsce.getResponse().getResponseHeaderValue("Location"), is(j.getURL().toExternalForm() + "userContent/"));
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
@OptionalPackage(requirePlugins = "Security3501RootAction")
|
||||||
|
package jenkins.security.security3501Test;
|
||||||
|
|
||||||
|
import org.jenkinsci.plugins.variant.OptionalPackage;
|
Loading…
Reference in New Issue