You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

143 lines
5.3 KiB

  1. From 4a600dabd5e2799bf0c3048859ee4f00808b7d89 Mon Sep 17 00:00:00 2001
  2. From: Glenn Strauss <gstrauss@gluelogic.com>
  3. Date: Sat, 6 Feb 2021 08:29:41 -0500
  4. Subject: [PATCH] [mod_auth] close HTTP/2 connection after bad pass
  5. mitigation slows down brute force password attacks
  6. x-ref:
  7. "Possible feature: authentication brute force hardening"
  8. https://redmine.lighttpd.net/boards/3/topics/8885
  9. Signed-off-by: Glenn Strauss <gstrauss@gluelogic.com>
  10. ---
  11. src/connections.c | 22 +++++++++++++++++++++-
  12. src/mod_accesslog.c | 2 +-
  13. src/mod_auth.c | 6 +++---
  14. src/reqpool.c | 1 +
  15. src/request.h | 2 +-
  16. src/response.c | 4 ++--
  17. 6 files changed, 29 insertions(+), 8 deletions(-)
  18. --- a/src/connections.c
  19. +++ b/src/connections.c
  20. @@ -228,7 +228,7 @@ static void connection_handle_response_e
  21. }
  22. }
  23. - if (r->keep_alive) {
  24. + if (r->keep_alive > 0) {
  25. request_reset(r);
  26. config_reset_config(r);
  27. con->is_readable = 1; /* potentially trigger optimistic read */
  28. @@ -1265,6 +1265,19 @@ connection_set_fdevent_interest (request
  29. }
  30. +__attribute_cold__
  31. +static void
  32. +connection_request_end_h2 (request_st * const h2r, connection * const con)
  33. +{
  34. + if (h2r->keep_alive >= 0) {
  35. + h2r->keep_alive = -1;
  36. + h2_send_goaway(con, H2_E_NO_ERROR);
  37. + }
  38. + else /*(abort connection upon second request to close h2 connection)*/
  39. + h2_send_goaway(con, H2_E_ENHANCE_YOUR_CALM);
  40. +}
  41. +
  42. +
  43. static void
  44. connection_state_machine_h2 (request_st * const h2r, connection * const con)
  45. {
  46. @@ -1359,8 +1372,15 @@ connection_state_machine_h2 (request_st
  47. && !chunkqueue_is_empty(con->read_queue))
  48. resched |= 1;
  49. h2_send_end_stream(r, con);
  50. + const int alive = r->keep_alive;
  51. h2_retire_stream(r, con);/*r invalidated;removed from h2c->r[]*/
  52. --i;/* adjust loop i; h2c->rused was modified to retire r */
  53. + /*(special-case: allow *stream* to set r->keep_alive = -1 to
  54. + * trigger goaway on h2 connection, e.g. after mod_auth failure
  55. + * in attempt to mitigate brute force attacks by forcing a
  56. + * reconnect and (somewhat) slowing down retries)*/
  57. + if (alive < 0)
  58. + connection_request_end_h2(h2r, con);
  59. }
  60. }
  61. }
  62. --- a/src/mod_accesslog.c
  63. +++ b/src/mod_accesslog.c
  64. @@ -1108,7 +1108,7 @@ static int log_access_record (const requ
  65. break;
  66. case FORMAT_CONNECTION_STATUS:
  67. if (r->state == CON_STATE_RESPONSE_END) {
  68. - if (0 == r->keep_alive) {
  69. + if (r->keep_alive <= 0) {
  70. buffer_append_string_len(b, CONST_STR_LEN("-"));
  71. } else {
  72. buffer_append_string_len(b, CONST_STR_LEN("+"));
  73. --- a/src/mod_auth.c
  74. +++ b/src/mod_auth.c
  75. @@ -828,7 +828,7 @@ static handler_t mod_auth_check_basic(re
  76. log_error(r->conf.errh, __FILE__, __LINE__,
  77. "password doesn't match for %s username: %s IP: %s",
  78. r->uri.path.ptr, username->ptr, r->con->dst_addr_buf->ptr);
  79. - r->keep_alive = 0; /*(disable keep-alive if bad password)*/
  80. + r->keep_alive = -1; /*(disable keep-alive if bad password)*/
  81. rc = HANDLER_UNSET;
  82. break;
  83. }
  84. @@ -1461,7 +1461,7 @@ static handler_t mod_auth_check_digest(r
  85. return HANDLER_FINISHED;
  86. case HANDLER_ERROR:
  87. default:
  88. - r->keep_alive = 0; /*(disable keep-alive if unknown user)*/
  89. + r->keep_alive = -1; /*(disable keep-alive if unknown user)*/
  90. buffer_free(b);
  91. return mod_auth_send_401_unauthorized_digest(r, require, 0);
  92. }
  93. @@ -1482,7 +1482,7 @@ static handler_t mod_auth_check_digest(r
  94. log_error(r->conf.errh, __FILE__, __LINE__,
  95. "digest: auth failed for %s: wrong password, IP: %s",
  96. username, r->con->dst_addr_buf->ptr);
  97. - r->keep_alive = 0; /*(disable keep-alive if bad password)*/
  98. + r->keep_alive = -1; /*(disable keep-alive if bad password)*/
  99. buffer_free(b);
  100. return mod_auth_send_401_unauthorized_digest(r, require, 0);
  101. --- a/src/reqpool.c
  102. +++ b/src/reqpool.c
  103. @@ -58,6 +58,7 @@ request_reset (request_st * const r)
  104. http_response_reset(r);
  105. r->loops_per_request = 0;
  106. + r->keep_alive = 0;
  107. r->h2state = 0; /* H2_STATE_IDLE */
  108. r->h2id = 0;
  109. --- a/src/request.h
  110. +++ b/src/request.h
  111. @@ -175,7 +175,7 @@ struct request_st {
  112. char resp_header_repeated;
  113. char loops_per_request; /* catch endless loops in a single request */
  114. - char keep_alive; /* only request.c can enable it, all other just disable */
  115. + int8_t keep_alive; /* only request.c can enable it, all other just disable */
  116. char async_callback;
  117. buffer *tmp_buf; /* shared; same as srv->tmp_buf */
  118. --- a/src/response.c
  119. +++ b/src/response.c
  120. @@ -103,9 +103,9 @@ http_response_write_header (request_st *
  121. if (light_btst(r->resp_htags, HTTP_HEADER_UPGRADE)
  122. && r->http_version == HTTP_VERSION_1_1) {
  123. http_header_response_set(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("upgrade"));
  124. - } else if (0 == r->keep_alive) {
  125. + } else if (r->keep_alive <= 0) {
  126. http_header_response_set(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("close"));
  127. - } else if (r->http_version == HTTP_VERSION_1_0) {/*(&& r->keep_alive != 0)*/
  128. + } else if (r->http_version == HTTP_VERSION_1_0) {/*(&& r->keep_alive > 0)*/
  129. http_header_response_set(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"), CONST_STR_LEN("keep-alive"));
  130. }