High severityNVD Advisory· Published Nov 19, 2018· Updated Sep 17, 2024
UAA Privilege Escalation
CVE-2018-15761
Description
Cloud Foundry UAA release, versions prior to v64.0, and UAA, versions prior to 4.23.0, contains a validation error which allows for privilege escalation. A remote authenticated user may modify the url and content of a consent page to gain a token with arbitrary scopes that escalates their privileges.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | < 4.23.0 | 4.23.0 |
Affected products
2- Range: all versions
- Cloud Foundry/UAA Releasev5Range: all versions
Patches
4f34df0ec7b06Release v64.0
3 files changed · +58 −0
.final_builds/packages/uaa/index.yml+4 −0 modified@@ -239,6 +239,10 @@ builds: version: 6bf986583ead5ef377db34ba005e9906e6e8f49d blobstore_id: 78e565d2-9d82-4202-bcb8-edf1dc036eb0 sha1: 4d607dd4bb2c5b411d80dbd265fc3a675a520d93 + 6e13a5edd2a3cceb79d181bcac64771ea95fcccd: + version: 6e13a5edd2a3cceb79d181bcac64771ea95fcccd + blobstore_id: 14ab124d-2568-4ec2-4c2c-7e3090917b5f + sha1: 5abbc685c74d54600b48ed300b71f96d6ee6f594 72ae1ac3c77d562234fa32a002e2e720df64303e: version: 72ae1ac3c77d562234fa32a002e2e720df64303e blobstore_id: 421cba86-00e3-4305-8f5e-26c03044f58a
releases/uaa/index.yml+2 −0 modified@@ -37,6 +37,8 @@ builds: version: "59" 24dca834-abce-4612-b708-499ff3d05023: version: "44" + 259ac2dd-987c-41d2-6226-6a0e000f018a: + version: "64.0" 272a6cea-3bdd-498c-8a34-ac932149d11b: version: "30.7" 2b93ae1f-1d14-4d9b-4bfb-a75b71b13975:
releases/uaa/uaa-64.0.yml+52 −0 added@@ -0,0 +1,52 @@ +name: uaa +version: "64.0" +commit_hash: 9dfeac2 +uncommitted_changes: false +jobs: +- name: bbr-uaadb + version: 3a3b455d3de6d7f3e5196f5fe8efe63f1b18b160 + fingerprint: 3a3b455d3de6d7f3e5196f5fe8efe63f1b18b160 + sha1: d41b9d10da857484c264f94fd373c5dab28d4029 +- name: uaa + version: 2590c68e0b957e34b86fe47794c8514cbc7950e5 + fingerprint: 2590c68e0b957e34b86fe47794c8514cbc7950e5 + sha1: 1c87934105a3e7333e4ed4eb0a71392530431351 +- name: uaa_key_rotator + version: 9f01e290f1086570487a3664a14ca86040d55141 + fingerprint: 9f01e290f1086570487a3664a14ca86040d55141 + sha1: 271367693e84103d1dbca989ebf3ea23bb3ba7c2 +- name: uaa_postgres + version: 79fe0a96f112f08932b265cabc43f473c77e607c + fingerprint: 79fe0a96f112f08932b265cabc43f473c77e607c + sha1: 9a1c568e14d1d32995f12c50c76370e4bae3698e +packages: +- name: golang-1.10-linux + version: 1baefde79e99ed3d1df72084315333032a294e59 + fingerprint: 1baefde79e99ed3d1df72084315333032a294e59 + sha1: 212148ae3c30da937bea9403c857069c4530c4e3 + dependencies: [] +- name: uaa + version: 6e13a5edd2a3cceb79d181bcac64771ea95fcccd + fingerprint: 6e13a5edd2a3cceb79d181bcac64771ea95fcccd + sha1: 5abbc685c74d54600b48ed300b71f96d6ee6f594 + dependencies: [] +- name: uaa_postgres + version: 42df2bc21212094a1b61e3db697ab1c61260cb59 + fingerprint: 42df2bc21212094a1b61e3db697ab1c61260cb59 + sha1: 5f8106a0de854291aecfbb157a55e5ddf71bfe32 + dependencies: [] +- name: uaa_rotator + version: ea93fb4e077cc3886a5dbfb402e6480f94321af1 + fingerprint: ea93fb4e077cc3886a5dbfb402e6480f94321af1 + sha1: e04ec7a1daf684a4e6f2457803011eb90193312a + dependencies: + - golang-1.10-linux +- name: uaa_utils + version: 90097ea98715a560867052a2ff0916ec3460aabb + fingerprint: 90097ea98715a560867052a2ff0916ec3460aabb + sha1: 9b7d3235eede336c7aa305ffb5847d44c6800a2d + dependencies: [] +license: + version: 43aff6a01f4ba508f9cb013dcec9c612287e6c57 + fingerprint: 43aff6a01f4ba508f9cb013dcec9c612287e6c57 + sha1: 18461c918f686ee6cf8a3f777a2bbc2837e5fa96
206e2a3a8a08Bump release version to 4.23.0
1 file changed · +1 −1
gradle.properties+1 −1 modified@@ -1,4 +1,4 @@ -version=4.23.0-SNAPSHOT +version=4.23.0 # Required for LdapMockMvcTests when asserting it can find a user in a different language org.gradle.jvmargs=-Dfile.encoding=utf8 -XX:+StartAttachListener
95b7d9e7fae5Validate authz parameters against original authz request
3 files changed · +76 −18
server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationEndpoint.java+15 −10 modified@@ -29,16 +29,7 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.exceptions.BadClientCredentialsException; -import org.springframework.security.oauth2.common.exceptions.ClientAuthenticationException; -import org.springframework.security.oauth2.common.exceptions.InvalidClientException; -import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; -import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException; -import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException; -import org.springframework.security.oauth2.common.exceptions.UnauthorizedClientException; -import org.springframework.security.oauth2.common.exceptions.UnsupportedResponseTypeException; -import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException; +import org.springframework.security.oauth2.common.exceptions.*; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.ClientDetails; @@ -95,6 +86,7 @@ import static org.cloudfoundry.identity.uaa.util.JsonUtils.hasText; import static org.cloudfoundry.identity.uaa.util.UaaUrlUtils.addFragmentComponent; import static org.cloudfoundry.identity.uaa.util.UaaUrlUtils.addQueryParameter; +import static org.springframework.security.oauth2.common.util.OAuth2Utils.SCOPE_PREFIX; /** * Authorization endpoint that returns id_token's if requested. @@ -369,6 +361,19 @@ public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, throw new InvalidRequestException("Changes were detected from the original authorization request."); } + for (String approvalParameter : approvalParameters.keySet()) { + if (approvalParameter.startsWith(SCOPE_PREFIX)) { + String scope = approvalParameters.get(approvalParameter).substring(SCOPE_PREFIX.length()); + Set<String> originalScopes = (Set<String>) originalAuthorizationRequest.get("scope"); + if (!originalScopes.contains(scope)) { + sessionStatus.setComplete(); + + return new RedirectView(getUnsuccessfulRedirect(authorizationRequest, + new InvalidScopeException("The requested scopes are invalid. Please use valid scope names in the request."), false), false, true, false); + } + } + } + try { Set<String> responseTypes = authorizationRequest.getResponseTypes(); String grantType = deriveGrantTypeFromResponseType(responseTypes);
server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationEndpointTest.java+23 −0 modified@@ -19,12 +19,15 @@ import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; import org.springframework.web.bind.support.SimpleSessionStatus; import org.springframework.web.servlet.View; +import org.springframework.web.servlet.view.RedirectView; import java.util.*; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_IMPLICIT; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -174,6 +177,8 @@ public void approveUnmodifiedRequest() { View view = uaaAuthorizationEndpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); assertThat(view, notNullValue()); + assertThat(view, instanceOf(RedirectView.class)); + assertThat(((RedirectView)view).getUrl(), not(containsString("error=invalid_scope"))); } @Test(expected = InvalidRequestException.class) @@ -282,6 +287,24 @@ public void testApproveWithModifiedAuthorities() { uaaAuthorizationEndpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); } + @Test + public void testApproveWithModifiedApprovalParameters() { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "http://anywhere.com", "state-1234", "read", Collections.singleton("code")); + authorizationRequest.setApproved(false); + model.put("authorizationRequest", authorizationRequest); + model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", uaaAuthorizationEndpoint.unmodifiableMap(authorizationRequest)); + + Map<String, String> approvalParameters = new HashMap<>(); + approvalParameters.put("user_oauth_approval", "true"); + approvalParameters.put("scope.0", "foobar"); + + View view = uaaAuthorizationEndpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + + assertThat(view, instanceOf(RedirectView.class)); + assertThat(((RedirectView)view).getUrl(), containsString("error=invalid_scope")); + } + private AuthorizationRequest getAuthorizationRequest(String clientId, String redirectUri, String state, String scope, Set<String> responseTypes) { HashMap<String, String> parameters = new HashMap<>();
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/approvals/ApprovalsMockMvcTests.java+38 −8 modified@@ -49,10 +49,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; public class ApprovalsMockMvcTests extends AbstractTokenMockMvcTests { @@ -122,7 +119,7 @@ public void test_oauth_authorize_without_csrf() throws Exception { post("/oauth/authorize") .session(session) .param(USER_OAUTH_APPROVAL, "true") - .param("scope.0","test.scope1") + .param("scope.0","scope.test.scope1") ) .andExpect(status().is4xxClientError()); @@ -132,7 +129,7 @@ public void test_oauth_authorize_without_csrf() throws Exception { .with(cookieCsrf().useInvalidToken()) .session(session) .param(USER_OAUTH_APPROVAL, "true") - .param("scope.0","test.scope1") + .param("scope.0","scope.test.scope1") ) .andExpect(status().is4xxClientError()); @@ -145,8 +142,8 @@ public void test_oauth_authorize_without_csrf() throws Exception { .with(cookieCsrf()) .session(session) .param(USER_OAUTH_APPROVAL, "true") - .param("scope.0","test.scope1") - .param("scope.1","test.scope2") + .param("scope.0","scope.test.scope1") + .param("scope.1","scope.test.scope2") ) .andExpect(status().isFound()) .andExpect(redirectedUrlPattern("**/*code=*")); @@ -163,6 +160,39 @@ public void test_oauth_authorize_without_csrf() throws Exception { .andExpect(status().isFound()); //approval page no longer showing up } + @Test + public void test_oauth_authorize_modified_scope() throws Exception { + String state = generator.generate(); + + MockHttpSession session = getAuthenticatedSession(user1); + getMockMvc().perform( + get("/oauth/authorize") + .session(session) + .param(RESPONSE_TYPE, "code") + .param(STATE, state) + .param(CLIENT_ID, client1.getClientId())) + .andExpect(status().isOk()); //200 means the approvals page + + + assertNotNull(session.getAttribute("authorizationRequest")); + assertNotNull(session.getAttribute("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST")); + + getMockMvc().perform( + post("/oauth/authorize") + .with(cookieCsrf()) + .session(session) + .param(USER_OAUTH_APPROVAL, "true") + .param("scope.0","scope.different.scope") + .param("scope.1","scope.test.scope2") + ) + .andDo(print()) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrlPattern("http://test.example.org/redirect?error=invalid_scope&error_description=The%20requested%20scopes%20are%20invalid.%20Please%20use%20valid%20scope%20names%20in%20the%20request*")); + + assertNull(session.getAttribute("authorizationRequest")); + assertNull(session.getAttribute("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST")); + } + @Test public void test_get_approvals() throws Exception { test_oauth_authorize_without_csrf();
3f0730a015d1Validate authorization request on approval
4 files changed · +281 −14
server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationEndpoint.java+85 −8 modified@@ -27,6 +27,7 @@ import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.BadClientCredentialsException; import org.springframework.security.oauth2.common.exceptions.ClientAuthenticationException; @@ -58,6 +59,7 @@ import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.util.UrlUtils; import org.springframework.stereotype.Controller; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.HttpSessionRequiredException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -82,13 +84,7 @@ import java.io.UnsupportedEncodingException; import java.net.URI; import java.security.Principal; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import static java.util.Arrays.stream; import static java.util.Collections.EMPTY_SET; @@ -108,7 +104,7 @@ * https://github.com/fhanik/spring-security-oauth/compare/feature/extendable-redirect-generator?expand=1 */ @Controller -@SessionAttributes("authorizationRequest") +@SessionAttributes({"authorizationRequest", "org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST"}) public class UaaAuthorizationEndpoint extends AbstractEndpoint implements AuthenticationEntryPoint { private AuthorizationCodeServices authorizationCodeServices = new InMemoryAuthorizationCodeServices(); @@ -233,6 +229,8 @@ public ModelAndView authorize(Map<String, Object> model, // so any auth request parameters passed to approveOrDeny will be ignored and retrieved from the session. model.put("authorizationRequest", authorizationRequest); model.put("original_uri", UrlUtils.buildFullRequestUrl(request)); + model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", unmodifiableMap(authorizationRequest)); + return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal); } } catch (RedirectMismatchException e) { @@ -317,6 +315,36 @@ private ModelAndView switchIdp(Map<String, Object> model, ClientDetails client, return new ModelAndView("switch_idp", model, HttpStatus.UNAUTHORIZED); } + Map<String, Object> unmodifiableMap(AuthorizationRequest authorizationRequest) { + Map<String, Object> authorizationRequestMap = new HashMap<>(); + + authorizationRequestMap.put(OAuth2Utils.CLIENT_ID, authorizationRequest.getClientId()); + authorizationRequestMap.put(OAuth2Utils.STATE, authorizationRequest.getState()); + authorizationRequestMap.put(OAuth2Utils.REDIRECT_URI, authorizationRequest.getRedirectUri()); + + if (authorizationRequest.getResponseTypes() != null) { + authorizationRequestMap.put(OAuth2Utils.RESPONSE_TYPE, + Collections.unmodifiableSet(new HashSet<>(authorizationRequest.getResponseTypes()))); + } + if (authorizationRequest.getScope() != null) { + authorizationRequestMap.put(OAuth2Utils.SCOPE, + Collections.unmodifiableSet(new HashSet<>(authorizationRequest.getScope()))); + } + + authorizationRequestMap.put("approved", authorizationRequest.isApproved()); + + if (authorizationRequest.getResourceIds() != null) { + authorizationRequestMap.put("resourceIds", + Collections.unmodifiableSet(new HashSet<>(authorizationRequest.getResourceIds()))); + } + if (authorizationRequest.getAuthorities() != null) { + authorizationRequestMap.put("authorities", + Collections.unmodifiableSet(new HashSet<GrantedAuthority>(authorizationRequest.getAuthorities()))); + } + + return authorizationRequestMap; + } + @RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL) public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model, SessionStatus sessionStatus, Principal principal) { @@ -334,6 +362,13 @@ public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, throw new InvalidRequestException("Cannot approve uninitialized authorization request."); } + // Check to ensure the Authorization Request was not modified during the user approval step + @SuppressWarnings("unchecked") + Map<String, Object> originalAuthorizationRequest = (Map<String, Object>) model.get("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST"); + if (isAuthorizationRequestModified(authorizationRequest, originalAuthorizationRequest)) { + throw new InvalidRequestException("Changes were detected from the original authorization request."); + } + try { Set<String> responseTypes = authorizationRequest.getResponseTypes(); String grantType = deriveGrantTypeFromResponseType(responseTypes); @@ -370,6 +405,48 @@ public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, } + private boolean isAuthorizationRequestModified(AuthorizationRequest authorizationRequest, Map<String, Object> originalAuthorizationRequest) { + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.getClientId(), + originalAuthorizationRequest.get(OAuth2Utils.CLIENT_ID))) { + return true; + } + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.getState(), + originalAuthorizationRequest.get(OAuth2Utils.STATE))) { + return true; + } + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.getRedirectUri(), + originalAuthorizationRequest.get(OAuth2Utils.REDIRECT_URI))) { + return true; + } + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.getResponseTypes(), + originalAuthorizationRequest.get(OAuth2Utils.RESPONSE_TYPE))) { + return true; + } + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.isApproved(), + originalAuthorizationRequest.get("approved"))) { + return true; + } + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.getResourceIds(), + originalAuthorizationRequest.get("resourceIds"))) { + return true; + } + if (!ObjectUtils.nullSafeEquals( + authorizationRequest.getAuthorities(), + originalAuthorizationRequest.get("authorities"))) { + return true; + } + + return !ObjectUtils.nullSafeEquals( + authorizationRequest.getScope(), + originalAuthorizationRequest.get(OAuth2Utils.SCOPE)); + } + protected String deriveGrantTypeFromResponseType(Set<String> responseTypes) { if (responseTypes.contains("token")) { return GRANT_TYPE_IMPLICIT;
server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationEndpointTest.java+160 −6 modified@@ -6,21 +6,26 @@ import org.cloudfoundry.identity.uaa.oauth.token.CompositeToken; import org.junit.Before; import org.junit.Test; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; +import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.security.oauth2.provider.OAuth2RequestFactory; import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; +import org.springframework.web.bind.support.SimpleSessionStatus; +import org.springframework.web.servlet.View; -import java.util.Calendar; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; +import java.util.*; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_IMPLICIT; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -35,6 +40,10 @@ public class UaaAuthorizationEndpointTest { private Set<String> responseTypes; private OpenIdSessionStateCalculator openIdSessionStateCalculator; + private HashMap<String, Object> model = new HashMap<>(); + private SimpleSessionStatus sessionStatus = new SimpleSessionStatus(); + private UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken("foo", "bar", Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))); + @Before public void setup() { oAuth2RequestFactory = mock(OAuth2RequestFactory.class); @@ -47,6 +56,7 @@ public void setup() { responseTypes = new HashSet<>(); when(openIdSessionStateCalculator.calculate("userid", null, "http://example.com")).thenReturn("opbshash"); + when(authorizationCodeServices.createAuthorizationCode(any(OAuth2Authentication.class))).thenReturn("code"); } @@ -150,4 +160,148 @@ public void buildRedirectURI_includesSessionStateForPromptEqualsNone() { assertThat(result, containsString("session_state=opbshash")); } -} \ No newline at end of file + + @Test + public void approveUnmodifiedRequest() { + AuthorizationRequest authorizationRequest = getAuthorizationRequest("foo", "http://anywhere.com", "state-1234", "read", Collections.singleton("code")); + model.put("authorizationRequest", authorizationRequest); + model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", uaaAuthorizationEndpoint.unmodifiableMap(authorizationRequest)); + + Map<String, String> approvalParameters = new HashMap<>(); + approvalParameters.put("user_oauth_approval", "true"); + + when(authorizationCodeServices.createAuthorizationCode(any(OAuth2Authentication.class))).thenReturn("code"); + + View view = uaaAuthorizationEndpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + assertThat(view, notNullValue()); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedScope() { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "http://anywhere.com", "state-1234", "read", Collections.singleton("code")); + model.put("authorizationRequest", authorizationRequest); + model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", uaaAuthorizationEndpoint.unmodifiableMap(authorizationRequest)); + + authorizationRequest.setScope(Arrays.asList("read", "write")); // Modify authorization request + Map<String, String> approvalParameters = new HashMap<>(); + approvalParameters.put("user_oauth_approval", "true"); + + uaaAuthorizationEndpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedClientId() { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "http://anywhere.com", "state-1234", "read", Collections.singleton("code")); + model.put("authorizationRequest", authorizationRequest); + model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", uaaAuthorizationEndpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setClientId("bar"); // Modify authorization request + Map<String, String> approvalParameters = new HashMap<>(); + approvalParameters.put("user_oauth_approval", "true"); + + uaaAuthorizationEndpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedState() { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "http://anywhere.com", "state-1234", "read", Collections.singleton("code")); + model.put("authorizationRequest", authorizationRequest); + model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", uaaAuthorizationEndpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setState("state-5678"); // Modify authorization request + Map<String, String> approvalParameters = new HashMap<>(); + approvalParameters.put("user_oauth_approval", "true"); + + uaaAuthorizationEndpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedRedirectUri() { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "http://anywhere.com", "state-1234", "read", Collections.singleton("code")); + model.put("authorizationRequest", authorizationRequest); + model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", uaaAuthorizationEndpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setRedirectUri("http://somewhere.com"); // Modify authorization request + Map<String, String> approvalParameters = new HashMap<>(); + approvalParameters.put("user_oauth_approval", "true"); + + uaaAuthorizationEndpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedResponseTypes() { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "http://anywhere.com", "state-1234", "read", Collections.singleton("code")); + model.put("authorizationRequest", authorizationRequest); + model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", uaaAuthorizationEndpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setResponseTypes(Collections.singleton("implicit")); // Modify authorization request + Map<String, String> approvalParameters = new HashMap<>(); + approvalParameters.put("user_oauth_approval", "true"); + + uaaAuthorizationEndpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedApproved() { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "http://anywhere.com", "state-1234", "read", Collections.singleton("code")); + authorizationRequest.setApproved(false); + model.put("authorizationRequest", authorizationRequest); + model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", uaaAuthorizationEndpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setApproved(true); // Modify authorization request + Map<String, String> approvalParameters = new HashMap<>(); + approvalParameters.put("user_oauth_approval", "true"); + + uaaAuthorizationEndpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedResourceIds() { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "http://anywhere.com", "state-1234", "read", Collections.singleton("code")); + model.put("authorizationRequest", authorizationRequest); + model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", uaaAuthorizationEndpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setResourceIds(Collections.singleton("resource-other")); // Modify authorization request + Map<String, String> approvalParameters = new HashMap<>(); + approvalParameters.put("user_oauth_approval", "true"); + + uaaAuthorizationEndpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + @Test(expected = InvalidRequestException.class) + public void testApproveWithModifiedAuthorities() { + AuthorizationRequest authorizationRequest = getAuthorizationRequest( + "foo", "http://anywhere.com", "state-1234", "read", Collections.singleton("code")); + model.put("authorizationRequest", authorizationRequest); + model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", uaaAuthorizationEndpoint.unmodifiableMap(authorizationRequest)); + authorizationRequest.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("authority-other")); // Modify authorization request + Map<String, String> approvalParameters = new HashMap<>(); + approvalParameters.put("user_oauth_approval", "true"); + + uaaAuthorizationEndpoint.approveOrDeny(approvalParameters, model, sessionStatus, principal); + } + + private AuthorizationRequest getAuthorizationRequest(String clientId, String redirectUri, String state, + String scope, Set<String> responseTypes) { + HashMap<String, String> parameters = new HashMap<>(); + parameters.put(OAuth2Utils.CLIENT_ID, clientId); + if (redirectUri != null) { + parameters.put(OAuth2Utils.REDIRECT_URI, redirectUri); + } + if (state != null) { + parameters.put(OAuth2Utils.STATE, state); + } + if (scope != null) { + parameters.put(OAuth2Utils.SCOPE, scope); + } + if (responseTypes != null) { + parameters.put(OAuth2Utils.RESPONSE_TYPE, OAuth2Utils.formatParameterList(responseTypes)); + } + return new AuthorizationRequest(parameters, Collections.emptyMap(), + parameters.get(OAuth2Utils.CLIENT_ID), + OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)), null, null, false, + parameters.get(OAuth2Utils.STATE), parameters.get(OAuth2Utils.REDIRECT_URI), + OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.RESPONSE_TYPE))); + } +}
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/approvals/ApprovalsMockMvcTests.java+3 −0 modified@@ -115,6 +115,7 @@ public void test_oauth_authorize_without_csrf() throws Exception { assertNotNull(session.getAttribute("authorizationRequest")); + assertNotNull(session.getAttribute("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST")); //no token getMockMvc().perform( @@ -136,6 +137,7 @@ public void test_oauth_authorize_without_csrf() throws Exception { .andExpect(status().is4xxClientError()); assertNotNull(session.getAttribute("authorizationRequest")); + assertNotNull(session.getAttribute("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST")); //valid token getMockMvc().perform( @@ -150,6 +152,7 @@ public void test_oauth_authorize_without_csrf() throws Exception { .andExpect(redirectedUrlPattern("**/*code=*")); assertNull(session.getAttribute("authorizationRequest")); + assertNull(session.getAttribute("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST")); getMockMvc().perform( get("/oauth/authorize")
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java+33 −0 modified@@ -40,6 +40,7 @@ import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.codec.Base64; @@ -1534,6 +1535,7 @@ public void testOpenIdTokenHybridFlowWithNoImplicitGrantWhenLenientWhenAppNotApp authorizationRequest.setState(state); session.setAttribute("authorizationRequest", authorizationRequest); + session.setAttribute("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", unmodifiableMap(authorizationRequest)); MvcResult result = getMockMvc().perform( post("/oauth/authorize") @@ -1570,6 +1572,7 @@ public void testOpenIdTokenHybridFlowWithNoImplicitGrantWhenStrictWhenAppNotAppr authorizationRequest.setResponseTypes(new TreeSet<>(Arrays.asList("code", "id_token"))); authorizationRequest.setState(state); session.setAttribute("authorizationRequest", authorizationRequest); + session.setAttribute("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", unmodifiableMap(authorizationRequest)); MvcResult result = getMockMvc().perform( post("/oauth/authorize") @@ -4261,4 +4264,34 @@ public void setAuthentication(Authentication authentication) { this.authentication = authentication; } } + + Map<String, Object> unmodifiableMap(AuthorizationRequest authorizationRequest) { + Map<String, Object> authorizationRequestMap = new HashMap<>(); + + authorizationRequestMap.put(OAuth2Utils.CLIENT_ID, authorizationRequest.getClientId()); + authorizationRequestMap.put(OAuth2Utils.STATE, authorizationRequest.getState()); + authorizationRequestMap.put(OAuth2Utils.REDIRECT_URI, authorizationRequest.getRedirectUri()); + + if (authorizationRequest.getResponseTypes() != null) { + authorizationRequestMap.put(OAuth2Utils.RESPONSE_TYPE, + Collections.unmodifiableSet(new HashSet<>(authorizationRequest.getResponseTypes()))); + } + if (authorizationRequest.getScope() != null) { + authorizationRequestMap.put(OAuth2Utils.SCOPE, + Collections.unmodifiableSet(new HashSet<>(authorizationRequest.getScope()))); + } + + authorizationRequestMap.put("approved", authorizationRequest.isApproved()); + + if (authorizationRequest.getResourceIds() != null) { + authorizationRequestMap.put("resourceIds", + Collections.unmodifiableSet(new HashSet<String>(authorizationRequest.getResourceIds()))); + } + if (authorizationRequest.getAuthorities() != null) { + authorizationRequestMap.put("authorities", + Collections.unmodifiableSet(new HashSet<GrantedAuthority>(authorizationRequest.getAuthorities()))); + } + + return authorizationRequestMap; + } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-292x-hjr8-226fghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-15761ghsaADVISORY
- github.com/cloudfoundry/uaa/commit/3f0730a015d10166de23b7e036743c185f0576a6ghsaWEB
- github.com/cloudfoundry/uaa/commit/95b7d9e7fae534a362b98de1df5bf501cd52c481ghsaWEB
- www.cloudfoundry.org/blog/cve-2018-15761ghsaWEB
- www.cloudfoundry.org/blog/cve-2018-15761/mitrex_refsource_CONFIRM
News mentions
0No linked articles in our index yet.