]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] http: capture invalid requests/responses even if accepted
authorWilly Tarreau <w@1wt.eu>
Thu, 2 Apr 2009 13:18:36 +0000 (15:18 +0200)
committerWilly Tarreau <w@1wt.eu>
Thu, 2 Apr 2009 19:36:37 +0000 (21:36 +0200)
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
include/proto/proto_http.h
src/proto_http.c

index 68f946d00747d95c9e99708dd7985dd89f429313..32a2b7d4194a178a36df4d02240c1fe1adf8acec 100644 (file)
@@ -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
index 41f1922a94a6bdb2693713c011f8234254496398..175eb04794406306e4e25b0492bd056260f97a6f 100644 (file)
@@ -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 */
 
index a77ab79bda986caab67aadeef6d82caa205e3300..3325b31fc0349ae944f6d94eb2e6647db8c461d8 100644 (file)
@@ -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