Skip to content

Commit e1e335f

Browse files
zhuizhuhaomengagentzh
authored andcommitted
security: ngx.req.set_header(): now we always escape bytes in header names and header values which are prohibited by RFC 7230.
See https://tools.ietf.org/html/rfc7230#section-3.2.6 for more details. Signed-off-by: Yichun Zhang (agentzh) <[email protected]>
1 parent be35318 commit e1e335f

10 files changed

+219
-74
lines changed

README.markdown

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4552,9 +4552,9 @@ or a Lua table holding the query arguments' key-value pairs, as in
45524552
ngx.req.set_uri_args({ a = 3, b = "hello world" })
45534553
```
45544554

4555-
In the former case, i.e., when the whole query-strng is provided directly,
4555+
In the former case, i.e., when the whole query-string is provided directly,
45564556
the input Lua string should already be well-formed with the URI encoding.
4557-
For security considerations, his method will autoamticaly escape any control and
4557+
For security considerations, this method will automatically escape any control and
45584558
whitespace characters (ASCII code 0x00 ~ 0x32 and 0x7F) in the Lua string.
45594559

45604560
In the latter case, this method will escape argument keys and values according to the URI escaping rule.
@@ -4883,6 +4883,11 @@ ngx.req.set_header
48834883

48844884
Set the current request's request header named `header_name` to value `header_value`, overriding any existing ones.
48854885

4886+
The input Lua string `header_name` and `header_value` should already be well-formed with the URI encoding.
4887+
For security considerations, this method will automatically escape " ", """, "(", ")", ",", "/", ":", ";", "?",
4888+
"<", "=", ">", "?", "@", "[", "]", "\", "{", "}", 0x00-0x1F, 0x7F-0xFF in `header_name` and automatically escape
4889+
"0x00-0x08, 0x0A-0x0F, 0x7F in `header_value`.
4890+
48864891
By default, all the subrequests subsequently initiated by [ngx.location.capture](#ngxlocationcapture) and [ngx.location.capture_multi](#ngxlocationcapture_multi) will inherit the new header.
48874892

48884893
Here is an example of setting the `Content-Type` header:

doc/HttpLuaModule.wiki

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4084,6 +4084,11 @@ The <code>__index</code> metamethod will not be added when the <code>raw</code>
40844084
40854085
Set the current request's request header named <code>header_name</code> to value <code>header_value</code>, overriding any existing ones.
40864086
4087+
The input Lua string `header_name` and `header_value` should already be well-formed with the URI encoding.
4088+
For security considerations, this method will automatically escape " ", """, "(", ")", ",", "/", ":", ";", "?",
4089+
"<", "=", ">", "?", "@", "[", "]", "\", "{", "}", 0x00-0x1F, 0x7F-0xFF in `header_name` and automatically escape
4090+
"0x00-0x08, 0x0A-0x0F, 0x7F in `header_value`.
4091+
40874092
By default, all the subrequests subsequently initiated by [[#ngx.location.capture|ngx.location.capture]] and [[#ngx.location.capture_multi|ngx.location.capture_multi]] will inherit the new header.
40884093
40894094
Here is an example of setting the <code>Content-Type</code> header:

src/ngx_http_lua_headers_in.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -653,16 +653,18 @@ ngx_http_lua_set_input_header(ngx_http_request_t *r, ngx_str_t key,
653653
{
654654
ngx_http_lua_header_val_t hv;
655655
ngx_http_lua_set_header_t *handlers = ngx_http_lua_set_handlers;
656-
656+
ngx_int_t rc;
657657
ngx_uint_t i;
658658

659659
dd("set header value: %.*s", (int) value.len, value.data);
660660

661-
if (ngx_http_lua_check_unsafe_string(r, key.data, key.len,
662-
"header name") != NGX_OK
663-
|| ngx_http_lua_check_unsafe_string(r, value.data, value.len,
664-
"header value") != NGX_OK)
665-
{
661+
rc = ngx_http_lua_copy_escaped_header(r, &key, 1);
662+
if (rc != NGX_OK) {
663+
return NGX_ERROR;
664+
}
665+
666+
rc = ngx_http_lua_copy_escaped_header(r, &value, 0);
667+
if (rc != NGX_OK) {
666668
return NGX_ERROR;
667669
}
668670

src/ngx_http_lua_headers_out.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -491,11 +491,11 @@ ngx_http_lua_set_output_header(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx,
491491

492492
dd("set header value: %.*s", (int) value.len, value.data);
493493

494-
if (ngx_http_lua_check_unsafe_string(r, key.data, key.len,
495-
"header name") != NGX_OK
496-
|| ngx_http_lua_check_unsafe_string(r, value.data, value.len,
497-
"header value") != NGX_OK)
498-
{
494+
if (ngx_http_lua_copy_escaped_header(r, &key, 1) != NGX_OK) {
495+
return NGX_ERROR;
496+
}
497+
498+
if (ngx_http_lua_copy_escaped_header(r, &value, 0) != NGX_OK) {
499499
return NGX_ERROR;
500500
}
501501

src/ngx_http_lua_util.c

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2008,8 +2008,52 @@ ngx_http_lua_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type)
20082008

20092009
/* mail_auth is the same as memcached */
20102010

2011+
/* " ", """, "(", ")", ",", "/", ":", ";", "?",
2012+
* "<", "=", ">", "?", "@", "[", "]", "\", "{",
2013+
* "}", %00-%1F, %7F-%FF
2014+
*/
2015+
2016+
static uint32_t header_name[] = {
2017+
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
2018+
2019+
/* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
2020+
0xfc009305, /* 1111 1100 0000 0000 1001 0011 0000 0101 */
2021+
2022+
/* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
2023+
0x38000001, /* 0011 1000 0000 0000 0000 0000 0000 0001 */
2024+
2025+
/* ~}| {zyx wvut srqp onml kjih gfed cba` */
2026+
0xa8000000, /* 1010 1000 0000 0000 0000 0000 0000 0000 */
2027+
2028+
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
2029+
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
2030+
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
2031+
0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */
2032+
};
2033+
2034+
/* "%00-%08, %0A-%0F, %7F */
2035+
2036+
static uint32_t header_value[] = {
2037+
0xfffffdff, /* 1111 1111 1111 1111 1111 1101 1111 1111 */
2038+
2039+
/* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */
2040+
0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
2041+
2042+
/* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */
2043+
0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
2044+
2045+
/* ~}| {zyx wvut srqp onml kjih gfed cba` */
2046+
0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */
2047+
2048+
0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
2049+
0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
2050+
0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
2051+
0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */
2052+
};
2053+
20112054
static uint32_t *map[] =
2012-
{ uri, args, uri_component, html, refresh, memcached, memcached };
2055+
{ uri, args, uri_component, html, refresh, memcached, memcached,
2056+
header_name, header_value };
20132057

20142058
escape = map[type];
20152059

@@ -4328,4 +4372,38 @@ ngx_http_lua_escape_log(u_char *dst, u_char *src, size_t size)
43284372
}
43294373

43304374

4375+
ngx_int_t
4376+
ngx_http_lua_copy_escaped_header(ngx_http_request_t *r,
4377+
ngx_str_t *dst, int is_name)
4378+
{
4379+
size_t escape;
4380+
size_t len;
4381+
u_char *data;
4382+
int type;
4383+
4384+
type = is_name
4385+
? NGX_HTTP_LUA_ESCAPE_HEADER_NAME : NGX_HTTP_LUA_ESCAPE_HEADER_VALUE;
4386+
4387+
data = dst->data;
4388+
len = dst->len;
4389+
4390+
escape = ngx_http_lua_escape_uri(NULL, data, len, type);
4391+
if (escape > 0) {
4392+
/*
4393+
* we allocate space for the trailling '\0' char here because nginx
4394+
* header values must be null-terminated
4395+
*/
4396+
dst->data = ngx_palloc(r->pool, len + 2 * escape + 1);
4397+
if (dst->data == NULL) {
4398+
return NGX_ERROR;
4399+
}
4400+
4401+
ngx_http_lua_escape_uri(dst->data, data, len, type);
4402+
dst->len = len + 2 * escape;
4403+
dst->data[dst->len] = '\0';
4404+
}
4405+
4406+
return NGX_OK;
4407+
}
4408+
43314409
/* vi:set ft=c ts=4 sw=4 et fdm=marker: */

src/ngx_http_lua_util.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
# define NGX_HTTP_SWITCHING_PROTOCOLS 101
2828
#endif
2929

30+
#define NGX_HTTP_LUA_ESCAPE_HEADER_NAME 7
31+
32+
#define NGX_HTTP_LUA_ESCAPE_HEADER_VALUE 8
3033

3134
/* key in Lua vm registry for all the "ngx.ctx" tables */
3235
#define ngx_http_lua_ctx_tables_key "ngx_lua_ctx_tables"
@@ -169,6 +172,9 @@ void ngx_http_lua_unescape_uri(u_char **dst, u_char **src, size_t size,
169172
uintptr_t ngx_http_lua_escape_uri(u_char *dst, u_char *src,
170173
size_t size, ngx_uint_t type);
171174

175+
ngx_int_t ngx_http_lua_copy_escaped_header(ngx_http_request_t *r,
176+
ngx_str_t *dst, int is_name);
177+
172178
void ngx_http_lua_inject_req_api(ngx_log_t *log, lua_State *L);
173179

174180
void ngx_http_lua_process_args_option(ngx_http_request_t *r,

t/016-resp-header.t

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use Test::Nginx::Socket::Lua;
88

99
repeat_each(2);
1010

11-
plan tests => repeat_each() * (blocks() * 3 + 80);
11+
plan tests => repeat_each() * (blocks() * 3 + 77);
1212

1313
#no_diff();
1414
no_long_string();
@@ -51,7 +51,7 @@ Content-Type: text/html
5151
5252
5353
54-
=== TEST 3: set response content-type header
54+
=== TEST 3: set response content-length header
5555
--- config
5656
location /read {
5757
content_by_lua '
@@ -1976,14 +1976,12 @@ Content-Type: application/json
19761976
}
19771977
--- request
19781978
GET /t
1979-
--- error_code: 500
19801979
--- response_headers
1981-
header:
1980+
header: value%0Dfoo:bar%0Abar:foo
19821981
foo:
19831982
bar:
1984-
--- error_log
1985-
unsafe byte "0xd" in header value "value\x0Dfoo:bar\x0Abar:foo"
1986-
failed to set header
1983+
--- no_error_log
1984+
[error]
19871985
19881986
19891987
@@ -1997,14 +1995,12 @@ failed to set header
19971995
}
19981996
--- request
19991997
GET /t
2000-
--- error_code: 500
20011998
--- response_headers
2002-
header:
1999+
header: value%0Afoo:bar%0Dbar:foo
20032000
foo:
20042001
bar:
2005-
--- error_log
2006-
unsafe byte "0xa" in header value "value\x0Afoo:bar\x0Dbar:foo"
2007-
failed to set header
2002+
--- no_error_log
2003+
[error]
20082004
20092005
20102006
@@ -2018,14 +2014,13 @@ failed to set header
20182014
}
20192015
--- request
20202016
GET /t
2021-
--- error_code: 500
20222017
--- response_headers
2018+
header%3A%20value%0Dfoo%3Abar%0Abar%3Afoo: xx
20232019
header:
20242020
foo:
20252021
bar:
2026-
--- error_log
2027-
unsafe byte "0xd" in header name "header: value\x0Dfoo:bar\x0Abar:foo"
2028-
failed to set header
2022+
--- no_error_log
2023+
[error]
20292024
20302025
20312026
@@ -2039,14 +2034,13 @@ failed to set header
20392034
}
20402035
--- request
20412036
GET /t
2042-
--- error_code: 500
20432037
--- response_headers
2038+
header%3A%20value%0Afoo%3Abar%0Dbar%3Afoo: xx
20442039
header:
20452040
foo:
20462041
bar:
2047-
--- error_log
2048-
unsafe byte "0xa" in header name "header: value\x0Afoo:bar\x0Dbar:foo"
2049-
failed to set header
2042+
--- no_error_log
2043+
[error]
20502044
20512045
20522046
@@ -2060,14 +2054,13 @@ failed to set header
20602054
}
20612055
--- request
20622056
GET /t
2063-
--- error_code: 500
20642057
--- response_headers
2058+
%0Dheader%3A%20value%0Dfoo%3Abar%0Abar%3Afoo: xx
20652059
header:
20662060
foo:
20672061
bar:
2068-
--- error_log
2069-
unsafe byte "0xd" in header name "\x0Dheader: value\x0Dfoo:bar\x0Abar:foo"
2070-
failed to set header
2062+
--- no_error_log
2063+
[error]
20712064
20722065
20732066
@@ -2081,14 +2074,13 @@ failed to set header
20812074
}
20822075
--- request
20832076
GET /t
2084-
--- error_code: 500
20852077
--- response_headers
2078+
%0Aheader%3A%20value%0Afoo%3Abar%0Dbar%3Afoo: xx
20862079
header:
20872080
foo:
20882081
bar:
2089-
--- error_log
2090-
unsafe byte "0xa" in header name "\x0Aheader: value\x0Afoo:bar\x0Dbar:foo"
2091-
failed to set header
2082+
--- no_error_log
2083+
[error]
20922084
20932085
20942086
@@ -2105,11 +2097,10 @@ failed to set header
21052097
}
21062098
--- request
21072099
GET /t
2108-
--- error_code: 500
21092100
--- response_headers
2110-
foo:
21112101
xx:
21122102
xxx:
2113-
--- error_log
2114-
unsafe byte "0xa" in header value "foo\x0Axx:bar"
2115-
failed to set header
2103+
--- raw_response_headers_like chomp
2104+
foo: foo%0Axx:bar\r\nfoo: bar%0Dxxx:foo\r\n
2105+
--- no_error_log
2106+
[error]

0 commit comments

Comments
 (0)