From 35077deaf14f824af53d38b2ba86834b66fa288c Mon Sep 17 00:00:00 2001 From: Daiyuu Nobori Date: Mon, 9 Oct 2023 17:13:41 +0200 Subject: [PATCH] Fix Vulnerability: CVE-2023-25774 TALOS-2023-1743 SoftEther VPN vpnserver ConnectionAccept () denial of service vulnerability --- src/Cedar/Admin.c | 12 +-- src/Cedar/Cedar.c | 69 ++++++++++++++ src/Cedar/Cedar.h | 2 + src/Cedar/Listener.c | 217 +++++++++++++++++++++++++++++++++++++++++++ src/Cedar/Listener.h | 19 ++++ 5 files changed, 309 insertions(+), 10 deletions(-) diff --git a/src/Cedar/Admin.c b/src/Cedar/Admin.c index 85f9bd9b..b79a5cea 100644 --- a/src/Cedar/Admin.c +++ b/src/Cedar/Admin.c @@ -726,9 +726,8 @@ void AdminWebProcPost(CONNECTION *c, SOCK *s, HTTP_HEADER *h, UINT post_data_siz if (RecvAll(s, data, post_data_size, s->SecureMode)) { c->JsonRpcAuthed = true; -#ifndef GC_SOFTETHER_OSS + RemoveDosEntry(c->Listener, s); -#endif // GC_SOFTETHER_OSS // Divide url_target into URL and query string StrCpy(url, sizeof(url), url_target); @@ -767,9 +766,8 @@ void AdminWebProcGet(CONNECTION *c, SOCK *s, HTTP_HEADER *h, char *url_target) } c->JsonRpcAuthed = true; -#ifndef GC_SOFTETHER_OSS + RemoveDosEntry(c->Listener, s); -#endif // GC_SOFTETHER_OSS // Divide url_target into URL and query string StrCpy(url, sizeof(url), url_target); @@ -1199,9 +1197,7 @@ void JsonRpcProcOptions(CONNECTION *c, SOCK *s, HTTP_HEADER *h, char *url_target c->JsonRpcAuthed = true; -#ifndef GC_SOFTETHER_OSS RemoveDosEntry(c->Listener, s); -#endif // GC_SOFTETHER_OSS AdminWebSendBody(s, 200, "OK", NULL, 0, NULL, NULL, NULL, h); } @@ -1228,9 +1224,7 @@ void JsonRpcProcGet(CONNECTION *c, SOCK *s, HTTP_HEADER *h, char *url_target) c->JsonRpcAuthed = true; -#ifndef GC_SOFTETHER_OSS RemoveDosEntry(c->Listener, s); -#endif // GC_SOFTETHER_OSS // Divide url_target into URL and query string StrCpy(url, sizeof(url), url_target); @@ -1357,9 +1351,7 @@ void JsonRpcProcPost(CONNECTION *c, SOCK *s, HTTP_HEADER *h, UINT post_data_size c->JsonRpcAuthed = true; -#ifndef GC_SOFTETHER_OSS RemoveDosEntry(c->Listener, s); -#endif // GC_SOFTETHER_OSS if (json_req == NULL || json_req_object == NULL) { diff --git a/src/Cedar/Cedar.c b/src/Cedar/Cedar.c index 02b4ee2b..6f3e32c2 100644 --- a/src/Cedar/Cedar.c +++ b/src/Cedar/Cedar.c @@ -322,6 +322,34 @@ void DecrementNoSsl(CEDAR *c, IP *ip, UINT num_dec) UnlockList(c->NonSslList); } +// Check whether the specified IP address is in Non-SSL connection list +bool IsInNoSsl(CEDAR *c, IP *ip) +{ + bool ret = false; + // Validate arguments + if (c == NULL || ip == NULL) + { + return false; + } + + LockList(c->NonSslList); + { + NON_SSL *n = SearchNoSslList(c, ip); + + if (n != NULL) + { + if (n->EntryExpires > Tick64() && n->Count > NON_SSL_MIN_COUNT) + { + n->EntryExpires = Tick64() + (UINT64)NON_SSL_ENTRY_EXPIRES; + ret = true; + } + } + } + UnlockList(c->NonSslList); + + return ret; +} + // Add new entry to Non-SSL connection list bool AddNoSsl(CEDAR *c, IP *ip) { @@ -704,6 +732,47 @@ void DelConnection(CEDAR *cedar, CONNECTION *c) UnlockList(cedar->ConnectionList); } +// Get the number of unestablished connections +UINT GetUnestablishedConnections(CEDAR *cedar) +{ + UINT i, ret; + // Validate arguments + if (cedar == NULL) + { + return 0; + } + + ret = 0; + + LockList(cedar->ConnectionList); + { + for (i = 0;i < LIST_NUM(cedar->ConnectionList);i++) + { + CONNECTION *c = LIST_DATA(cedar->ConnectionList, i); + + switch (c->Type) + { + case CONNECTION_TYPE_CLIENT: + case CONNECTION_TYPE_INIT: + case CONNECTION_TYPE_LOGIN: + case CONNECTION_TYPE_ADDITIONAL: + switch (c->Status) + { + case CONNECTION_STATUS_ACCEPTED: + case CONNECTION_STATUS_NEGOTIATION: + case CONNECTION_STATUS_USERAUTH: + ret++; + break; + } + break; + } + } + } + UnlockList(cedar->ConnectionList); + + return ret + Count(cedar->AcceptingSockets); +} + // Add connection to Cedar void AddConnection(CEDAR *cedar, CONNECTION *c) { diff --git a/src/Cedar/Cedar.h b/src/Cedar/Cedar.h index ec1b18b9..699263a9 100644 --- a/src/Cedar/Cedar.h +++ b/src/Cedar/Cedar.h @@ -1022,6 +1022,7 @@ void DelHubEx(CEDAR *c, HUB *h, bool no_lock); void StopAllHub(CEDAR *c); void StopAllConnection(CEDAR *c); void AddConnection(CEDAR *cedar, CONNECTION *c); +UINT GetUnestablishedConnections(CEDAR *cedar); void DelConnection(CEDAR *cedar, CONNECTION *c); void SetCedarCipherList(CEDAR *cedar, char *name); void InitCedar(); @@ -1046,6 +1047,7 @@ bool AddNoSsl(CEDAR *c, IP *ip); void DecrementNoSsl(CEDAR *c, IP *ip, UINT num_dec); void DeleteOldNoSsl(CEDAR *c); NON_SSL *SearchNoSslList(CEDAR *c, IP *ip); +bool IsInNoSsl(CEDAR *c, IP *ip); void FreeTinyLog(TINY_LOG *t); void WriteTinyLog(TINY_LOG *t, char *str); TINY_LOG *NewTinyLog(); diff --git a/src/Cedar/Listener.c b/src/Cedar/Listener.c index 6ea0d5cc..ec295403 100644 --- a/src/Cedar/Listener.c +++ b/src/Cedar/Listener.c @@ -17,6 +17,7 @@ #include "Mayaqua/Memory.h" #include "Mayaqua/Object.h" #include "Mayaqua/Str.h" +#include "Mayaqua/Tick64.h" static bool disable_dos = false; static UINT max_connections_per_ip = DEFAULT_MAX_CONNECTIONS_PER_IP; @@ -181,6 +182,11 @@ void TCPAcceptedThread(THREAD *t, void *param) ConnectionAccept(c); flag1 = c->flag1; + if (c->JsonRpcAuthed) + { + RemoveDosEntry(r, s); + } + // Release SLog(r->Cedar, "LS_CONNECTION_END_1", c->Name); ReleaseListener(c->Listener); @@ -221,6 +227,46 @@ void TCPAccepted(LISTENER *r, SOCK *s) num_clients_from_this_ip = GetNumIpClient(&s->RemoteIP); +#ifdef USE_DOS_ATTACK_DETECTION + if (disable_dos == false && r->DisableDos == false && r->Protocol != LISTENER_INPROC) + { + UINT max_uec, now_uec; + // DOS attack check + if (CheckDosAttack(r, s) == false) + { + Debug("DOS Attack 1 !!\n"); + IPToStr(tmp, sizeof(tmp), &s->RemoteIP); + SLog(r->Cedar, "LS_LISTENER_DOS", r->Port, tmp, s->RemotePort); + return; + } + if (StrCmpi(s->UnderlayProtocol, SOCK_UNDERLAY_NATIVE_V6) == 0 || + StrCmpi(s->UnderlayProtocol, SOCK_UNDERLAY_NATIVE_V4) == 0) + { + if (IsInNoSsl(r->Cedar, &s->RemoteIP)) + { + Debug("DOS Attack 2 !!\n"); + IPToStr(tmp, sizeof(tmp), &s->RemoteIP); + SLog(r->Cedar, "LS_LISTENER_DOS", r->Port, tmp, s->RemotePort); + return; + } + } + if (num_clients_from_this_ip > GetMaxConnectionsPerIp()) + { + Debug("DOS Attack 3 !!\n"); + IPToStr(tmp, sizeof(tmp), &s->RemoteIP); + SLog(r->Cedar, "LS_LISTENER_DOS", r->Port, tmp, s->RemotePort); + return; + } + max_uec = GetMaxUnestablishedConnections(); + now_uec = GetUnestablishedConnections(cedar); + if (now_uec > max_uec) + { + Debug("DOS Attack 4 !!\n"); + SLog(r->Cedar, "LS_LISTENER_MAXUEC", max_uec, now_uec); + return; + } + } +#endif // USE_DOS_ATTACK_DETECTION IPToStr(tmp, sizeof(tmp), &s->RemoteIP); @@ -239,6 +285,169 @@ void TCPAccepted(LISTENER *r, SOCK *s) ReleaseThread(t); } +// Remove a DOS entry +bool RemoveDosEntry(LISTENER *r, SOCK *s) +{ + DOS *d; + bool ok = false; + // Validate arguments + if (r == NULL || s == NULL) + { + return false; + } + + LockList(r->DosList); + { + // Delete old entries from the DOS attack list + RefreshDosList(r); + + // Search the table + d = SearchDosList(r, &s->RemoteIP); + + if (d != NULL) + { + Delete(r->DosList, d); + Free(d); + ok = true; + } + } + UnlockList(r->DosList); + + return ok; +} + +// Check whether this is a DOS attack +bool CheckDosAttack(LISTENER *r, SOCK *s) +{ + DOS *d; + bool ok = true; + // Validate arguments + if (r == NULL || s == NULL) + { + return false; + } + + LockList(r->DosList); + { + // Delete old entries from the DOS attack list + RefreshDosList(r); + + // Search the table + d = SearchDosList(r, &s->RemoteIP); + + if (d != NULL) + { + // There is a entry already + // This should mean being under a DOS attack + d->LastConnectedTick = Tick64(); + d->CurrentExpireSpan = MIN(d->CurrentExpireSpan * (UINT64)2, DOS_TABLE_EXPIRES_MAX); + d->AccessCount++; + if (d->AccessCount > DOS_TABLE_MAX_LIMIT_PER_IP) + { + ok = false; + } + } + else + { + // Create a new entry + d = ZeroMalloc(sizeof(DOS)); + d->CurrentExpireSpan = (UINT64)DOS_TABLE_EXPIRES_FIRST; + d->FirstConnectedTick = d->LastConnectedTick = Tick64(); + d->AccessCount = 1; + d->DeleteEntryTick = d->FirstConnectedTick + (UINT64)DOS_TABLE_EXPIRES_TOTAL; + Copy(&d->IpAddress, &s->RemoteIP, sizeof(IP)); + Add(r->DosList, d); + } + } + UnlockList(r->DosList); + + return ok; +} + +// Delete old entries from the DOS attack list +void RefreshDosList(LISTENER *r) +{ + // Validate arguments + if (r == NULL) + { + return; + } + + if (r->DosListLastRefreshTime == 0 || + (r->DosListLastRefreshTime + (UINT64)DOS_TABLE_REFRESH_INTERVAL) <= Tick64()) + { + UINT i; + LIST *o; + r->DosListLastRefreshTime = Tick64(); + + o = NewListFast(NULL); + for (i = 0;i < LIST_NUM(r->DosList);i++) + { + DOS *d = LIST_DATA(r->DosList, i); + if ((d->LastConnectedTick + d->CurrentExpireSpan) <= Tick64() || + (d->DeleteEntryTick <= Tick64())) + { + Add(o, d); + } + } + + for (i = 0;i < LIST_NUM(o);i++) + { + DOS *d = LIST_DATA(o, i); + Delete(r->DosList, d); + Free(d); + } + + ReleaseList(o); + } +} + +// Search the DOS attack list by the IP address +DOS *SearchDosList(LISTENER *r, IP *ip) +{ + DOS *d, t; + // Validate arguments + if (r == NULL || ip == NULL) + { + return NULL; + } + + Copy(&t.IpAddress, ip, sizeof(IP)); + + d = Search(r->DosList, &t); + + if (d != NULL) + { + if ((d->LastConnectedTick + d->CurrentExpireSpan) <= Tick64() || + (d->DeleteEntryTick <= Tick64())) + { + // Delete old entries + Delete(r->DosList, d); + Free(d); + return NULL; + } + } + + return d; +} + +// Comparison of DOS attack list entries +int CompareDos(void *p1, void *p2) +{ + DOS *d1, *d2; + if (p1 == NULL || p2 == NULL) + { + return 0; + } + d1 = *(DOS **)p1; + d2 = *(DOS **)p2; + if (d1 == NULL || d2 == NULL) + { + return 0; + } + + return CmpIpAddr(&d1->IpAddress, &d2->IpAddress); +} // UDP listener main loop void ListenerUDPMainLoop(LISTENER *r) @@ -653,6 +862,13 @@ void CleanupListener(LISTENER *r) return; } + // Release the DOS attack list + for (i = 0;i < LIST_NUM(r->DosList);i++) + { + DOS *d = LIST_DATA(r->DosList, i); + Free(d); + } + ReleaseList(r->DosList); if (r->Sock != NULL) { @@ -802,6 +1018,7 @@ LISTENER *NewListenerEx5(CEDAR *cedar, UINT proto, UINT port, THREAD_PROC *proc, r->Port = port; r->Event = NewEvent(); + r->DosList = NewList(CompareDos); r->LocalOnly = local_only; r->ShadowIPv6 = shadow_ipv6; diff --git a/src/Cedar/Listener.h b/src/Cedar/Listener.h index 40b816f1..e3609df5 100644 --- a/src/Cedar/Listener.h +++ b/src/Cedar/Listener.h @@ -10,12 +10,24 @@ #include "CedarType.h" +#include "Mayaqua/MayaType.h" #include "Mayaqua/Kernel.h" +#include "Mayaqua/Network.h" // Function to call when receiving a new connection typedef void (NEW_CONNECTION_PROC)(CONNECTION *c); +// DOS attack list +struct DOS +{ + IP IpAddress; // IP address + UINT64 FirstConnectedTick; // Time which a client connects at the first time + UINT64 LastConnectedTick; // Time which a client connected at the last time + UINT64 CurrentExpireSpan; // Current time-out period of this record + UINT64 DeleteEntryTick; // Time planned to delete this entry + UINT AccessCount; // The number of accesses +}; // Listener structure struct LISTENER @@ -31,6 +43,8 @@ struct LISTENER volatile bool Halt; // Halting flag UINT Status; // State + LIST *DosList; // DOS attack list + UINT64 DosListLastRefreshTime; // Time that the DOS list is refreshed at the last THREAD_PROC *ThreadProc; // Thread procedure void *ThreadParam; // Thread parameters @@ -105,6 +119,11 @@ void FreeDynamicListener(DYNAMIC_LISTENER *d); bool ListenerRUDPRpcRecvProc(RUDP_STACK *r, UDPPACKET *p); void ListenerSetProcRecvRpcEnable(bool b); +int CompareDos(void *p1, void *p2); +DOS *SearchDosList(LISTENER *r, IP *ip); +void RefreshDosList(LISTENER *r); +bool CheckDosAttack(LISTENER *r, SOCK *s); +bool RemoveDosEntry(LISTENER *r, SOCK *s); #endif // LISTENER_H