From 4076a152559f4812726d8be45b82e443e505dbdd Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Thu, 2 Apr 2009 15:18:36 +0200 Subject: [PATCH] [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. --- doc/configuration.txt | 70 ++++++++++++++++++++++++++++++++++++++ include/proto/proto_http.h | 3 ++ src/proto_http.c | 69 +++++++++++++++++++++++++------------ 3 files changed, 120 insertions(+), 22 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 68f946d007..32a2b7d419 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index 41f1922a94..175eb04794 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -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 */ diff --git a/src/proto_http.c b/src/proto_http.c index a77ab79bda..3325b31fc0 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -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 -- 2.47.2