Skip to content

Commit

Permalink
Merge pull request #575 from rdebath/patch-passfix
Browse files Browse the repository at this point in the history
Fix /pass bypass security issue.
  • Loading branch information
UnknownShadow200 committed Jan 31, 2021
2 parents 65b8998 + c9dacf3 commit eeb1baa
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 6 deletions.
82 changes: 77 additions & 5 deletions MCGalaxy/Commands/Moderation/CmdPass.cs
Expand Up @@ -21,6 +21,10 @@
using System.IO;
using System.Security.Cryptography;
using System.Text;
#if !DISABLE_OLD_PASSWORDFILE
using System.Collections.Generic;
using System.Linq;
#endif

namespace MCGalaxy.Commands.Moderation {
public sealed class CmdPass : Command2 {
Expand Down Expand Up @@ -66,6 +70,17 @@ public sealed class CmdPass : Command2 {
p.Message("You are now &averified %Sand have &aaccess to admin commands and features.");
p.verifiedPass = true;
p.Unverified = false;

#if !DISABLE_OLD_PASSWORDFILE
string oldpath = HashPath(p.name, false);
string path = HashPath(p.name, true);
if (oldpath != path) {
File.Delete(oldpath);
byte[] computed = ComputeHash(p.name, password);
File.WriteAllBytes(path, computed);
p.Message("Your password was &areset for the new format");
}
#endif
} else {
p.passtries++;
p.Message("%WWrong Password. %SRemember your password is %Wcase sensitive.");
Expand Down Expand Up @@ -108,33 +123,53 @@ public sealed class CmdPass : Command2 {
}
}

static string HashPath(string name) { return "extra/passwords/" + name + ".dat"; }
static bool HasPassword(string name) { return File.Exists(HashPath(name)); }

static byte[] ComputeHash(string name, string pass) {
#if !DISABLE_OLD_PASSWORDFILE
static byte[] OldComputeHash(string name, string pass) {
// Pointless, but kept for backwards compatibility
pass = pass.Replace("<", "(");
pass = pass.Replace(">", ")");

MD5 hash = MD5.Create();
byte[] nameB = hash.ComputeHash(Encoding.ASCII.GetBytes(name));
// This line means that non-ASCII characters in passwords are
// all encoded as the "?" character.
byte[] dataB = hash.ComputeHash(Encoding.ASCII.GetBytes(pass));

byte[] result = new byte[nameB.Length + dataB.Length];
Array.Copy(nameB, 0, result, 0, nameB.Length);
Array.Copy(dataB, 0, result, nameB.Length, dataB.Length);
return hash.ComputeHash(result);
}
#endif

static byte[] ComputeHash(string name, string pass) {
// The constant string added to the username salt is to mitigate
// rainbox tables. We should really have a unique salt for each
// user, but this is close enough.
return SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes("0bec662b-416f-450c-8f50-664fd4a41d49" + name.ToLower() + " " + pass));
}

static bool CheckHash(string name, string pass) {
byte[] stored = File.ReadAllBytes(HashPath(name));
byte[] computed = ComputeHash(name, pass);

#if !DISABLE_OLD_PASSWORDFILE
// Old style MD5 passwords.
byte[] oldcomputed = OldComputeHash(name, pass);
if (stored.Length == oldcomputed.Length) {
bool oldokay = true;
for (int i = 0; i < stored.Length; i++) {
if (stored[i] != oldcomputed[i]) oldokay = false;
}
if (oldokay) return true;
}

// Old passwords stored UTF8 string instead of just the raw 16 byte hashes
// We need to support both since this behaviour was accidentally changed
if (stored.Length != computed.Length) {
return Encoding.UTF8.GetString(stored) == Encoding.UTF8.GetString(computed);
}
#endif
for (int i = 0; i < stored.Length; i++) {
if (stored[i] != computed[i]) return false;
}
Expand All @@ -150,5 +185,42 @@ public sealed class CmdPass : Command2 {
p.Message("%HIf you are an admin, use this command to verify your login.");
p.Message("%H You will need to be verified to be able to use commands.");
}

public static bool HasPassword(string name) { return File.Exists(HashPath(name)); }

#if DISABLE_OLD_PASSWORDFILE
static string HashPath(string name) { return "extra/passwords/" + name.ToLower() + ".pwd"; }
#else
static string HashPath(string name, bool ForUpdate = false)
{
string PassName = "extra/passwords/" + name.ToLower() + ".pwd";
if (ForUpdate || File.Exists(PassName))
return PassName;

string OldName = "extra/passwords/" + name + ".dat";
if (File.Exists(PassName))
return PassName;

string directory = Path.GetDirectoryName(OldName);
IEnumerable<string> foundFiles = EnumerateFilesCI(directory, OldName);

if (foundFiles.Count() < 1)
return PassName;

return foundFiles.First();
}

private static IEnumerable<string> EnumerateFilesCI(string directory, string pathAndFileName)
{
foreach (string file in Directory.EnumerateFiles(directory))
{
if (String.Equals(file, pathAndFileName, StringComparison.OrdinalIgnoreCase) )
{
yield return file;
}
}
}
#endif

}
}
2 changes: 1 addition & 1 deletion MCGalaxy/Player/Player.cs
Expand Up @@ -357,7 +357,7 @@ public partial class Player : Entity, IDisposable {
Unverified = Server.Config.verifyadmins && Rank >= Server.Config.VerifyAdminsRank;
if (!Unverified) return;

if (!File.Exists("extra/passwords/" + name + ".dat")) {
if (!Commands.Moderation.CmdPass.HasPassword(name)) {
Message("%WPlease set your admin verification password with %T/SetPass [password]!");
} else {
Message("%WPlease complete admin verification with %T/Pass [password]!");
Expand Down

0 comments on commit eeb1baa

Please sign in to comment.