Skip to content

Commit de3a71d

Browse files
committed
Overwrite existing headers
1 parent 77c8f78 commit de3a71d

File tree

4 files changed

+153
-73
lines changed

4 files changed

+153
-73
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ compiler:
44
- gcc
55
env:
66
matrix:
7-
- NGINX_VERSION=1.16.0
8-
- NGINX_VERSION=1.17.2 -
7+
- NGINX_VERSION=1.16.1
8+
- NGINX_VERSION=1.17.3 -
99
addons:
1010
apt:
1111
packages:

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ It's easy to install the module in your stable nginx instance dynamically:
108108
Then add it at the top of your `nginx.conf`:
109109

110110
load_module modules/ngx_http_security_headers_module.so;
111+
112+
In case you use ModSecurity NGINX module, make sure it's loaded last, like so:
113+
114+
load_module modules/ngx_http_security_headers_module.so;
115+
load_module modules/ngx_http_modsecurity_module.so;
111116

112117
### Other platforms
113118

src/ngx_http_security_headers_module.c

Lines changed: 102 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ static void *ngx_http_security_headers_create_loc_conf(ngx_conf_t *cf);
4848
static char *ngx_http_security_headers_merge_loc_conf(ngx_conf_t *cf,
4949
void *parent, void *child);
5050
static ngx_int_t ngx_http_security_headers_init(ngx_conf_t *cf);
51+
static ngx_int_t ngx_set_headers_out_by_search(ngx_http_request_t *r,
52+
ngx_str_t *key, ngx_str_t *value);
5153

5254
ngx_str_t ngx_http_security_headers_default_nosniff_types[] = {
5355
ngx_string("text/css"),
@@ -125,17 +127,16 @@ ngx_module_t ngx_http_security_headers_module = {
125127

126128
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
127129

128-
129130
/* header filter handler */
130131

131132
static ngx_int_t
132133
ngx_http_security_headers_filter(ngx_http_request_t *r)
133134
{
134-
ngx_table_elt_t *h_x_cto;
135-
ngx_table_elt_t *h_x_xss;
136-
ngx_table_elt_t *h_x_fo;
137135
ngx_http_security_headers_loc_conf_t *slcf;
138-
ngx_uint_t i;
136+
137+
ngx_str_t key;
138+
ngx_str_t val;
139+
139140

140141
slcf = ngx_http_get_module_loc_conf(r, ngx_http_security_headers_module);
141142

@@ -147,54 +148,46 @@ ngx_http_security_headers_filter(ngx_http_request_t *r)
147148
if (r->headers_out.status == NGX_HTTP_OK
148149
&& ngx_http_test_content_type(r, &slcf->nosniff_types) != NULL)
149150
{
150-
h_x_cto = ngx_list_push(&r->headers_out.headers);
151-
if (h_x_cto == NULL) {
152-
return NGX_ERROR;
153-
}
151+
ngx_str_set(&key, "X-Content-Type-Options");
152+
ngx_str_set(&val, "nosniff");
154153

155-
h_x_cto->hash = 1;
156-
ngx_str_set(&h_x_cto->key, "X-Content-Type-Options");
157-
ngx_str_set(&h_x_cto->value, "nosniff");
154+
ngx_set_headers_out_by_search(r, &key, &val);
158155
}
159156

160157
/* Add X-XSS-Protection */
161158
if (r->headers_out.status != NGX_HTTP_NOT_MODIFIED
162159
&& NGX_HTTP_SECURITY_HEADER_OMIT != slcf->xss)
163160
{
164-
h_x_xss = ngx_list_push(&r->headers_out.headers);
165-
if (h_x_xss == NULL) {
166-
return NGX_ERROR;
167-
}
168-
169-
h_x_xss->hash = 1;
170-
ngx_str_set(&h_x_xss->key, "X-XSS-Protection");
161+
ngx_str_set(&key, "X-XSS-Protection");
171162
if (NGX_HTTP_XSS_HEADER_ON == slcf->xss) {
172-
ngx_str_set(&h_x_xss->value, "1");
163+
ngx_str_set(&val, "1");
173164
} else if (NGX_HTTP_XSS_HEADER_BLOCK == slcf->xss) {
174-
ngx_str_set(&h_x_xss->value, "1; mode=block");
165+
ngx_str_set(&val, "1; mode=block");
175166
} else if (NGX_HTTP_XSS_HEADER_OFF == slcf->xss) {
176-
ngx_str_set(&h_x_xss->value, "0");
167+
ngx_str_set(&val, "0");
177168
}
169+
ngx_set_headers_out_by_search(r, &key, &val);
178170
}
179171

180-
/* Add X-Frame-Options */
172+
/* Add X-Frame-Options */
181173
if (r->headers_out.status != NGX_HTTP_NOT_MODIFIED
182174
&& NGX_HTTP_SECURITY_HEADER_OMIT != slcf->fo)
183175
{
184-
h_x_fo = ngx_list_push(&r->headers_out.headers);
185-
if (h_x_fo == NULL) {
186-
return NGX_ERROR;
187-
}
188-
189-
h_x_fo->hash = 1;
190-
ngx_str_set(&h_x_fo->key, "X-Frame-Options");
176+
ngx_str_set(&key, "X-Frame-Options");
191177
if (NGX_HTTP_FO_HEADER_SAME == slcf->fo) {
192-
ngx_str_set(&h_x_fo->value, "SAMEORIGIN");
178+
ngx_str_set(&val, "SAMEORIGIN");
193179
} else if (NGX_HTTP_FO_HEADER_DENY == slcf->fo) {
194-
ngx_str_set(&h_x_fo->value, "DENY");
180+
ngx_str_set(&val, "DENY");
195181
}
182+
ngx_set_headers_out_by_search(r, &key, &val);
196183
}
197184

185+
/* Find X-Powered-By header */
186+
ngx_str_set(&key, "x-powered-by");
187+
ngx_str_set(&val, "");
188+
ngx_set_headers_out_by_search(r, &key, &val);
189+
190+
198191
/* Deal with Server header */
199192
ngx_table_elt_t *h_server;
200193
h_server = r->headers_out.server;
@@ -203,43 +196,11 @@ ngx_http_security_headers_filter(ngx_http_request_t *r)
203196
if (h_server == NULL) {
204197
return NGX_ERROR;
205198
}
206-
/*
207-
* h->key.data = (u_char *) "Server";
208-
* h->key.len = sizeof("Server") - 1;
209-
* h->value.data = (u_char *) "";
210-
* h->value.len = sizeof("") - 1;
211-
*/
212-
199+
h_server->value.len = 0;
200+
h_server->value.data = (u_char *) "";
201+
h_server->hash = 0;
213202
r->headers_out.server = h_server;
214203
}
215-
h_server->hash = 0;
216-
217-
/* Find X-Powered-By header */
218-
ngx_list_part_t *part = NULL;
219-
ngx_table_elt_t *header = NULL;
220-
221-
part = &r->headers_out.headers.part;
222-
header = part->elts;
223-
for ( i = 0 ; ; i++ ) {
224-
if (i >= part->nelts) {
225-
if (part->next == NULL) {
226-
break;
227-
}
228-
229-
part = part->next;
230-
header = part->elts;
231-
i = 0;
232-
}
233-
if (header[i].hash == 0) {
234-
continue;
235-
}
236-
if (ngx_strcasecmp(header[i].key.data,
237-
(u_char *)"x-powered-by") == 0)
238-
{
239-
header[i].hash = 0;
240-
break;
241-
}
242-
}
243204

244205
/* proceed to the next handler in chain */
245206

@@ -259,7 +220,7 @@ ngx_http_security_headers_create_loc_conf(ngx_conf_t *cf)
259220

260221
conf->xss = NGX_CONF_UNSET_UINT;
261222
conf->fo = NGX_CONF_UNSET_UINT;
262-
conf->enable = NGX_CONF_UNSET_UINT;
223+
conf->enable = NGX_CONF_UNSET;
263224

264225
return conf;
265226
}
@@ -300,4 +261,76 @@ ngx_http_security_headers_init(ngx_conf_t *cf)
300261
ngx_http_top_header_filter = ngx_http_security_headers_filter;
301262

302263
return NGX_OK;
303-
}
264+
}
265+
266+
static ngx_int_t
267+
ngx_set_headers_out_by_search(ngx_http_request_t *r,
268+
ngx_str_t *key, ngx_str_t *value)
269+
{
270+
ngx_list_part_t *part;
271+
ngx_table_elt_t *hi;
272+
ngx_uint_t i;
273+
ngx_table_elt_t *h = NULL;
274+
275+
/*
276+
Get the first part of the list. There is usual only one part.
277+
*/
278+
part = &r->headers_out.headers.part;
279+
hi = part->elts;
280+
281+
/*
282+
Headers list array may consist of more than one part,
283+
so loop through all of it
284+
*/
285+
for (i = 0; /* void */ ; i++) {
286+
if (i >= part->nelts) {
287+
if (part->next == NULL) {
288+
/* The last part, search is done. */
289+
break;
290+
}
291+
292+
part = part->next;
293+
hi = part->elts;
294+
i = 0;
295+
}
296+
297+
if (hi[i].hash == 0) {
298+
continue;
299+
}
300+
301+
/*
302+
Just compare the lengths and then the names case insensitively.
303+
*/
304+
if (key->len != hi[i].key.len
305+
|| ngx_strcasecmp(key->data, hi[i].key.data) != 0)
306+
{
307+
/* This header doesn't match. */
308+
continue;
309+
}
310+
311+
h = hi;
312+
break;
313+
}
314+
if (h == NULL) {
315+
h = ngx_list_push(&r->headers_out.headers);
316+
}
317+
if (h == NULL) {
318+
return NGX_ERROR;
319+
}
320+
h->key = *key;
321+
h->value = *value;
322+
if (value->len == 0) {
323+
h->hash = 0;
324+
} else {
325+
h->hash = 1;
326+
}
327+
328+
h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
329+
if (h->lowcase_key == NULL) {
330+
return NGX_ERROR;
331+
}
332+
333+
ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
334+
335+
return NGX_OK;
336+
}

t/headers.t

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ hello world
1818
!Server
1919
2020
21-
=== TEST 2: x-xss-protection added
21+
22+
=== TEST 2: no nosniff for html
2223
--- config
2324
security_headers on;
2425
location = /hello {
@@ -29,4 +30,45 @@ hello world
2930
--- response_body
3031
hello world
3132
--- response_headers
32-
X-XSS-Protection: 1; mode=block
33+
!x-content-type-options
34+
x-frame-options: SAMEORIGIN
35+
x-xss-protection: 1; mode=block
36+
37+
38+
39+
=== TEST 3: nosniff for css
40+
--- config
41+
security_headers on;
42+
location = /hello.css {
43+
default_type text/css;
44+
return 200 "hello world\n";
45+
}
46+
--- request
47+
GET /hello.css
48+
--- response_body
49+
hello world
50+
--- response_headers
51+
content-type: text/css
52+
x-content-type-options: nosniff
53+
54+
55+
56+
=== TEST 4: proxied ok
57+
--- config
58+
location = /hello {
59+
add_header x-frame-options SAMEORIGIN1;
60+
return 200 "hello world\n";
61+
}
62+
location = /hello-proxied {
63+
security_headers on;
64+
proxy_buffering off;
65+
proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/hello;
66+
}
67+
--- request
68+
GET /hello-proxied
69+
--- response_body
70+
hello world
71+
--- response_headers
72+
!x-content-type-options
73+
x-frame-options: SAMEORIGIN
74+
!server

0 commit comments

Comments
 (0)