diff --git a/common/network/Blacklist.cxx b/common/network/Blacklist.cxx new file mode 100644 index 0000000..a5caeea --- /dev/null +++ b/common/network/Blacklist.cxx @@ -0,0 +1,61 @@ +/* Copyright (C) 2021 Kasm + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +static std::map hits; +static std::map blacklist; + +unsigned char bl_isBlacklisted(const char *addr) { + const unsigned char count = blacklist.count(addr); + if (!count) + return 0; + + const time_t now = time(NULL); + const unsigned timeout = rfb::Blacklist::initialTimeout; + + if (now - timeout > blacklist[addr]) { + blacklist.erase(addr); + hits.erase(addr); + return 0; + } else { + blacklist[addr] = now; + return 1; + } +} + +void bl_addFailure(const char *addr) { + const unsigned num = ++hits[addr]; + if (num >= (unsigned) rfb::Blacklist::threshold) { + blacklist[addr] = time(NULL); + } +} diff --git a/common/network/Blacklist.h b/common/network/Blacklist.h new file mode 100644 index 0000000..799c796 --- /dev/null +++ b/common/network/Blacklist.h @@ -0,0 +1,33 @@ +/* Copyright (C) 2021 Kasm + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef __NETWORK_BLACKLIST_H__ +#define __NETWORK_BLACKLIST_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +unsigned char bl_isBlacklisted(const char *); +void bl_addFailure(const char *); + +#ifdef __cplusplus +} // extern C +#endif + +#endif // __NETWORK_TCP_SOCKET_H__ diff --git a/common/network/CMakeLists.txt b/common/network/CMakeLists.txt index d63c696..a767e73 100644 --- a/common/network/CMakeLists.txt +++ b/common/network/CMakeLists.txt @@ -2,6 +2,7 @@ include_directories(${CMAKE_SOURCE_DIR}/common ${CMAKE_SOURCE_DIR}/unix/kasmvncp set(NETWORK_SOURCES GetAPIMessager.cxx + Blacklist.cxx Socket.cxx TcpSocket.cxx websocket.c diff --git a/common/network/websocket.c b/common/network/websocket.c index 6270b49..01d246e 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -32,6 +32,7 @@ #include /* sha1 hash */ #include "websocket.h" #include "kasmpasswd.h" +#include /* * Global state @@ -1203,7 +1204,7 @@ nope: return 1; } -ws_ctx_t *do_handshake(int sock) { +ws_ctx_t *do_handshake(int sock, const char *ip) { char handshake[4096], response[4096], sha1[29], trailer[17]; char *scheme, *pre; headers_t *headers; @@ -1271,10 +1272,20 @@ ws_ctx_t *do_handshake(int sock) { usleep(10); } + if (bl_isBlacklisted(ip)) { + wserr("IP %s is blacklisted, dropping\n", ip); + sprintf(response, "HTTP/1.1 401 Forbidden\r\n" + "\r\n"); + ws_send(ws_ctx, response, strlen(response)); + free_ws_ctx(ws_ctx); + return NULL; + } + unsigned char owner = 0; if (!settings.disablebasicauth) { const char *hdr = strstr(handshake, "Authorization: Basic "); if (!hdr) { + bl_addFailure(ip); handler_emsg("BasicAuth required, but client didn't send any. 401 Unauth\n"); sprintf(response, "HTTP/1.1 401 Unauthorized\r\n" "WWW-Authenticate: Basic realm=\"Websockify\"\r\n" @@ -1288,6 +1299,7 @@ ws_ctx_t *do_handshake(int sock) { const char *end = strchr(hdr, '\r'); if (!end || end - hdr > 256) { handler_emsg("Client sent invalid BasicAuth, dropping connection\n"); + bl_addFailure(ip); free_ws_ctx(ws_ctx); return NULL; } @@ -1357,6 +1369,7 @@ ws_ctx_t *do_handshake(int sock) { if (len <= 0 || strcmp(authbuf, response)) { handler_emsg("BasicAuth user/pw did not match\n"); + bl_addFailure(ip); sprintf(response, "HTTP/1.1 401 Forbidden\r\n" "\r\n"); ws_send(ws_ctx, response, strlen(response)); @@ -1445,7 +1458,7 @@ void *subthread(void *ptr) { ws_ctx_t *ws_ctx; - ws_ctx = do_handshake(csock); + ws_ctx = do_handshake(csock, pass->ip); if (ws_ctx == NULL) { handler_msg("No connection after handshake\n"); goto out; // Child process exits