KEYCLOAK-2651

No CSRF protection or general security headers on welcome page
This commit is contained in:
Stian Thorgersen 2016-04-04 09:07:21 +02:00
parent dfa7c76331
commit ff73e1a36a
3 changed files with 60 additions and 3 deletions

View file

@ -17,11 +17,20 @@
package org.keycloak.services.resources; package org.keycloak.services.resources;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.MimeTypeUtil; import org.keycloak.common.util.MimeTypeUtil;
import org.keycloak.models.BrowserSecurityHeaders;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.ApplianceBootstrap; import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.util.CacheControlUtil; import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.CookieHelper;
import org.keycloak.theme.BrowserSecurityHeaderSetup;
import org.keycloak.theme.FreeMarkerUtil; import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.theme.ThemeProvider; import org.keycloak.theme.ThemeProvider;
@ -35,8 +44,12 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException; import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -55,13 +68,20 @@ public class WelcomeResource {
private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
private static final String KEYCLOAK_STATE_CHECKER = "KEYCLOAK_STATE_CHECKER";
private boolean bootstrap; private boolean bootstrap;
@Context
protected HttpHeaders headers;
@Context @Context
private UriInfo uriInfo; private UriInfo uriInfo;
@Context @Context
protected KeycloakSession session; private KeycloakSession session;
private String stateChecker;
public WelcomeResource(boolean bootstrap) { public WelcomeResource(boolean bootstrap) {
this.bootstrap = bootstrap; this.bootstrap = bootstrap;
@ -99,6 +119,9 @@ public class WelcomeResource {
throw new WebApplicationException(Response.Status.BAD_REQUEST); throw new WebApplicationException(Response.Status.BAD_REQUEST);
} }
String stateChecker = formData.getFirst("stateChecker");
csrfCheck(stateChecker);
String username = formData.getFirst("username"); String username = formData.getFirst("username");
String password = formData.getFirst("password"); String password = formData.getFirst("password");
String passwordConfirmation = formData.getFirst("passwordConfirmation"); String passwordConfirmation = formData.getFirst("passwordConfirmation");
@ -159,6 +182,9 @@ public class WelcomeResource {
map.put("bootstrap", bootstrap); map.put("bootstrap", bootstrap);
if (bootstrap) { if (bootstrap) {
map.put("localUser", isLocal()); map.put("localUser", isLocal());
updateCsrfChecks();
map.put("stateChecker", stateChecker);
} }
if (successMessage != null) { if (successMessage != null) {
map.put("successMessage", successMessage); map.put("successMessage", successMessage);
@ -168,7 +194,12 @@ public class WelcomeResource {
} }
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil(); FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil();
String result = freeMarkerUtil.processTemplate(map, "index.ftl", getTheme()); String result = freeMarkerUtil.processTemplate(map, "index.ftl", getTheme());
return Response.status(errorMessage == null ? Response.Status.OK : Response.Status.BAD_REQUEST).entity(result).cacheControl(CacheControlUtil.noCache()).build();
ResponseBuilder rb = Response.status(errorMessage == null ? Status.OK : Status.BAD_REQUEST)
.entity(result)
.cacheControl(CacheControlUtil.noCache());
BrowserSecurityHeaderSetup.headers(rb, BrowserSecurityHeaders.defaultHeaders);
return rb.build();
} catch (Exception e) { } catch (Exception e) {
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR); throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
} }
@ -199,4 +230,23 @@ public class WelcomeResource {
} }
} }
private void updateCsrfChecks() {
RealmModel realm = session.realms().getRealmByName(Config.getAdminRealm());
Cookie cookie = headers.getCookies().get(KEYCLOAK_STATE_CHECKER);
if (cookie != null) {
stateChecker = cookie.getValue();
} else {
stateChecker = KeycloakModelUtils.generateSecret();
String cookiePath = uriInfo.getPath();
boolean secureOnly = uriInfo.getRequestUri().getScheme().equalsIgnoreCase("https");
CookieHelper.addCookie(KEYCLOAK_STATE_CHECKER, stateChecker, cookiePath, null, null, -1, secureOnly, true);
}
}
private void csrfCheck(String stateChecker) {
if (!this.stateChecker.equals(stateChecker)) {
throw new ForbiddenException();
}
}
} }

View file

@ -30,7 +30,11 @@ import java.util.Map;
public class BrowserSecurityHeaderSetup { public class BrowserSecurityHeaderSetup {
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, RealmModel realm) { public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, RealmModel realm) {
for (Map.Entry<String, String> entry : realm.getBrowserSecurityHeaders().entrySet()) { return headers(builder, realm.getBrowserSecurityHeaders());
}
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, Map<String, String> headers) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey()); String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
if (headerName != null && entry.getValue() != null && entry.getValue().length() > 0) { if (headerName != null && entry.getValue() != null && entry.getValue().length() > 0) {
builder.header(headerName, entry.getValue()); builder.header(headerName, entry.getValue());
@ -38,4 +42,5 @@ public class BrowserSecurityHeaderSetup {
} }
return builder; return builder;
} }
} }

View file

@ -90,6 +90,8 @@
<input id="passwordConfirmation" name="passwordConfirmation" type="password" /> <input id="passwordConfirmation" name="passwordConfirmation" type="password" />
</p> </p>
<input id="stateChecker" name="stateChecker" type="hidden" value="${stateChecker}" />
<button id="create-button" type="submit">Create</button> <button id="create-button" type="submit">Create</button>
</form> </form>
</#if> </#if>