diff --git a/src/nxt_http_set_headers.c b/src/nxt_http_set_headers.c index 7fd6aba52..1ca6df2ff 100644 --- a/src/nxt_http_set_headers.c +++ b/src/nxt_http_set_headers.c @@ -68,6 +68,29 @@ nxt_http_set_headers_init(nxt_router_conf_t *rtcf, nxt_http_action_t *action, } +/* + * Reject values that would inject a header boundary into the response. + * Templated values (e.g. $uri, $arg_*) can carry CR/LF/NUL bytes if the + * client encodes them in the request, and writing those bytes verbatim + * into the wire serialiser yields HTTP response splitting. Static + * config values are operator-controlled and trusted, but the check is + * cheap enough to apply to both paths. + */ +static nxt_bool_t +nxt_http_header_value_is_safe(const nxt_str_t *v) +{ + size_t i; + + for (i = 0; i < v->length; i++) { + if (v->start[i] == '\r' || v->start[i] == '\n' || v->start[i] == '\0') { + return 0; + } + } + + return 1; +} + + static nxt_http_field_t * nxt_http_resp_header_find(nxt_http_request_t *r, u_char *name, size_t length) { @@ -144,6 +167,17 @@ nxt_http_set_headers(nxt_http_request_t *r) return NXT_ERROR; } } + + if (value[i].start != NULL + && nxt_slow_path(!nxt_http_header_value_is_safe(&value[i]))) + { + nxt_log(&r->task, NXT_LOG_INFO, + "set_headers \"%V\": dropping value containing CR, LF " + "or NUL (HTTP response-splitting protection)", + &hv->name); + value[i].start = NULL; + value[i].length = 0; + } } for (i = 0; i < n; i++) {