From 8fdfb985c6aa59869e6821bc2ac0f18bb25bb377 Mon Sep 17 00:00:00 2001 From: Allan Burdajewicz Date: Fri, 30 Aug 2024 23:26:27 +1000 Subject: [PATCH] [JENKINS-73422] Add escape hatch for Authenticated user access to Resource URL (#9644) Co-authored-by: Daniel Beck <1831569+daniel-beck@users.noreply.github.com> (cherry picked from commit 5fe9a448059c5aafd2fa354cdabb12e05b3978e2) --- .../jenkins/security/ResourceDomainRootAction.java | 6 +++++- .../test/java/jenkins/security/ResourceDomainTest.java | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/jenkins/security/ResourceDomainRootAction.java b/core/src/main/java/jenkins/security/ResourceDomainRootAction.java index 7955103d6be5..fc18071fade7 100644 --- a/core/src/main/java/jenkins/security/ResourceDomainRootAction.java +++ b/core/src/main/java/jenkins/security/ResourceDomainRootAction.java @@ -117,7 +117,7 @@ public Object getDynamic(String id, StaplerRequest req, StaplerResponse rsp) thr return null; } - if (!ACL.isAnonymous2(Jenkins.getAuthentication2())) { + if (!ALLOW_AUTHENTICATED_USER && !ACL.isAnonymous2(Jenkins.getAuthentication2())) { rsp.sendError(400); return null; } @@ -327,4 +327,8 @@ private static Token decode(String value) { // Not @Restricted because the entire class is @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") public static /* not final for Groovy */ int VALID_FOR_MINUTES = SystemProperties.getInteger(ResourceDomainRootAction.class.getName() + ".validForMinutes", 30); + + /* Escape hatch for a security hardening preventing one of the known ways to elevate arbitrary file read to RCE */ + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "for script console") + public static /* not final for Groovy */ boolean ALLOW_AUTHENTICATED_USER = SystemProperties.getBoolean(ResourceDomainRootAction.class.getName() + ".allowAuthenticatedUser", false); } diff --git a/test/src/test/java/jenkins/security/ResourceDomainTest.java b/test/src/test/java/jenkins/security/ResourceDomainTest.java index 42f2a1dbbf78..b8f2d551b094 100644 --- a/test/src/test/java/jenkins/security/ResourceDomainTest.java +++ b/test/src/test/java/jenkins/security/ResourceDomainTest.java @@ -399,7 +399,7 @@ public HttpResponse doDynamic() throws Exception { } @Test - public void authenticatedCannotAccessResourceDomain() throws Exception { + public void authenticatedCannotAccessResourceDomainUnlessAllowedBySystemProperty() throws Exception { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); final MockAuthorizationStrategy authorizationStrategy = new MockAuthorizationStrategy(); authorizationStrategy.grant(Jenkins.ADMINISTER).everywhere().to("admin").grant(Jenkins.READ).everywhere().toEveryone(); @@ -416,5 +416,13 @@ public void authenticatedCannotAccessResourceDomain() throws Exception { try (JenkinsRule.WebClient wc = j.createWebClient().withBasicCredentials("admin")) { assertThat(assertThrows(FailingHttpStatusCodeException.class, () -> wc.getPage(new URL(resourceUrl))).getStatusCode(), is(400)); } + + ResourceDomainRootAction.ALLOW_AUTHENTICATED_USER = true; + try (JenkinsRule.WebClient wc = j.createWebClient().withBasicApiToken("admin")) { + assertThat(wc.getPage(new URL(resourceUrl)).getWebResponse().getStatusCode(), is(200)); + } + try (JenkinsRule.WebClient wc = j.createWebClient().withBasicCredentials("admin")) { + assertThat(wc.getPage(new URL(resourceUrl)).getWebResponse().getStatusCode(), is(200)); + } } }