CVE-2017-7662
Description
Apache CXF Fediz ships with an OpenId Connect (OIDC) service which has a Client Registration Service, which is a simple web application that allows clients to be created, deleted, etc. A CSRF (Cross Style Request Forgery) style vulnerability has been found in this web application in Apache CXF Fediz prior to 1.4.0 and 1.3.2, meaning that a malicious web application could create new clients, or reset secrets, etc, after the admin user has logged on to the client registration service and the session is still active.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.cxf.fediz:fediz-oidcMaven | < 1.3.2 | 1.3.2 |
Affected products
3- Apache Software Foundation/Apache CXF Fedizv5Range: prior to 1.4.0, 1.3.2 and 1.2.4.
Patches
1c68e4820816cAdding CSRF support to the OIDC client reg webapp
7 files changed · +163 −12
services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/clients/ClientRegistrationService.java+47 −8 modified@@ -38,6 +38,7 @@ import java.util.TreeSet; import java.util.logging.Logger; +import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.FormParam; import javax.ws.rs.GET; @@ -56,6 +57,9 @@ import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.common.util.Base64UrlUtility; import org.apache.cxf.common.util.StringUtils; +import org.apache.cxf.fediz.service.oidc.CSRFUtils; +import org.apache.cxf.message.Message; +import org.apache.cxf.phase.PhaseInterceptorChain; import org.apache.cxf.rs.security.oauth2.common.Client; import org.apache.cxf.rs.security.oauth2.common.ServerAccessToken; import org.apache.cxf.rs.security.oauth2.common.UserSubject; @@ -67,10 +71,11 @@ import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants; import org.apache.cxf.rs.security.oidc.idp.OidcUserSubject; import org.apache.cxf.rt.security.crypto.CryptoUtils; +import org.apache.cxf.transport.http.AbstractHTTPDestination; @Path("/") public class ClientRegistrationService { - + private static final Logger LOG = LogUtils.getL7dLogger(ClientRegistrationService.class); private Map<String, Collection<Client>> registrations = new HashMap<>(); @@ -119,7 +124,11 @@ public Client getRegisteredClient(@PathParam("id") String id) { @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces(MediaType.TEXT_HTML) @Path("/{id}/remove") - public RegisteredClients removeClient(@PathParam("id") String id) { + public RegisteredClients removeClient(@PathParam("id") String id, + @FormParam("client_csrfToken") String csrfToken) { + // CSRF + checkCSRFToken(csrfToken); + Collection<Client> clients = getClientRegistrations(); for (Iterator<Client> it = clients.iterator(); it.hasNext();) { Client c = it.next(); @@ -139,7 +148,11 @@ public RegisteredClients removeClient(@PathParam("id") String id) { @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces(MediaType.TEXT_HTML) @Path("/{id}/reset") - public Client resetClient(@PathParam("id") String id) { + public Client resetClient(@PathParam("id") String id, + @FormParam("client_csrfToken") String csrfToken) { + // CSRF + checkCSRFToken(csrfToken); + Client c = getRegisteredClient(id); if (c.isConfidential()) { c.setClientSecret(generateClientSecret()); @@ -172,7 +185,11 @@ protected ClientTokens doGetClientIssuedTokens(Client c) { @Produces(MediaType.TEXT_HTML) @Path("/{id}/at/{tokenId}/revoke") public ClientTokens revokeClientAccessToken(@PathParam("id") String clientId, - @PathParam("tokenId") String tokenId) { + @PathParam("tokenId") String tokenId, + @FormParam("client_csrfToken") String csrfToken) { + // CSRF + checkCSRFToken(csrfToken); + return doRevokeClientToken(clientId, tokenId, OAuthConstants.ACCESS_TOKEN); } @@ -181,7 +198,11 @@ public ClientTokens revokeClientAccessToken(@PathParam("id") String clientId, @Produces(MediaType.TEXT_HTML) @Path("/{id}/rt/{tokenId}/revoke") public ClientTokens revokeClientRefreshToken(@PathParam("id") String clientId, - @PathParam("tokenId") String tokenId) { + @PathParam("tokenId") String tokenId, + @FormParam("client_csrfToken") String csrfToken) { + // CSRF + checkCSRFToken(csrfToken); + return doRevokeClientToken(clientId, tokenId, OAuthConstants.REFRESH_TOKEN); } @@ -213,7 +234,11 @@ public ClientCodeGrants getClientCodeGrants(@PathParam("id") String id) { @Produces(MediaType.TEXT_HTML) @Path("/{id}/codes/{code}/revoke") public ClientCodeGrants revokeClientCodeGrant(@PathParam("id") String id, - @PathParam("code") String code) { + @PathParam("code") String code, + @FormParam("client_csrfToken") String csrfToken) { + // CSRF + checkCSRFToken(csrfToken); + if (dataProvider instanceof AuthorizationCodeDataProvider) { ((AuthorizationCodeDataProvider)dataProvider).removeCodeGrant(code); return getClientCodeGrants(id); @@ -230,9 +255,13 @@ public Response registerForm(@FormParam("client_name") String appName, @FormParam("client_audience") String audience, @FormParam("client_redirectURI") String redirectURI, @FormParam("client_logoutURI") String logoutURI, - @FormParam("client_homeRealm") String homeRealm + @FormParam("client_homeRealm") String homeRealm, + @FormParam("client_csrfToken") String csrfToken ) { try { + // CSRF + checkCSRFToken(csrfToken); + // Client Name if (StringUtils.isEmpty(appName)) { throwInvalidRegistrationException("The client name must not be empty"); @@ -322,13 +351,23 @@ public Response registerForm(@FormParam("client_name") String appName, } } + private void checkCSRFToken(String csrfToken) { + // CSRF + Message message = PhaseInterceptorChain.getCurrentMessage(); + HttpServletRequest httpRequest = (HttpServletRequest) message.get(AbstractHTTPDestination.HTTP_REQUEST); + String savedToken = CSRFUtils.getCSRFToken(httpRequest, false); + if (StringUtils.isEmpty(csrfToken) || StringUtils.isEmpty(savedToken) + || !savedToken.equals(csrfToken)) { + throwInvalidRegistrationException("Invalid CSRF Token"); + } + } private void throwInvalidRegistrationException(String error) { throw new InvalidRegistrationException(error); } private boolean isValidURI(String uri, boolean requireHttps) { - + UrlValidator urlValidator = null; if (requireHttps) {
services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/CSRFUtils.java+52 −0 added@@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.fediz.service.oidc; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.cxf.common.util.StringUtils; +import org.apache.cxf.rt.security.crypto.CryptoUtils; + +public final class CSRFUtils { + + public static final String CSRF_TOKEN = "CSRF_TOKEN"; + + private CSRFUtils() { + // complete + } + + public static String getCSRFToken(HttpServletRequest request, boolean create) { + if (request != null && request.getSession() != null) { + // Return an existing token first + String savedToken = (String)request.getSession().getAttribute(CSRF_TOKEN); + if (savedToken != null) { + return savedToken; + } + + // If no existing token then create a new one, save it, and return it + if (create) { + String token = StringUtils.toHexString(CryptoUtils.generateSecureRandomBytes(16)); + request.getSession().setAttribute(CSRF_TOKEN, token); + return token; + } + } + + return null; + } +}
services/oidc/src/main/webapp/WEB-INF/views/clientCodeGrants.jsp+6 −1 modified@@ -6,6 +6,7 @@ <%@ page import="java.util.Locale"%> <%@ page import="java.util.TimeZone"%> <%@ page import="javax.servlet.http.HttpServletRequest" %> +<%@ page import="org.apache.cxf.fediz.service.oidc.CSRFUtils" %> <%@ page import="org.apache.cxf.fediz.service.oidc.clients.ClientCodeGrants" %> <%@ page import="org.owasp.esapi.ESAPI" %> @@ -15,7 +16,10 @@ String basePath = request.getContextPath() + request.getServletPath(); if (!basePath.endsWith("/")) { basePath += "/"; - } + } + + // Get or generate the CSRF token + String csrfToken = CSRFUtils.getCSRFToken(request, true); %> <html xmlns="http://www.w3.org/1999/xhtml"> <head> @@ -76,6 +80,7 @@ %> <td> <form action="<%=basePath%>console/clients/<%= client.getClientId() + "/codes/" + token.getCode() + "/revoke"%>" method="POST"> + <input type="hidden" value="<%=csrfToken%>" name="client_csrfToken" /> <input type="submit" value="Delete"/> </form> </td>
services/oidc/src/main/webapp/WEB-INF/views/client.jsp+11 −1 modified@@ -4,6 +4,7 @@ <%@ page import="java.util.Locale"%> <%@ page import="java.util.TimeZone"%> <%@ page import="javax.servlet.http.HttpServletRequest" %> +<%@ page import="org.apache.cxf.fediz.service.oidc.CSRFUtils" %> <%@ page import="org.owasp.esapi.ESAPI" %> <% @@ -16,7 +17,10 @@ String basePath = request.getContextPath() + request.getServletPath(); if (!basePath.endsWith("/")) { basePath += "/"; - } + } + + // Get or generate the CSRF token + String token = CSRFUtils.getCSRFToken(request, true); %> <html xmlns="http://www.w3.org/1999/xhtml"> <head> @@ -178,6 +182,9 @@ %> <td class="td_no_border"> <form name="resetSecretForm" action="<%=basePath%>console/clients/<%= client.getClientId() + "/reset"%>" method="POST"> + <div class="form-line"> + <input type="hidden" value="<%=token%>" name="client_csrfToken" /> + </div> <div data-type="control_button" class="form-line"> <button name="submit_reset_button" class="form-submit-button" type="submit">Reset Client Secret</button> </form> @@ -188,6 +195,9 @@ %> <td class="td_no_border"> <form name="deleteForm" action="<%=basePath%>console/clients/<%= client.getClientId() + "/remove"%>" method="POST"> + <div class="form-line"> + <input type="hidden" value="<%=token%>" name="client_csrfToken" /> + </div> <div data-type="control_button" class="form-line"> <button name="submit_delete_button" class="form-submit-button" type="submit">Delete Client</button> </div>
services/oidc/src/main/webapp/WEB-INF/views/clientTokens.jsp+6 −1 modified@@ -7,6 +7,7 @@ <%@ page import="java.util.Locale"%> <%@ page import="java.util.TimeZone"%> <%@ page import="javax.servlet.http.HttpServletRequest" %> +<%@ page import="org.apache.cxf.fediz.service.oidc.CSRFUtils" %> <%@ page import="org.apache.cxf.fediz.service.oidc.clients.ClientTokens" %> <%@ page import="org.owasp.esapi.ESAPI" %> @@ -19,7 +20,9 @@ } SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss", Locale.US); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); - + + // Get or generate the CSRF token + String csrfToken = CSRFUtils.getCSRFToken(request, true); %> <html xmlns="http://www.w3.org/1999/xhtml"> <head> @@ -111,6 +114,7 @@ %> <td> <form action="<%=basePath%>console/clients/<%= client.getClientId() + "/at/" + token.getTokenKey() + "/revoke"%>" method="POST"> + <input type="hidden" value="<%=csrfToken%>" name="client_csrfToken" /> <input type="submit" value="Delete"/> </form> </td> @@ -170,6 +174,7 @@ <td> <form action="<%=basePath%>console/clients/<%= client.getClientId() + "/rt/" + token.getTokenKey() + "/revoke"%>" method="POST"> + <input type="hidden" value="<%=csrfToken%>" name="client_csrfToken" /> <input type="submit" value="Delete"/> </form> </td>
services/oidc/src/main/webapp/WEB-INF/views/registerClient.jsp+9 −1 modified@@ -1,11 +1,16 @@ <%@ page - import="javax.servlet.http.HttpServletRequest,java.util.Map,java.util.Iterator,org.apache.cxf.fediz.service.oidc.clients.RegisterClient"%> + import="javax.servlet.http.HttpServletRequest,java.util.Map,java.util.Iterator,org.apache.cxf.fediz.service.oidc.clients.RegisterClient, + org.apache.cxf.fediz.service.oidc.CSRFUtils" +%> <% RegisterClient reg = (RegisterClient)request.getAttribute("data"); String basePath = request.getContextPath() + request.getServletPath(); if (!basePath.endsWith("/")) { basePath += "/"; } + + // Get or generate the CSRF token + String csrfToken = CSRFUtils.getCSRFToken(request, true); %> <html xmlns="http://www.w3.org/1999/xhtml"> <head> @@ -113,6 +118,9 @@ input, select, button { %> </select> </div> + <div class="form-line"> + <input type="hidden" value="<%=csrfToken%>" name="client_csrfToken" /> + </div> <div data-type="control_button" class="form-line"> <button name="submit_button" class="form-submit-button" type="submit">Register API Client</button> </div>
systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/OIDCTest.java+32 −0 modified@@ -794,6 +794,38 @@ public void testLogout() throws Exception { webClient.close(); } + // Test that the form has the correct CSRF token in it when creating a client + @org.junit.Test + public void testCSRFClientRegistration() throws Exception { + String url = "https://localhost:" + getRpHttpsPort() + "/fediz-oidc/console/clients"; + String user = "alice"; + String password = "ecila"; + + // Login to the client page successfully + WebClient webClient = setupWebClient(user, password, getIdpHttpsPort()); + HtmlPage loginPage = login(url, webClient); + final String bodyTextContent = loginPage.getBody().getTextContent(); + Assert.assertTrue(bodyTextContent.contains("Registered Clients")); + + // Register a new client + + WebRequest request = new WebRequest(new URL(url), HttpMethod.POST); + request.setRequestParameters(new ArrayList<NameValuePair>()); + + request.getRequestParameters().add(new NameValuePair("client_name", "bad_client")); + request.getRequestParameters().add(new NameValuePair("client_type", "confidential")); + request.getRequestParameters().add(new NameValuePair("client_redirectURI", "https://127.0.0.1")); + request.getRequestParameters().add(new NameValuePair("client_audience", "")); + request.getRequestParameters().add(new NameValuePair("client_logoutURI", "")); + request.getRequestParameters().add(new NameValuePair("client_homeRealm", "")); + request.getRequestParameters().add(new NameValuePair("client_csrfToken", "12345")); + + HtmlPage registeredClientPage = webClient.getPage(request); + Assert.assertTrue(registeredClientPage.asXml().contains("Invalid CSRF Token")); + + webClient.close(); + } + private static WebClient setupWebClient(String user, String password, String idpPort) { final WebClient webClient = new WebClient(); webClient.getOptions().setUseInsecureSSL(true);
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
17- cxf.apache.org/security-advisories.data/CVE-2017-7662.txt.ascnvdPatchVendor AdvisoryWEB
- github.com/advisories/GHSA-f5ch-36rg-vfccghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2017-7662ghsaADVISORY
- www.securitytracker.com/id/1038498nvdWEB
- github.com/apache/cxf-fediz/commit/c68e4820816c19241568f4a8fe8600bffb0243cdghsaWEB
- lists.apache.org/thread.html/r36e44ffc1a9b365327df62cdfaabe85b9a5637de102cea07d79b2dbf@%3Ccommits.cxf.apache.org%3EghsaWEB
- lists.apache.org/thread.html/rc774278135816e7afc943dc9fc78eb0764f2c84a2b96470a0187315c@%3Ccommits.cxf.apache.org%3EghsaWEB
- lists.apache.org/thread.html/rd49aabd984ed540c8ff7916d4d79405f3fa311d2fdbcf9ed307839a6@%3Ccommits.cxf.apache.org%3EghsaWEB
- lists.apache.org/thread.html/rec7160382badd3ef4ad017a22f64a266c7188b9ba71394f0d321e2d4@%3Ccommits.cxf.apache.org%3EghsaWEB
- lists.apache.org/thread.html/rfb87e0bf3995e7d560afeed750fac9329ff5f1ad49da365129b7f89e@%3Ccommits.cxf.apache.org%3EghsaWEB
- lists.apache.org/thread.html/rff42cfa5e7d75b7c1af0e37589140a8f1999e578a75738740b244bd4@%3Ccommits.cxf.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r36e44ffc1a9b365327df62cdfaabe85b9a5637de102cea07d79b2dbf%40%3Ccommits.cxf.apache.org%3Envd
- lists.apache.org/thread.html/rc774278135816e7afc943dc9fc78eb0764f2c84a2b96470a0187315c%40%3Ccommits.cxf.apache.org%3Envd
- lists.apache.org/thread.html/rd49aabd984ed540c8ff7916d4d79405f3fa311d2fdbcf9ed307839a6%40%3Ccommits.cxf.apache.org%3Envd
- lists.apache.org/thread.html/rec7160382badd3ef4ad017a22f64a266c7188b9ba71394f0d321e2d4%40%3Ccommits.cxf.apache.org%3Envd
- lists.apache.org/thread.html/rfb87e0bf3995e7d560afeed750fac9329ff5f1ad49da365129b7f89e%40%3Ccommits.cxf.apache.org%3Envd
- lists.apache.org/thread.html/rff42cfa5e7d75b7c1af0e37589140a8f1999e578a75738740b244bd4%40%3Ccommits.cxf.apache.org%3Envd
News mentions
0No linked articles in our index yet.