Hello there,

in case you find this useful, here is a small patch to slightly improve the security by proposing an alternative to sending a clear-text password over the network.

This small patch introduces the possibility to avoid sending a clear-text password over the network by using a MD5 hash instead; the provided code uses this facility with the 'rconPassword'.

The compatibility with legacy clients or servers is also maintained, ie
a MD5-enabled server still allows legacy clients to send a clear-text
'rconPassword' and vice-versa.

Basically, the patch introduces a new function, Com_MD5String(), which
take a string with an optionnal prefix and returns the calculated MD5 hash. A new cvar, 'sv_MD5', is added in the server and propagated through the server infostring to let any clients know about the MD5 availibility.

To avoid sending the password by itself (either in plain-text or its MD5 counterpart), the MD5 hash is created using the 'rconPassword' prefixed by the client's current challenge; 2 clients on the same server will then send 2 different MD5 hashes.

Have a nice day

ps: please, add a notice on the ioq3 ML web page to state that only members of this ML are allowed to posts to this ML
Index: code/server/server.h
===================================================================
--- code/server/server.h	(révision 1803)
+++ code/server/server.h	(copie de travail)
@@ -282,6 +282,7 @@
 extern	cvar_t	*sv_lanForceRate;
 extern	cvar_t	*sv_strictAuth;
 extern	cvar_t	*sv_banFile;
+extern	cvar_t	*sv_MD5;	// uZu - let clients know about MD5 availibility
 
 extern	serverBan_t serverBans[SERVER_MAXBANS];
 extern	int serverBansCount;
Index: code/server/sv_init.c
===================================================================
--- code/server/sv_init.c	(révision 1803)
+++ code/server/sv_init.c	(copie de travail)
@@ -644,6 +644,9 @@
 	sv_minPing = Cvar_Get ("sv_minPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO );
 	sv_maxPing = Cvar_Get ("sv_maxPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO );
 	sv_floodProtect = Cvar_Get ("sv_floodProtect", "1", CVAR_ARCHIVE | CVAR_SERVERINFO );
+	// uZu - let clients know about MD5 availibility
+	sv_MD5 = Cvar_Get ("sv_MD5", "1", CVAR_SERVERINFO | CVAR_ROM);
+	// !uZu
 
 	// systeminfo
 	Cvar_Get ("sv_cheats", "1", CVAR_SYSTEMINFO | CVAR_ROM );
Index: code/server/sv_main.c
===================================================================
--- code/server/sv_main.c	(révision 1803)
+++ code/server/sv_main.c	(copie de travail)
@@ -58,6 +58,7 @@
 cvar_t	*sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491)
 cvar_t	*sv_strictAuth;
 cvar_t	*sv_banFile;
+cvar_t	*sv_MD5;	// uZu - let clients know about MD5 availibility
 
 serverBan_t serverBans[SERVER_MAXBANS];
 int serverBansCount = 0;
@@ -660,6 +661,10 @@
 		Info_SetValueForKey( infostring, "game", gamedir );
 	}
 
+	// uZu - let clients know about MD5 availibility
+	Info_SetValueForKey( infostring, "sv_MD5", va("%i", sv_MD5->integer) );
+	// !uZu
+
 	NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring );
 }
 
@@ -690,6 +695,11 @@
 #define SV_OUTPUTBUF_LENGTH (1024 - 16)
 	char		sv_outputbuf[SV_OUTPUTBUF_LENGTH];
 	char *cmd_aux;
+	// uZu - use the current client's challenge as MD5 hash prefix
+	challenge_t	*challenge;
+	char		challenge_str[12];
+	int		i;
+	// !uZu
 
 	// Prevent using rcon as an amplifier and make dictionary attacks impractical
 	if ( SVC_RateLimitAddress( from, 10, 1000 ) ) {
@@ -698,8 +708,22 @@
 		return;
 	}
 
+	// uZu - avoid clear text password but support legacy clients
+	challenge = &svs.challenges[0];
+	for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) {
+		if ( challenge->connected && NET_CompareAdr(from, challenge->adr) ) {
+			// found the current client's challenge
+			break;
+		}
+	}
+	Com_sprintf( challenge_str, sizeof(challenge_str), "%d", challenge->challenge );
+	// !uZu
+
 	if ( !strlen( sv_rconPassword->string ) ||
-		strcmp (Cmd_Argv(1), sv_rconPassword->string) ) {
+		// uZu - first check the MD5 hash
+		(strcmp (Cmd_Argv(1), Com_MD5String(sv_rconPassword->string, challenge_str, sizeof(challenge_str))) &&
+		// !uZu
+		strcmp (Cmd_Argv(1), sv_rconPassword->string)) ) {
 		static leakyBucket_t bucket;
 
 		// Make DoS via rcon impractical
Index: code/qcommon/md5.c
===================================================================
--- code/qcommon/md5.c	(révision 1803)
+++ code/qcommon/md5.c	(copie de travail)
@@ -308,3 +308,27 @@
 	}
 	return final;
 }
+
+// uZu - create and returns the MD5 hash of a string
+char *Com_MD5String( const char *string, const char *prefix, int prefix_len )
+{
+	MD5_CTX md5;
+	static char final[33] = {""};
+	unsigned char digest[16] = {""}; 
+	int i;
+
+	MD5Init(&md5);
+
+	if (prefix_len && *prefix)
+		MD5Update(&md5 , (unsigned char *)prefix, prefix_len);
+
+	MD5Update(&md5, (unsigned char *)string, sizeof(string));
+	MD5Final(&md5, digest);
+	final[0] = '\0';
+	for(i = 0; i < 16; i++) {
+		Q_strcat(final, sizeof(final), va("%02X", digest[i]));
+	}
+	return final;
+}
+// !uZu
+
Index: code/qcommon/qcommon.h
===================================================================
--- code/qcommon/qcommon.h	(révision 1803)
+++ code/qcommon/qcommon.h	(copie de travail)
@@ -803,6 +803,9 @@
 int			Com_Milliseconds( void );	// will be journaled properly
 unsigned	Com_BlockChecksum( const void *buffer, int length );
 char		*Com_MD5File(const char *filename, int length, const char *prefix, int prefix_len);
+// uZu create and returns the MD5 hash of a string
+char		*Com_MD5String(const char *string, const char *prefix, int prefix_len);
+// !uZu
 int			Com_Filter(char *filter, char *name, int casesensitive);
 int			Com_FilterPath(char *filter, char *name, int casesensitive);
 int			Com_RealTime(qtime_t *qtime);
Index: code/client/cl_main.c
===================================================================
--- code/client/cl_main.c	(révision 1803)
+++ code/client/cl_main.c	(copie de travail)
@@ -1606,7 +1606,14 @@
 void CL_Rcon_f( void ) {
 	char	message[MAX_RCON_MESSAGE];
 	netadr_t	to;
+	// uZu - to know if a server has MD5 support
+	const char	*serverInfo;
+	char		challenge_str[12];
 
+	serverInfo = cl.gameState.stringData
+		+ cl.gameState.stringOffsets[ CS_SERVERINFO ];
+	// !uZu
+
 	if ( !rcon_client_password->string ) {
 		Com_Printf ("You must set 'rconpassword' before\n"
 					"issuing an rcon command.\n");
@@ -1621,7 +1628,13 @@
 
 	Q_strcat (message, MAX_RCON_MESSAGE, "rcon ");
 
-	Q_strcat (message, MAX_RCON_MESSAGE, rcon_client_password->string);
+	// uZu - if server has MD5 support, send the MD5 hash of the
+	// current challenge+password instead of the clear-text password
+	Com_sprintf( challenge_str, sizeof(challenge_str), "%d", clc.challenge );
+	Q_strcat (message, MAX_RCON_MESSAGE, strlen(Info_ValueForKey(serverInfo, "sv_MD5")) ?
+		Com_MD5String(rcon_client_password->string, challenge_str, sizeof(challenge_str)) :
+		rcon_client_password->string);
+	// !uZu
 	Q_strcat (message, MAX_RCON_MESSAGE, " ");
 
 	// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
Index: Makefile
===================================================================
--- Makefile	(révision 1803)
+++ Makefile	(copie de travail)
@@ -1634,6 +1634,7 @@
   $(B)/ded/cvar.o \
   $(B)/ded/files.o \
   $(B)/ded/md4.o \
+  $(B)/ded/md5.o \
   $(B)/ded/msg.o \
   $(B)/ded/net_chan.o \
   $(B)/ded/net_ip.o \
_______________________________________________
ioquake3 mailing list
ioquake3@lists.ioquake.org
http://lists.ioquake.org/listinfo.cgi/ioquake3-ioquake.org
By sending this message I agree to love ioquake3 and libsdl.

Reply via email to