From 85c6018f6a300aa12da8e825d172e7819db0d569 Mon Sep 17 00:00:00 2001 From: Danila Vershinin Date: Thu, 5 Dec 2019 22:58:30 +0300 Subject: [PATCH] Ability to control Referrer-Policy header #2 --- README.md | 33 +++++++----- src/ngx_http_security_headers_module.c | 74 ++++++++++++++++++++++++-- t/headers.t | 46 +++++++++++++++- 3 files changed, 137 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 49a0630..d27995a 100644 --- a/README.md +++ b/README.md @@ -23,20 +23,14 @@ Accept-Ranges: bytes Connection: keep-alive X-Frame-Options: SAMEORIGIN <----------- X-XSS-Protection: 1; mode=block <----------- +Referrer-Policy: no-referrer-when-downgrade <----------- ``` -Running `curl -IL http://example.com/some.css` (or `some.js`) will yield additional headers: +Running `curl -IL http://example.com/some.css` (or `some.js`) will yield *additional* security header: ``` HTTP/1.1 200 OK -Server: nginx -Date: Tue, 21 May 2019 16:15:46 GMT -Content-Type: text/css; charset=UTF-8 -Vary: Accept-Encoding -Accept-Ranges: bytes -Connection: keep-alive -X-Frame-Options: SAMEORIGIN <----------- -X-XSS-Protection: 1; mode=block <----------- +... X-Content-Type-Options: nosniff <----------- ``` @@ -62,6 +56,7 @@ Enables or disables applying security headers. The default set includes: * `X-Frame-Options: SAMEORIGIN` * `X-XSS-Protection: 1; mode=block` +* `Referrer-Policy: strict-origin-when-cross-origin` * `X-Content-Type-Options: nosniff` (for CSS and Javascript) The values of these headers (or their inclusion) can be controlled with other `security_headers_*` directives below. @@ -77,6 +72,9 @@ Enables hiding headers which leak software information: * `Server` * `X-Powered-By` +Next are the common security headers being set. It's worth noting that special value of `omit` for directives below +will disable sending a particular header by the module (useful if you want to let your backend app to send it). + ### `security_headers_xss` - **syntax**: `security_headers off | on | block | omit` @@ -84,17 +82,28 @@ Enables hiding headers which leak software information: - **context**: `http`, `server`, `location` Controls `X-XSS-Protection` header. -Special `omit` value will disable sending the header. +Special `omit` value will disable sending the header by the module. The `off` value is for disabling XSS protection: `X-XSS-Protection: 0`. ### `security_headers_frame` -- **syntax**: `security_headers_frames sameorigin | deny | omit` +- **syntax**: `security_headers_frame sameorigin | deny | omit` - **default**: `sameorigin` - **context**: `http`, `server`, `location` Controls inclusion and value of `X-Frame-Options` header. -Special `omit` value will disable sending the header. +Special `omit` value will disable sending the header by the module. + + +### `security_headers_referrer_policy` + +- **syntax**: `security_headers_referrer_policy no-referrer | no-referrer-when-downgrade | same-origin | origin +| strict-origin | origin-when-cross-origin | strict-origin-when-cross-origin | unsafe-url | omit` +- **default**: `no-referrer-when-downgrade` +- **context**: `http`, `server`, `location` + +Controls inclusion and value of [`Referrer-Policy`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) header. +Special `omit` value will disable sending the header by the module. ### `security_headers_nosniff_types` diff --git a/src/ngx_http_security_headers_module.c b/src/ngx_http_security_headers_module.c index 92c83ad..ffd8146 100644 --- a/src/ngx_http_security_headers_module.c +++ b/src/ngx_http_security_headers_module.c @@ -16,6 +16,17 @@ #define NGX_HTTP_FO_HEADER_SAME 1 #define NGX_HTTP_FO_HEADER_DENY 2 +/* The Referrer Policy header */ +#define NGX_HTTP_RP_HEADER_NO 1 +#define NGX_HTTP_RP_HEADER_DOWNGRADE 2 +#define NGX_HTTP_RP_HEADER_SAME_ORIGIN 3 +#define NGX_HTTP_RP_HEADER_ORIGIN 4 +#define NGX_HTTP_RP_HEADER_STRICT_ORIGIN 5 +#define NGX_HTTP_RP_HEADER_ORIGIN_WHEN_CROSS 6 +#define NGX_HTTP_RP_HEADER_STRICT_ORIG_WHEN_CROSS 7 +#define NGX_HTTP_RP_HEADER_UNSAFE_URL 8 + + typedef struct { ngx_flag_t enable; @@ -23,6 +34,7 @@ typedef struct { ngx_uint_t xss; ngx_uint_t fo; + ngx_uint_t rp; ngx_hash_t nosniff_types; ngx_array_t *types_keys; @@ -44,6 +56,34 @@ static ngx_conf_enum_t ngx_http_frame_options[] = { { ngx_null_string, 0 } }; +static ngx_conf_enum_t ngx_http_referrer_policy[] = { + { ngx_string("no-referrer"), + NGX_HTTP_RP_HEADER_NO }, + + { ngx_string("no-referrer-when-downgrade"), + NGX_HTTP_RP_HEADER_DOWNGRADE }, + + { ngx_string("same-origin"), + NGX_HTTP_RP_HEADER_SAME_ORIGIN }, + + { ngx_string("origin"), + NGX_HTTP_RP_HEADER_ORIGIN }, + + { ngx_string("strict-origin"), + NGX_HTTP_RP_HEADER_STRICT_ORIGIN }, + + { ngx_string("origin-when-cross-origin"), + NGX_HTTP_RP_HEADER_ORIGIN_WHEN_CROSS }, + + { ngx_string("unsafe-url"), + NGX_HTTP_RP_HEADER_UNSAFE_URL }, + + { ngx_string("omit"), + NGX_HTTP_SECURITY_HEADER_OMIT }, + + { ngx_null_string, 0 } +}; + static ngx_int_t ngx_http_security_headers_filter(ngx_http_request_t *r); static void *ngx_http_security_headers_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_security_headers_merge_loc_conf(ngx_conf_t *cf, @@ -96,6 +136,13 @@ static ngx_command_t ngx_http_security_headers_commands[] = { offsetof(ngx_http_security_headers_loc_conf_t, fo), ngx_http_frame_options }, + { ngx_string("security_headers_referrer_policy"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_enum_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_security_headers_loc_conf_t, rp), + ngx_http_referrer_policy }, + ngx_null_command }; @@ -220,9 +267,27 @@ ngx_http_security_headers_filter(ngx_http_request_t *r) } /* Referrer-Policy: no-referrer-when-downgrade */ - if (r->headers_out.status != NGX_HTTP_NOT_MODIFIED) { - ngx_str_set(&key, "Referrer-Policy"); - ngx_str_set(&val, "no-referrer-when-downgrade"); + if (r->headers_out.status != NGX_HTTP_NOT_MODIFIED + && NGX_HTTP_SECURITY_HEADER_OMIT != slcf->rp) { + ngx_str_set(&key, "Referrer-Policy"); + + if (NGX_HTTP_RP_HEADER_NO == slcf->rp) { + ngx_str_set(&val, "no-referrer"); + } else if (NGX_HTTP_RP_HEADER_DOWNGRADE == slcf->rp) { + ngx_str_set(&val, "no-referrer-when-downgrade"); + } else if (NGX_HTTP_RP_HEADER_SAME_ORIGIN == slcf->rp) { + ngx_str_set(&val, "same-origin"); + } else if (NGX_HTTP_RP_HEADER_ORIGIN == slcf->rp) { + ngx_str_set(&val, "origin"); + } else if (NGX_HTTP_RP_HEADER_STRICT_ORIGIN == slcf->rp) { + ngx_str_set(&val, "strict-origin"); + } else if (NGX_HTTP_RP_HEADER_ORIGIN_WHEN_CROSS == slcf->rp) { + ngx_str_set(&val, "origin-when-cross-origin"); + } else if (NGX_HTTP_RP_HEADER_STRICT_ORIG_WHEN_CROSS == slcf->rp) { + ngx_str_set(&val, "strict-origin-when-cross-origin"); + } else if (NGX_HTTP_RP_HEADER_UNSAFE_URL == slcf->rp) { + ngx_str_set(&val, "unsafe-url"); + } ngx_set_headers_out_by_search(r, &key, &val); } @@ -245,6 +310,7 @@ ngx_http_security_headers_create_loc_conf(ngx_conf_t *cf) conf->xss = NGX_CONF_UNSET_UINT; conf->fo = NGX_CONF_UNSET_UINT; + conf->rp = NGX_CONF_UNSET_UINT; conf->enable = NGX_CONF_UNSET; conf->hide_server_tokens = NGX_CONF_UNSET_UINT; @@ -275,6 +341,8 @@ ngx_http_security_headers_merge_loc_conf(ngx_conf_t *cf, void *parent, NGX_HTTP_XSS_HEADER_BLOCK); ngx_conf_merge_uint_value(conf->fo, prev->fo, NGX_HTTP_FO_HEADER_SAME); + ngx_conf_merge_uint_value(conf->rp, prev->rp, + NGX_HTTP_RP_HEADER_STRICT_ORIG_WHEN_CROSS); return NGX_CONF_OK; } diff --git a/t/headers.t b/t/headers.t index db06498..e3048a0 100644 --- a/t/headers.t +++ b/t/headers.t @@ -96,4 +96,48 @@ hello world !x-content-type-options x-frame-options: SAMEORIGIN !Server -Referrer-Policy: no-referrer-when-downgrade \ No newline at end of file +Referrer-Policy: strict-origin-when-cross-origin + + + +=== TEST 6: custom referrer-policy +--- config + security_headers on; + security_headers_referrer_policy unsafe-url; + location = /hello { + return 200 "hello world\n"; + } +--- request + GET /hello +--- response_body +hello world +--- response_headers +!x-content-type-options +x-frame-options: SAMEORIGIN +x-xss-protection: 1; mode=block +referrer-policy: unsafe-url + + + +=== TEST 7: co-exist with add header for custom referrer-policy +--- config + security_headers on; + security_headers_referrer_policy omit; + + location = /hello { + return 200 "hello world\n"; + add_header 'Referrer-Policy' 'origin'; + } + location = /hello-proxied { + proxy_buffering off; + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/hello; + } +--- request + GET /hello-proxied +--- response_body +hello world +--- response_headers +!x-content-type-options +x-frame-options: SAMEORIGIN +x-xss-protection: 1; mode=block +referrer-policy: origin \ No newline at end of file