mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-06 15:17:01 +02:00
[MEDIUM] http: capture invalid requests/responses even if accepted
It's useful to be able to accept an invalid header name in a request or response but still be able to monitor further such errors. Now, when an invalid request/response is received and accepted due to an "accept-invalid-http-{request|response}" option, the invalid request will be captured for later analysis with "show errors" on the stats socket.
This commit is contained in:
parent
32a4ec0ed7
commit
4076a15255
@ -592,6 +592,10 @@ monitor fail - X X -
|
||||
monitor-net X X X -
|
||||
monitor-uri X X X -
|
||||
[no] option abortonclose X - X X
|
||||
[no] option accept-invalid-
|
||||
http-request X X X -
|
||||
[no] option accept-invalid-
|
||||
http-response X - X X
|
||||
[no] option allbackups X - X X
|
||||
[no] option checkcache X - X X
|
||||
[no] option clitcpka X X X -
|
||||
@ -1806,6 +1810,72 @@ no option abortonclose
|
||||
See also : "timeout queue" and server's "maxconn" and "maxqueue" parameters
|
||||
|
||||
|
||||
option accept-invalid-http-request
|
||||
no option accept-invalid-http-request
|
||||
Enable or disable relaxing of HTTP request parsing
|
||||
May be used in sections : defaults | frontend | listen | backend
|
||||
yes | yes | yes | no
|
||||
Arguments : none
|
||||
|
||||
By default, HAProxy complies with RFC2616 in terms of message parsing. This
|
||||
means that invalid characters in header names are not permitted and cause an
|
||||
error to be returned to the client. This is the desired behaviour as such
|
||||
forbidden characters are essentially used to build attacks exploiting server
|
||||
weaknesses, and bypass security filtering. Sometimes, a buggy browser or
|
||||
server will emit invalid header names for whatever reason (configuration,
|
||||
implementation) and the issue will not be immediately fixed. In such a case,
|
||||
it is possible to relax HAProxy's header name parser to accept any character
|
||||
even if that does not make sense, by specifying this option.
|
||||
|
||||
This option should never be enabled by default as it hides application bugs
|
||||
and open security breaches. It should only be deployed after a problem has
|
||||
been confirmed.
|
||||
|
||||
When this option is enabled, erroneous header names will still be accepted in
|
||||
requests, but the complete request will be captured in order to permit later
|
||||
analysis using the "show errors" request on the UNIX stats socket. Doing this
|
||||
also helps confirming that the issue has been solved.
|
||||
|
||||
If this option has been enabled in a "defaults" section, it can be disabled
|
||||
in a specific instance by prepending the "no" keyword before it.
|
||||
|
||||
See also : "option accept-invalid-http-response" and "show errors" on the
|
||||
stats socket.
|
||||
|
||||
|
||||
option accept-invalid-http-response
|
||||
no option accept-invalid-http-response
|
||||
Enable or disable relaxing of HTTP response parsing
|
||||
May be used in sections : defaults | frontend | listen | backend
|
||||
yes | no | yes | yes
|
||||
Arguments : none
|
||||
|
||||
By default, HAProxy complies with RFC2616 in terms of message parsing. This
|
||||
means that invalid characters in header names are not permitted and cause an
|
||||
error to be returned to the client. This is the desired behaviour as such
|
||||
forbidden characters are essentially used to build attacks exploiting server
|
||||
weaknesses, and bypass security filtering. Sometimes, a buggy browser or
|
||||
server will emit invalid header names for whatever reason (configuration,
|
||||
implementation) and the issue will not be immediately fixed. In such a case,
|
||||
it is possible to relax HAProxy's header name parser to accept any character
|
||||
even if that does not make sense, by specifying this option.
|
||||
|
||||
This option should never be enabled by default as it hides application bugs
|
||||
and open security breaches. It should only be deployed after a problem has
|
||||
been confirmed.
|
||||
|
||||
When this option is enabled, erroneous header names will still be accepted in
|
||||
responses, but the complete response will be captured in order to permit
|
||||
later analysis using the "show errors" request on the UNIX stats socket.
|
||||
Doing this also helps confirming that the issue has been solved.
|
||||
|
||||
If this option has been enabled in a "defaults" section, it can be disabled
|
||||
in a specific instance by prepending the "no" keyword before it.
|
||||
|
||||
See also : "option accept-invalid-http-request" and "show errors" on the
|
||||
stats socket.
|
||||
|
||||
|
||||
option allbackups
|
||||
no option allbackups
|
||||
Use either all backup servers at a time or only the first one
|
||||
|
@ -86,6 +86,9 @@ int http_find_header2(const char *name, int len,
|
||||
void http_sess_log(struct session *s);
|
||||
void perform_http_redirect(struct session *s, struct stream_interface *si);
|
||||
void http_return_srv_error(struct session *s, struct stream_interface *si);
|
||||
void http_capture_bad_message(struct error_snapshot *es, struct session *s,
|
||||
struct buffer *buf, struct http_msg *msg,
|
||||
struct proxy *other_end);
|
||||
|
||||
#endif /* _PROTO_PROTO_HTTP_H */
|
||||
|
||||
|
@ -1614,6 +1614,8 @@ int http_process_request(struct session *s, struct buffer *req)
|
||||
/* 2: have we encountered a read error ? */
|
||||
else if (req->flags & BF_READ_ERROR) {
|
||||
/* we cannot return any message on error */
|
||||
if (msg->err_pos >= 0)
|
||||
http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
|
||||
msg->msg_state = HTTP_MSG_ERROR;
|
||||
req->analysers = 0;
|
||||
s->fe->failed_req++;
|
||||
@ -1627,6 +1629,8 @@ int http_process_request(struct session *s, struct buffer *req)
|
||||
/* 3: has the read timeout expired ? */
|
||||
else if (req->flags & BF_READ_TIMEOUT || tick_is_expired(req->analyse_exp, now_ms)) {
|
||||
/* read timeout : give up with an error message. */
|
||||
if (msg->err_pos >= 0)
|
||||
http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
|
||||
txn->status = 408;
|
||||
stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_408));
|
||||
msg->msg_state = HTTP_MSG_ERROR;
|
||||
@ -1641,6 +1645,8 @@ int http_process_request(struct session *s, struct buffer *req)
|
||||
|
||||
/* 4: have we encountered a close ? */
|
||||
else if (req->flags & BF_SHUTR) {
|
||||
if (msg->err_pos >= 0)
|
||||
http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
|
||||
txn->status = 400;
|
||||
stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_400));
|
||||
msg->msg_state = HTTP_MSG_ERROR;
|
||||
@ -1672,6 +1678,9 @@ int http_process_request(struct session *s, struct buffer *req)
|
||||
* of each header's length, so we can parse them quickly. *
|
||||
****************************************************************/
|
||||
|
||||
if (msg->err_pos >= 0)
|
||||
http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
|
||||
|
||||
req->analysers &= ~AN_REQ_HTTP_HDR;
|
||||
req->analyse_exp = TICK_ETERNITY;
|
||||
|
||||
@ -2331,21 +2340,13 @@ int http_process_request(struct session *s, struct buffer *req)
|
||||
return 1;
|
||||
|
||||
return_bad_req: /* let's centralize all bad requests */
|
||||
if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) {
|
||||
if (unlikely(msg->msg_state == HTTP_MSG_ERROR) || msg->err_pos >= 0) {
|
||||
/* we detected a parsing error. We want to archive this request
|
||||
* in the dedicated proxy area for later troubleshooting.
|
||||
*/
|
||||
struct error_snapshot *es = &s->fe->invalid_req;
|
||||
int maxlen = MIN(req->r - req->data + msg->som, sizeof(es->buf));
|
||||
memcpy(es->buf, req->data + msg->som, maxlen);
|
||||
es->pos = req->lr - req->data + msg->som;
|
||||
es->len = req->r - req->data + msg->som;
|
||||
es->when = date; // user-visible date
|
||||
es->sid = s->uniq_id;
|
||||
es->srv = s->srv;
|
||||
es->oe = s->be;
|
||||
es->src = s->cli_addr;
|
||||
http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
|
||||
}
|
||||
|
||||
txn->req.msg_state = HTTP_MSG_ERROR;
|
||||
txn->status = 400;
|
||||
req->analysers = 0;
|
||||
@ -2570,18 +2571,10 @@ int process_response(struct session *t)
|
||||
/* we detected a parsing error. We want to archive this response
|
||||
* in the dedicated proxy area for later troubleshooting.
|
||||
*/
|
||||
struct error_snapshot *es = &t->be->invalid_rep;
|
||||
int maxlen = MIN(rep->r - rep->data + msg->som, sizeof(es->buf));
|
||||
memcpy(es->buf, rep->data + msg->som, maxlen);
|
||||
es->pos = rep->lr - rep->data + msg->som;
|
||||
es->len = rep->r - rep->data + msg->som;
|
||||
es->when = date; // user-visible date
|
||||
es->sid = t->uniq_id;
|
||||
es->srv = t->srv;
|
||||
es->oe = t->fe;
|
||||
es->src = t->cli_addr;
|
||||
|
||||
hdr_response_bad:
|
||||
if (msg->msg_state == HTTP_MSG_ERROR || msg->err_pos >= 0)
|
||||
http_capture_bad_message(&t->be->invalid_rep, t, rep, msg, t->fe);
|
||||
|
||||
buffer_shutr_now(rep);
|
||||
buffer_shutw_now(req);
|
||||
if (t->srv)
|
||||
@ -2603,6 +2596,8 @@ int process_response(struct session *t)
|
||||
}
|
||||
/* read error */
|
||||
else if (rep->flags & BF_READ_ERROR) {
|
||||
if (msg->err_pos >= 0)
|
||||
http_capture_bad_message(&t->be->invalid_rep, t, rep, msg, t->fe);
|
||||
buffer_shutr_now(rep);
|
||||
buffer_shutw_now(req);
|
||||
if (t->srv)
|
||||
@ -2619,6 +2614,8 @@ int process_response(struct session *t)
|
||||
}
|
||||
/* read timeout : return a 504 to the client. */
|
||||
else if (rep->flags & BF_READ_TIMEOUT) {
|
||||
if (msg->err_pos >= 0)
|
||||
http_capture_bad_message(&t->be->invalid_rep, t, rep, msg, t->fe);
|
||||
buffer_shutr_now(rep);
|
||||
buffer_shutw_now(req);
|
||||
if (t->srv)
|
||||
@ -2635,6 +2632,8 @@ int process_response(struct session *t)
|
||||
}
|
||||
/* close from server */
|
||||
else if (rep->flags & BF_SHUTR) {
|
||||
if (msg->err_pos >= 0)
|
||||
http_capture_bad_message(&t->be->invalid_rep, t, rep, msg, t->fe);
|
||||
buffer_shutw_now(req);
|
||||
if (t->srv)
|
||||
t->srv->failed_resp++;
|
||||
@ -2650,6 +2649,8 @@ int process_response(struct session *t)
|
||||
}
|
||||
/* write error to client (we don't send any message then) */
|
||||
else if (rep->flags & BF_WRITE_ERROR) {
|
||||
if (msg->err_pos >= 0)
|
||||
http_capture_bad_message(&t->be->invalid_rep, t, rep, msg, t->fe);
|
||||
buffer_shutr_now(rep);
|
||||
t->be->failed_resp++;
|
||||
rep->analysers = 0;
|
||||
@ -2670,6 +2671,9 @@ int process_response(struct session *t)
|
||||
* of each header's length, so we can parse them quickly. *
|
||||
****************************************************************/
|
||||
|
||||
if (msg->err_pos >= 0)
|
||||
http_capture_bad_message(&t->be->invalid_rep, t, rep, msg, t->fe);
|
||||
|
||||
rep->analysers &= ~AN_RTR_HTTP_HDR;
|
||||
|
||||
/* ensure we keep this pointer to the beginning of the message */
|
||||
@ -4381,6 +4385,27 @@ int stats_check_uri_auth(struct session *t, struct proxy *backend)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Capture a bad request or response and archive it in the proxy's structure.
|
||||
*/
|
||||
void http_capture_bad_message(struct error_snapshot *es, struct session *s,
|
||||
struct buffer *buf, struct http_msg *msg,
|
||||
struct proxy *other_end)
|
||||
{
|
||||
int maxlen = MIN(buf->r - buf->data + msg->som, sizeof(es->buf));
|
||||
|
||||
memcpy(es->buf, buf->data + msg->som, maxlen);
|
||||
if (msg->err_pos >= 0)
|
||||
es->pos = msg->err_pos + msg->som;
|
||||
else
|
||||
es->pos = buf->lr - buf->data + msg->som;
|
||||
es->len = buf->r - buf->data + msg->som;
|
||||
es->when = date; // user-visible date
|
||||
es->sid = s->uniq_id;
|
||||
es->srv = s->srv;
|
||||
es->oe = other_end;
|
||||
es->src = s->cli_addr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Print a debug line with a header
|
||||
|
Loading…
Reference in New Issue
Block a user