From 32ddb11f522d9899bacb6ba829bdb0ae2e86767b Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 4 Feb 2017 19:11:53 -0500 Subject: [PATCH] Fix major security flaw in multi-user domain --- ShiftOS.Objects/Save.cs | 1 + ShiftOS.Server/Program.cs | 103 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/ShiftOS.Objects/Save.cs b/ShiftOS.Objects/Save.cs index c7fe43c..6231875 100644 --- a/ShiftOS.Objects/Save.cs +++ b/ShiftOS.Objects/Save.cs @@ -22,6 +22,7 @@ namespace ShiftOS.Objects public int Revision { get; set; } public string Password { get; set; } + public bool PasswordHashed { get; set; } public string SystemName { get; set; } private dynamic _settings = new SettingsObject(); diff --git a/ShiftOS.Server/Program.cs b/ShiftOS.Server/Program.cs index 9399859..ee35e16 100644 --- a/ShiftOS.Server/Program.cs +++ b/ShiftOS.Server/Program.cs @@ -33,6 +33,7 @@ using System.IO; using Newtonsoft.Json; using System.Net; using System.Net.Sockets; +using System.Security.Cryptography; namespace ShiftOS.Server { @@ -104,6 +105,13 @@ namespace ShiftOS.Server /// The command-line arguments. public static void Main(string[] args) { + foreach(var save in Directory.GetFiles("saves")) + { + var save_obj = JsonConvert.DeserializeObject(File.ReadAllText(save)); + if (save_obj.PasswordHashed == false) + save_obj.Password = Encryption.Encrypt(save_obj.Password); + } + if (!Directory.Exists("saves")) { Directory.CreateDirectory("saves"); @@ -392,7 +400,9 @@ Contents: { var save = JsonConvert.DeserializeObject(File.ReadAllText(savefile)); - if(save.Username == args["username"].ToString() && save.Password == args["password"].ToString()) + string hashedPass = Encryption.Encrypt(args["password"].ToString()); + + if(save.Username == args["username"].ToString() && save.Password == hashedPass) { server.DispatchTo(new Guid(msg.GUID), new NetObject("mud_savefile", new ServerMessage { @@ -523,7 +533,13 @@ Contents: break; case "mud_save": var sav = JsonConvert.DeserializeObject(msg.Contents); - File.WriteAllText("saves/" + sav.Username + ".save", JsonConvert.SerializeObject(sav, Formatting.Indented)); + if (!sav.PasswordHashed) + { + sav.Password = Encryption.Encrypt(sav.Password.ToString()); + sav.PasswordHashed = true; + } + + File.WriteAllText("saves/" + sav.Username + ".save", JsonConvert.SerializeObject(sav, Formatting.Indented)); server.DispatchTo(new Guid(msg.GUID), new NetObject("auth_failed", new ServerMessage { @@ -541,7 +557,9 @@ Contents: { var save = JsonConvert.DeserializeObject(File.ReadAllText(savefile)); - if (save.Username == args["username"].ToString() && save.Password == args["password"].ToString()) + string hashed = Encryption.Encrypt(args["password"].ToString()); + + if (save.Username == args["username"].ToString() && save.Password == hashed) { server.DispatchTo(new Guid(msg.GUID), new NetObject("mud_savefile", new ServerMessage { @@ -680,6 +698,18 @@ Contents: break; case "download_start": + if (!msg.Contents.StartsWith("shiftnet/")) + { + server.DispatchTo(new Guid(msg.GUID), new NetObject("shiftnet_got", new ServerMessage + { + Name = "shiftnet_file", + GUID = "server", + Contents = (File.Exists("badrequest.md") == true) ? File.ReadAllText("badrequest.md") : @"# Bad request. + +You have sent a bad request to the multi-user domain. Please try again." + })); + } + if (File.Exists(msg.Contents)) { server.DispatchTo(new Guid(msg.GUID), new NetObject("download", new ServerMessage @@ -708,6 +738,20 @@ The page you requested at was not found on this multi-user domain." { surl = surl.Remove(surl.Length - 1, 1); } + if (!surl.StartsWith("shiftnet/")) + { + server.DispatchTo(new Guid(msg.GUID), new NetObject("shiftnet_got", new ServerMessage + { + Name = "shiftnet_file", + GUID = "server", + Contents = (File.Exists("badrequest.md") == true) ? File.ReadAllText("badrequest.md") : @"# Bad request. + +You have sent a bad request to the multi-user domain. Please try again." + })); + + return; + } + if (File.Exists(surl)) { server.DispatchTo(new Guid(msg.GUID), new NetObject("shiftnet_got", new ServerMessage @@ -1184,6 +1228,59 @@ The page you requested at was not found on this multi-user domain." /// public static List chats = new List(); } + + public static class Encryption + { + public static string GetMacAddress() + { + if (!File.Exists("hash.dat")) + File.WriteAllText("hash.dat", Guid.NewGuid().ToString()); + + return File.ReadAllText("hash.dat"); + + } + + + // This constant string is used as a "salt" value for the PasswordDeriveBytes function calls. + // This size of the IV (in bytes) must = (keysize / 8). Default keysize is 256, so the IV must be + // 32 bytes long. Using a 16 character string here gives us 32 bytes when converted to a byte array. + private static readonly byte[] initVectorBytes = Encoding.ASCII.GetBytes("tu89geji340t89u2"); + + // This constant is used to determine the keysize of the encryption algorithm. + private const int keysize = 256; + + /// + /// Encrypt a string. + /// + /// Raw string to encrypt. + /// The encrypted string. + public static string Encrypt(string plainText) + { + byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText); + using (PasswordDeriveBytes password = new PasswordDeriveBytes(GetMacAddress(), null)) + { + byte[] keyBytes = password.GetBytes(keysize / 8); + using (RijndaelManaged symmetricKey = new RijndaelManaged()) + { + symmetricKey.Mode = CipherMode.CBC; + using (ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes)) + { + using (MemoryStream memoryStream = new MemoryStream()) + { + using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) + { + cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); + cryptoStream.FlushFinalBlock(); + byte[] cipherTextBytes = memoryStream.ToArray(); + return Convert.ToBase64String(cipherTextBytes); + } + } + } + } + } + } + + } } // Commenting by Carver