From 6702936db936ffeedf32408e3575f396e2e8a509 Mon Sep 17 00:00:00 2001 From: Edmundo Alvarez Date: Fri, 10 Aug 2018 18:37:30 +0200 Subject: [PATCH 1/2] Add custom request header to prevent CSRF (#4987) Improve our protection against CSRF by requiring a custom request header (`X-Requested-By`) in all non-GET requests sent to our API. This is mentioned as a way of CSRF prevention [1] and it is particularly suitable for REST APIs, since let requests to remain stateless. 1: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Protecting_REST_Services:_Use_of_Custom_Request_Headers --- .../main/java/org/graylog2/rest/RemoteInterfaceProvider.java | 2 ++ .../java/org/graylog2/shared/initializers/JerseyService.java | 2 ++ .../src/main/java/org/graylog2/shared/rest/CORSFilter.java | 4 ++-- .../src/main/resources/swagger/index.html.template | 4 ++++ graylog2-web-interface/src/logic/rest/FetchProvider.js | 4 +++- 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/graylog2-server/src/main/java/org/graylog2/rest/RemoteInterfaceProvider.java b/graylog2-server/src/main/java/org/graylog2/rest/RemoteInterfaceProvider.java index 00b2ca681be4..904f031a4bae 100644 --- a/graylog2-server/src/main/java/org/graylog2/rest/RemoteInterfaceProvider.java +++ b/graylog2-server/src/main/java/org/graylog2/rest/RemoteInterfaceProvider.java @@ -21,6 +21,7 @@ import com.google.common.net.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; +import org.glassfish.jersey.client.filter.CsrfProtectionFilter; import org.graylog2.cluster.Node; import org.graylog2.security.realm.SessionAuthenticator; import retrofit2.Retrofit; @@ -46,6 +47,7 @@ public T get(Node node, final String authorizationToken, Class interfaceC Request.Builder builder = original.newBuilder() .header(HttpHeaders.ACCEPT, MediaType.JSON_UTF_8.toString()) + .header(CsrfProtectionFilter.HEADER_NAME, "Graylog Server") .method(original.method(), original.body()); if (authorizationToken != null) { diff --git a/graylog2-server/src/main/java/org/graylog2/shared/initializers/JerseyService.java b/graylog2-server/src/main/java/org/graylog2/shared/initializers/JerseyService.java index fe7a3fb28b7f..3d7b7413c2f7 100644 --- a/graylog2-server/src/main/java/org/graylog2/shared/initializers/JerseyService.java +++ b/graylog2-server/src/main/java/org/graylog2/shared/initializers/JerseyService.java @@ -32,6 +32,7 @@ import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.ServerProperties; +import org.glassfish.jersey.server.filter.CsrfProtectionFilter; import org.glassfish.jersey.server.model.Resource; import org.graylog2.Configuration; import org.graylog2.audit.PluginAuditEventTypes; @@ -281,6 +282,7 @@ private ResourceConfig buildResourceConfig(final boolean enableCors, .register(new PrefixAddingModelProcessor(packagePrefixes)) .register(new AuditEventModelProcessor(pluginAuditEventTypes)) .registerClasses( + CsrfProtectionFilter.class, JacksonJaxbJsonProvider.class, JsonProcessingExceptionMapper.class, JacksonPropertyExceptionMapper.class, diff --git a/graylog2-server/src/main/java/org/graylog2/shared/rest/CORSFilter.java b/graylog2-server/src/main/java/org/graylog2/shared/rest/CORSFilter.java index ea87ffeb4347..4f21ab6fa621 100644 --- a/graylog2-server/src/main/java/org/graylog2/shared/rest/CORSFilter.java +++ b/graylog2-server/src/main/java/org/graylog2/shared/rest/CORSFilter.java @@ -40,7 +40,7 @@ public void filter(ContainerRequestContext requestContext, ContainerResponseCont if (origin != null && !origin.isEmpty()) { responseContext.getHeaders().add("Access-Control-Allow-Origin", origin); responseContext.getHeaders().add("Access-Control-Allow-Credentials", true); - responseContext.getHeaders().add("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Graylog-No-Session-Extension, X-Requested-With"); + responseContext.getHeaders().add("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Graylog-No-Session-Extension, X-Requested-With, X-Requested-By"); responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); // In order to avoid redoing the preflight thingy for every request, see http://stackoverflow.com/a/12021982/1088469 responseContext.getHeaders().add("Access-Control-Max-Age", "600"); // 10 minutes seems to be the maximum allowable value @@ -57,7 +57,7 @@ public void filter(ContainerRequestContext requestContext) throws IOException { options.header("Access-Control-Allow-Origin", origin); options.header("Access-Control-Allow-Credentials", true); options.header("Access-Control-Allow-Headers", - "Authorization, Content-Type, X-Graylog-No-Session-Extension, X-Requested-With"); + "Authorization, Content-Type, X-Graylog-No-Session-Extension, X-Requested-With, X-Requested-By"); options.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); // In order to avoid redoing the preflight thingy for every request, see http://stackoverflow.com/a/12021982/1088469 options.header("Access-Control-Max-Age", "600"); // 10 minutes seems to be the maximum allowable value diff --git a/graylog2-server/src/main/resources/swagger/index.html.template b/graylog2-server/src/main/resources/swagger/index.html.template index 1b3cf7007c04..32a861c6a5f8 100644 --- a/graylog2-server/src/main/resources/swagger/index.html.template +++ b/graylog2-server/src/main/resources/swagger/index.html.template @@ -63,6 +63,10 @@ }; $('#input_apiUser').change(updateApiAuth); $('#input_apiPassword').change(updateApiAuth); + + // Add CSRF header + window.authorizations.add("csrf", new ApiKeyAuthorization("X-Requested-By", "Graylog API Browser", "header")); + window.swaggerUi.load(); }); diff --git a/graylog2-web-interface/src/logic/rest/FetchProvider.js b/graylog2-web-interface/src/logic/rest/FetchProvider.js index ae0ec79fed15..814f3451691e 100644 --- a/graylog2-web-interface/src/logic/rest/FetchProvider.js +++ b/graylog2-web-interface/src/logic/rest/FetchProvider.js @@ -27,7 +27,9 @@ export class FetchError extends Error { export class Builder { constructor(method, url) { - this.request = request(method, url.replace(/([^:])\/\//, '$1/')).set('X-Requested-With', 'XMLHttpRequest'); + this.request = request(method, url.replace(/([^:])\/\//, '$1/')) + .set('X-Requested-With', 'XMLHttpRequest') + .set('X-Requested-By', 'XMLHttpRequest'); } authenticated() { From 370dd700bc8ada5448bf66459dec9a85fcd22d58 Mon Sep 17 00:00:00 2001 From: Edmundo Alvarez Date: Wed, 15 Aug 2018 17:32:42 +0200 Subject: [PATCH 2/2] Include CSRF HTTP header in upgrading document --- UPGRADING.rst | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/UPGRADING.rst b/UPGRADING.rst index fe9491246bbb..8890461b0f95 100644 --- a/UPGRADING.rst +++ b/UPGRADING.rst @@ -1,28 +1,20 @@ ************************** -Upgrading to Graylog 2.4.x +Upgrading to Graylog 2.5.x ************************** -.. _upgrade-from-23-to-24: +.. _upgrade-from-24-to-25: This file only contains the upgrade note for the upcoming release. Please see `our documentation `_ for the complete upgrade notes. -More plugins shipped by default -=============================== +Protecting against CSRF, HTTP header required +============================================= -The following Graylog plugins are now shipped as part of the Graylog server release. +Graylog server now requires all clients sending non-GET requests against the API to include a custom HTTP header +(``X-Requested-By``). The value of the header is not important, but it's presence is, as all requests without it will +be ignored and will return a 400 error. -- AWS Plugin - https://github.com/Graylog2/graylog-plugin-aws -- Threat Intelligence Plugin - https://github.com/Graylog2/graylog-plugin-threatintel -- NetFlow Plugin - https://github.com/Graylog2/graylog-plugin-netflow -- CEF Plugin - https://github.com/Graylog2/graylog-plugin-cef - -Make sure you remove all previous versions of these plugins from your ``plugin/`` folder! - -Removal of anonymous usage-stats plugin -======================================= - -The `anonymous usage-stats plugin `_ -got removed from Graylog and is now deprecated. Make sure you remove all old versions -of the plugin from your ``plugin/`` folder! +**This is important for people using scripts that modify Graylog in any way through the REST API**. We already adapted +Graylog web interface and our plugins, so if you don't use any scripts or 3rd party products to access Graylog, you +don't have to do anything else.