From 85acfd9d77182ca37eba35c8d0a70a09b7ddb701 Mon Sep 17 00:00:00 2001 From: eeeee Date: Wed, 4 Mar 2015 00:38:34 -0800 Subject: [PATCH 1/2] added security token to protocol to prevent packet injection from spoofed source ips --- src/engine/shared/network.cpp | 14 +++++-- src/engine/shared/network.h | 18 +++++++-- src/engine/shared/network_conn.cpp | 59 ++++++++++++++++++++++++---- src/engine/shared/network_server.cpp | 49 ++++++++++++++++++++--- 4 files changed, 121 insertions(+), 19 deletions(-) diff --git a/src/engine/shared/network.cpp b/src/engine/shared/network.cpp index dcdeb4d1c..6446ce75e 100644 --- a/src/engine/shared/network.cpp +++ b/src/engine/shared/network.cpp @@ -101,7 +101,7 @@ void CNetBase::SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void * net_udp_send(Socket, pAddr, aBuffer, 6+DataSize); } -void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket) +void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket, SECURITY_TOKEN SecurityToken) { unsigned char aBuffer[NET_MAX_PACKETSIZE]; int CompressedSize = -1; @@ -117,6 +117,14 @@ void CNetBase::SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct io_flush(ms_DataLogSent); } + if (SecurityToken != NET_SECURITY_TOKEN_UNSUPPORTED) + { + // append security token + // if SecurityToken is NET_SECURITY_TOKEN_UNKNOWN we will still append it hoping to negotiate it + mem_copy(&pPacket->m_aChunkData[pPacket->m_DataSize], &SecurityToken, sizeof(SecurityToken)); + pPacket->m_DataSize += sizeof(SecurityToken); + } + // compress CompressedSize = ms_Huffman.Compress(pPacket->m_aChunkData, pPacket->m_DataSize, &aBuffer[3], NET_MAX_PACKETSIZE-4); @@ -228,7 +236,7 @@ int CNetBase::UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct } -void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize) +void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize, SECURITY_TOKEN SecurityToken) { CNetPacketConstruct Construct; Construct.m_Flags = NET_PACKETFLAG_CONTROL; @@ -239,7 +247,7 @@ void CNetBase::SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int Con mem_copy(&Construct.m_aChunkData[1], pExtra, ExtraSize); // send the control message - CNetBase::SendPacket(Socket, pAddr, &Construct); + CNetBase::SendPacket(Socket, pAddr, &Construct, SecurityToken); } diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h index e8b15fe3e..781633ad3 100644 --- a/src/engine/shared/network.h +++ b/src/engine/shared/network.h @@ -78,6 +78,15 @@ enum NET_ENUM_TERMINATOR }; +typedef int SECURITY_TOKEN; + +static const unsigned char SECURITY_TOKEN_MAGIC[] = {'T', 'K', 'E', 'N'}; + +enum +{ + NET_SECURITY_TOKEN_UNKNOWN = -1, + NET_SECURITY_TOKEN_UNSUPPORTED = 0, +}; typedef int (*NETFUNC_DELCLIENT)(int ClientID, const char* pReason, void *pUser); typedef int (*NETFUNC_NEWCLIENT)(int ClientID, void *pUser); @@ -139,6 +148,7 @@ private: unsigned m_State; int m_Token; + SECURITY_TOKEN m_SecurityToken; int m_RemoteClosed; bool m_BlockCloseMsg; @@ -178,7 +188,7 @@ public: int Update(); int Flush(); - int Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr); + int Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_TOKEN SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED); int QueueChunk(int Flags, int DataSize, const void *pData); const char *ErrorString(); @@ -265,6 +275,8 @@ class CNetServer NETFUNC_DELCLIENT m_pfnDelClient; void *m_UserPtr; + char m_SecurityTokenSeed[16]; + CNetRecvUnpacker m_RecvUnpacker; public: @@ -386,9 +398,9 @@ public: static int Compress(const void *pData, int DataSize, void *pOutput, int OutputSize); static int Decompress(const void *pData, int DataSize, void *pOutput, int OutputSize); - static void SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize); + static void SendControlMsg(NETSOCKET Socket, NETADDR *pAddr, int Ack, int ControlMsg, const void *pExtra, int ExtraSize, SECURITY_TOKEN SecurityToken); static void SendPacketConnless(NETSOCKET Socket, NETADDR *pAddr, const void *pData, int DataSize); - static void SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket); + static void SendPacket(NETSOCKET Socket, NETADDR *pAddr, CNetPacketConstruct *pPacket, SECURITY_TOKEN SecurityToken); static int UnpackPacket(unsigned char *pBuffer, int Size, CNetPacketConstruct *pPacket); // The backroom is ack-NET_MAX_SEQUENCE/2. Used for knowing if we acked a packet or not diff --git a/src/engine/shared/network_conn.cpp b/src/engine/shared/network_conn.cpp index 339897656..fd052647b 100644 --- a/src/engine/shared/network_conn.cpp +++ b/src/engine/shared/network_conn.cpp @@ -24,6 +24,7 @@ void CNetConnection::Reset() m_LastRecvTime = 0; //m_LastUpdateTime = 0; m_Token = -1; + m_SecurityToken = NET_SECURITY_TOKEN_UNKNOWN; //mem_zero(&m_PeerAddr, sizeof(m_PeerAddr)); m_Buffer.Init(); @@ -79,7 +80,7 @@ int CNetConnection::Flush() // send of the packets m_Construct.m_Ack = m_Ack; - CNetBase::SendPacket(m_Socket, &m_PeerAddr, &m_Construct); + CNetBase::SendPacket(m_Socket, &m_PeerAddr, &m_Construct, m_SecurityToken); // update send times m_LastSendTime = time_get(); @@ -94,7 +95,7 @@ int CNetConnection::QueueChunkEx(int Flags, int DataSize, const void *pData, int unsigned char *pChunkData; // check if we have space for it, if not, flush the connection - if(m_Construct.m_DataSize + DataSize + NET_MAX_CHUNKHEADERSIZE > (int)sizeof(m_Construct.m_aChunkData)) + if(m_Construct.m_DataSize + DataSize + NET_MAX_CHUNKHEADERSIZE > (int)sizeof(m_Construct.m_aChunkData) - (int)sizeof(SECURITY_TOKEN)) Flush(); // pack all the data @@ -148,7 +149,7 @@ void CNetConnection::SendControl(int ControlMsg, const void *pExtra, int ExtraSi { // send the control message m_LastSendTime = time_get(); - CNetBase::SendControlMsg(m_Socket, &m_PeerAddr, m_Ack, ControlMsg, pExtra, ExtraSize); + CNetBase::SendControlMsg(m_Socket, &m_PeerAddr, m_Ack, ControlMsg, pExtra, ExtraSize, m_SecurityToken); } void CNetConnection::ResendChunk(CNetChunkResend *pResend) @@ -173,7 +174,7 @@ int CNetConnection::Connect(NETADDR *pAddr) m_PeerAddr = *pAddr; mem_zero(m_ErrorString, sizeof(m_ErrorString)); m_State = NET_CONNSTATE_CONNECT; - SendControl(NET_CTRLMSG_CONNECT, 0, 0); + SendControl(NET_CTRLMSG_CONNECT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)); return 0; } @@ -197,8 +198,22 @@ void CNetConnection::Disconnect(const char *pReason) Reset(); } -int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr) +int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr, SECURITY_TOKEN SecurityToken) { + if (m_SecurityToken != NET_SECURITY_TOKEN_UNKNOWN && m_SecurityToken != NET_SECURITY_TOKEN_UNSUPPORTED) + { + // supposed to have a valid token in this packet, check it + if (pPacket->m_DataSize < sizeof(m_SecurityToken)) + return -1; + pPacket->m_DataSize -= sizeof(m_SecurityToken); + if (m_SecurityToken != *(SECURITY_TOKEN*)&pPacket->m_aChunkData[pPacket->m_DataSize]) + { + if(g_Config.m_Debug) + dbg_msg("security", "token mismatch, expected %d got %d", m_SecurityToken, *(SECURITY_TOKEN*)&pPacket->m_aChunkData[pPacket->m_DataSize]); + return -1; + } + } + int64 Now = time_get(); // check if resend is requested @@ -262,7 +277,21 @@ int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr) m_LastSendTime = Now; m_LastRecvTime = Now; m_LastUpdateTime = Now; - SendControl(NET_CTRLMSG_CONNECTACCEPT, 0, 0); + if (m_SecurityToken == NET_SECURITY_TOKEN_UNKNOWN + && pPacket->m_DataSize >= 1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(m_SecurityToken) + && !mem_comp(&pPacket->m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC))) + { + m_SecurityToken = SecurityToken; + if(g_Config.m_Debug) + dbg_msg("security", "generated token %d", m_SecurityToken); + } + else + { + if(g_Config.m_Debug) + dbg_msg("security", "token not supported by client (packet size %d)", pPacket->m_DataSize); + m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED; + } + SendControl(NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)); if(g_Config.m_Debug) dbg_msg("connection", "got connection, sending connect+accept"); } @@ -272,6 +301,20 @@ int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr) // connection made if(CtrlMsg == NET_CTRLMSG_CONNECTACCEPT) { + if (m_SecurityToken == NET_SECURITY_TOKEN_UNKNOWN + && pPacket->m_DataSize >= 1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(m_SecurityToken) + && !mem_comp(&pPacket->m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC))) + { + m_SecurityToken = *(SECURITY_TOKEN*)(&pPacket->m_aChunkData[1 + sizeof(SECURITY_TOKEN_MAGIC)]); + if(g_Config.m_Debug) + dbg_msg("security", "got token %d", m_SecurityToken); + } + else + { + m_SecurityToken = NET_SECURITY_TOKEN_UNSUPPORTED; + if(g_Config.m_Debug) + dbg_msg("security", "token not supported by server"); + } m_LastRecvTime = Now; SendControl(NET_CTRLMSG_ACCEPT, 0, 0); m_State = NET_CONNSTATE_ONLINE; @@ -364,12 +407,12 @@ int CNetConnection::Update() else if(State() == NET_CONNSTATE_CONNECT) { if(time_get()-m_LastSendTime > time_freq()/2) // send a new connect every 500ms - SendControl(NET_CTRLMSG_CONNECT, 0, 0); + SendControl(NET_CTRLMSG_CONNECT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)); } else if(State() == NET_CONNSTATE_PENDING) { if(time_get()-m_LastSendTime > time_freq()/2) // send a new connect/accept every 500ms - SendControl(NET_CTRLMSG_CONNECTACCEPT, 0, 0); + SendControl(NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)); } return 0; diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp index fe1c19f21..a20f64783 100644 --- a/src/engine/shared/network_server.cpp +++ b/src/engine/shared/network_server.cpp @@ -4,9 +4,10 @@ #include +#include "config.h" #include "netban.h" #include "network.h" - +#include bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int MaxClientsPerIP, int Flags) { @@ -29,6 +30,29 @@ bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int Ma m_MaxClientsPerIP = MaxClientsPerIP; + IOHANDLE urandom = io_open("/dev/urandom", IOFLAG_READ); + if (urandom) { + io_read(urandom, m_SecurityTokenSeed, sizeof(m_SecurityTokenSeed)); + io_close(urandom); + } + else + { + dbg_msg("security", "/dev/urandom is not available. using sv_rcon_password for security token seed, make sure you have a long password!"); + str_copy(m_SecurityTokenSeed, g_Config.m_SvRconPassword, sizeof(m_SecurityTokenSeed)); + long timestamp = time_get(); + md5_state_t md5; + md5_byte_t digest[16]; + // do over 9000 rounds of md5 on the rcon password, to make it harder to recover the password from the token values + for (int i = 0; i < 1000000; i++) + { + md5_init(&md5); + md5_append(&md5, (unsigned char*)m_SecurityTokenSeed, sizeof(m_SecurityTokenSeed)); + md5_append(&md5, (unsigned char*)×tamp, sizeof(timestamp)); + md5_finish(&md5, digest); + mem_copy(m_SecurityTokenSeed, digest, sizeof(m_SecurityTokenSeed) < sizeof(digest) ? sizeof(m_SecurityTokenSeed) : sizeof(digest)); + } + } + for(int i = 0; i < NET_MAX_CLIENTS; i++) m_aSlots[i].m_Connection.Init(m_Socket, true); @@ -108,7 +132,7 @@ int CNetServer::Recv(CNetChunk *pChunk) if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf))) { // banned, reply with a message - CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf)+1); + CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf)+1, NET_SECURITY_TOKEN_UNSUPPORTED); continue; } @@ -170,7 +194,7 @@ int CNetServer::Recv(CNetChunk *pChunk) { char aBuf[128]; str_format(aBuf, sizeof(aBuf), "Only %d players with the same IP are allowed", m_MaxClientsPerIP); - CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, sizeof(aBuf)); + CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, sizeof(aBuf), NET_SECURITY_TOKEN_UNSUPPORTED); return 0; } } @@ -181,7 +205,22 @@ int CNetServer::Recv(CNetChunk *pChunk) if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE) { Found = true; - m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr); + long timestamp = time_get(); + md5_state_t md5; + md5_byte_t digest[16]; + SECURITY_TOKEN securityToken; + do + { + md5_init(&md5); + md5_append(&md5, (unsigned char*)m_SecurityTokenSeed, sizeof(m_SecurityTokenSeed)); + md5_append(&md5, (unsigned char*)&Addr, sizeof(Addr)); + md5_append(&md5, (unsigned char*)×tamp, sizeof(timestamp)); + md5_finish(&md5, digest); + securityToken = *(SECURITY_TOKEN*)digest; + timestamp++; + } + while (securityToken == NET_SECURITY_TOKEN_UNKNOWN || securityToken == NET_SECURITY_TOKEN_UNSUPPORTED); + m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr, securityToken); if(m_pfnNewClient) m_pfnNewClient(i, m_UserPtr); break; @@ -191,7 +230,7 @@ int CNetServer::Recv(CNetChunk *pChunk) if(!Found) { const char FullMsg[] = "This server is full"; - CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, FullMsg, sizeof(FullMsg)); + CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, FullMsg, sizeof(FullMsg), NET_SECURITY_TOKEN_UNSUPPORTED); } } } From fa0708b99daa54d14309652e36322787ed189cfd Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Fri, 6 Mar 2015 00:53:59 +0100 Subject: [PATCH 2/2] Make the secure random stuff platform-independent --- bam.lua | 1 + src/base/system.c | 65 ++++++++++++++++++++++++++++ src/base/system.h | 21 +++++++++ src/engine/shared/network.h | 2 +- src/engine/shared/network_server.cpp | 25 +++-------- 5 files changed, 93 insertions(+), 21 deletions(-) diff --git a/bam.lua b/bam.lua index 1d2582cef..30820226a 100644 --- a/bam.lua +++ b/bam.lua @@ -240,6 +240,7 @@ function build(settings) settings.link.libs:Add("ws2_32") settings.link.libs:Add("ole32") settings.link.libs:Add("shell32") + settings.link.libs:Add("advapi32") end -- compile zlib if needed diff --git a/src/base/system.c b/src/base/system.c index 1e555415c..5e77662cc 100644 --- a/src/base/system.c +++ b/src/base/system.c @@ -57,6 +57,7 @@ #include #include #include + #include #else #error NOT IMPLEMENTED #endif @@ -2355,6 +2356,70 @@ void shell_execute(const char *file, const char *argv) #endif } +struct SECURE_RANDOM_DATA +{ + int initialized; +#if defined(CONF_FAMILY_WINDOWS) + HCRYPTPROV provider; +#else + IOHANDLE urandom; +#endif +}; + +static struct SECURE_RANDOM_DATA secure_random_data = { 0 }; + +int secure_random_init() +{ + if(secure_random_data.initialized) + { + return 0; + } +#if defined(CONF_FAMILY_WINDOWS) + if(CryptAcquireContext(&secure_random_data.provider, NULL, NULL, PROV_RSA_FULL, 0)) + { + secure_random_data.initialized = 1; + return 0; + } + else + { + return 1; + } +#else + secure_random_data.urandom = io_open("/dev/urandom", IOFLAG_READ); + if(secure_random_data.urandom) + { + secure_random_data.initialized = 1; + return 0; + } + else + { + return 1; + } +#endif +} + +void secure_random_fill(unsigned char *bytes, size_t length) +{ + if(!secure_random_data.initialized) + { + dbg_msg("secure", "called secure_random_fill before secure_random_init"); + dbg_break(); + } +#if defined(CONF_FAMILY_WINDOWS) + if(!CryptGenRandom(secure_random_data.provider, length, bytes)) + { + dbg_msg("secure", "CryptGenRandom failed, last_error=%d", GetLastError()); + dbg_break(); + } +#else + if(length != io_read(secure_random_data.urandom, bytes, length)) + { + dbg_msg("secure", "io_read returned with a short read"); + dbg_break(); + } +#endif +} + #if defined(__cplusplus) } #endif diff --git a/src/base/system.h b/src/base/system.h index 2afec70db..238d88353 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -1328,6 +1328,27 @@ int pid(); void shell_execute(const char *file, const char *argv); +/* + Function: secure_random_init + Initializes the secure random module. + You *MUST* check the return value of this function. + + Returns: + 0 - Initialization succeeded. + 1 - Initialization failed. +*/ +int secure_random_init(); + +/* + Function: secure_random_fill + Fills the buffer with the specified amount of random bytes. + + Parameters: + buffer - Pointer to the start of the buffer. + length - Length of the buffer. +*/ +void secure_random_fill(unsigned char *bytes, size_t length); + #ifdef __cplusplus } #endif diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h index 781633ad3..7e2c9d43b 100644 --- a/src/engine/shared/network.h +++ b/src/engine/shared/network.h @@ -275,7 +275,7 @@ class CNetServer NETFUNC_DELCLIENT m_pfnDelClient; void *m_UserPtr; - char m_SecurityTokenSeed[16]; + unsigned char m_SecurityTokenSeed[16]; CNetRecvUnpacker m_RecvUnpacker; diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp index a20f64783..6c20307ab 100644 --- a/src/engine/shared/network_server.cpp +++ b/src/engine/shared/network_server.cpp @@ -30,29 +30,14 @@ bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int Ma m_MaxClientsPerIP = MaxClientsPerIP; - IOHANDLE urandom = io_open("/dev/urandom", IOFLAG_READ); - if (urandom) { - io_read(urandom, m_SecurityTokenSeed, sizeof(m_SecurityTokenSeed)); - io_close(urandom); - } - else + if(secure_random_init() != 0) { - dbg_msg("security", "/dev/urandom is not available. using sv_rcon_password for security token seed, make sure you have a long password!"); - str_copy(m_SecurityTokenSeed, g_Config.m_SvRconPassword, sizeof(m_SecurityTokenSeed)); - long timestamp = time_get(); - md5_state_t md5; - md5_byte_t digest[16]; - // do over 9000 rounds of md5 on the rcon password, to make it harder to recover the password from the token values - for (int i = 0; i < 1000000; i++) - { - md5_init(&md5); - md5_append(&md5, (unsigned char*)m_SecurityTokenSeed, sizeof(m_SecurityTokenSeed)); - md5_append(&md5, (unsigned char*)×tamp, sizeof(timestamp)); - md5_finish(&md5, digest); - mem_copy(m_SecurityTokenSeed, digest, sizeof(m_SecurityTokenSeed) < sizeof(digest) ? sizeof(m_SecurityTokenSeed) : sizeof(digest)); - } + dbg_msg("secure", "could not initialize secure RNG"); + return false; } + secure_random_fill(m_SecurityTokenSeed, sizeof(m_SecurityTokenSeed)); + for(int i = 0; i < NET_MAX_CLIENTS; i++) m_aSlots[i].m_Connection.Init(m_Socket, true);