Home Made Zero trust Security step 2

If you read my old post about how to create a simple program that can manage Windows Firewall to open ports with a simple udp request you surely got disappointed by the complete lack of security in the request. That program was no more than a mere proof of concept to understand if I can manage windows firewall programmatically in .NET Core.

The absolute critical problem in that program is that, UDP request to open a Tcp port is sent in clear text.

Basically the protocol is, a client C send to the server S a UDP packet in a specific port with a secret key, the server S check if the secret is correct and opens a corresponding TCP port, associated by UDP port in configuration, for requesting IP only and for a predetermined period of time.

You can easily spot the problem: the UDP packet was sent in clear text, everyone that intercept the communication will be able to open port because the secret is sent in clear text.

We have obvious solution to the problem, the most simple one is using the shared secret password to derive a symmetric cryptographic key to encrypt the message. This is far from being perfect, but it is a further step towards a more secure solution.

Since reusing the very same cryptographic key multiple time is not encouraged (even if using a different Initialization Vector solves the problem), a special class called PassdowrdDeriveBytes can be used to derive a sequence of bytes from a password, using a salt and it is cryptographically secure.

public static ICryptoTransform GetEncryptorFromPassword(
    this Aes aes,
    string password,
    byte[] salt)
{
    using (var pdb = new PasswordDeriveBytes(password, salt))
    {
        var key = pdb.GetBytes(32);
        var IV = pdb.GetBytes(16);
        return aes.CreateEncryptor(key, IV);
    }
}

The salt is a sequence of bytes to be used only once, to avoid generating the very same key each time you sent a message. You can use another approach, where you use the very same key and each time you change the Initialization vector, but using the salt to generate a unique Key and IV is probably a better method.

Given this brief introduction we can create a function to generate a random salt to be used for each message.

public static byte[] GenerateRandomSalt()
{
    using (var csp = new RNGCryptoServiceProvider())
    {
        byte[] salt = new byte[saltSize];
        csp.GetBytes(salt);
        return salt;
    }
}

Even for this simple method, it is important to use a random number generator that is cryptographically secure, such as RNGCryptoServiceProvider.  Armed with these two functions we can create a method to encrypt a generic stream of bytes.

public static Byte[] SimmetricEncrypt(string password, byte[] salt, byte[] data)
{
    using (var aes = Aes.Create())
    using (var encryptor = aes.GetEncryptorFromPassword(password, salt))
    using (MemoryStream msEncrypt = new MemoryStream())
    {
        using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
        {
            csEncrypt.Write(data, 0, data.Length);
        }

        // important, dispose CryptoStream before accessing the array
        return msEncrypt.ToArray();
    }
}

As you can see the shared password and the salt are needed and clearly the sequence of byte to encrypt. Encrypted message can be decrypted by a corresponding method that basically accepts the very same set of parameters.

public static Byte[] SimmetricDecrypt(string password, byte[] salt, byte[] data)
{
    try
    {
        using (var aes = Aes.Create())
        using (var encryptor = aes.GetDecryptorFromPassword(password, salt))
        using (MemoryStream msDecrypt = new MemoryStream())
        {
            using (MemoryStream msOriginalData = new MemoryStream(data))
            using (CryptoStream csDecrypt = new CryptoStream(msOriginalData, encryptor, CryptoStreamMode.Read))
            {
                csDecrypt.CopyTo(msDecrypt);
            }

            // important, dispose CryptoStream before accessing the array
            return msDecrypt.ToArray();
        }
    }
    catch (CryptographicException cex)
    {
        Log.Error(cex, "Error decrypting message");
        //Do not disclose anything to the caller.
        throw new SecurityException("Error in decrypting");
    }
}

Code is really simple to read, the only special care is I’ve intercepted each CryptographicException that the code can raise (such as bad password) and I rethrow a generic Security Exception with no any information. The aim is avoiding to give to the caller any possible clue on what went wrong.

Armed with these two simple functions, we can change communication protocol between client and server, using the shared key to encrypt the request message, so anyone that intercepts the message cannot understand what is contained inside.

To avoid reply attack, were an attacker simple retransmit the very same intercepted UDP packet, content of encrypted packet is a simple class that contains three properties

        /// <summary>
        /// This is the port we want to open
        /// </summary>
        public Int32 PortToOpen { get; private set; }

        /// <summary>
        /// This is the end of opening Date, remember that the
        /// server could leave the port opened for a lesser time
        /// if needed.
        /// </summary>
        public DateTime EndOpeningDate { get; private set; }

        /// <summary>
        /// Ip address to scope port opening to.
        /// </summary>
        public String IpAddress { get; private set; }

Now an attacker can reply an intercepted request, but the net result is to reapply the very same request, opening a port for a required IpAddress. Since the message is encrypted he/she cannot read what is inside the message, nor they can alter it.

This solution is more secure and starts to be almost production ready.

As usual the code is on GitHub (as today encryption is still on a feature branch) https://github.com/alkampfergit/StupidFirewallManager 

Gian Maria.

Home Made zero trust security?

I have a small office with some computers and servers and since I’m a fan of Zero Trust Security, I have firewall enabled even in local network. I’m especially concerned about my primary workstation, a Windows Machine where I have explicitly created firewall rules to block EVERY packet from another machine of the network.

I have backups, I have antivirus, but that machine is important and I do not want it to be compromised, working with a rule that block every contact from external code is nice and make it secure, but sometimes it is inconvenient.

Actually Zero Trust security can be complex, but for small offices it could be implemented with home made tools. In my scenario I’m pretty annoyed because there are some situation where I want to access my home network in VPN and then use my workstation in RDP, but since every port is closed, no RDP for me.

The alternative is to left RDP port opened always for all local machines, or for VPN ip subnet, but it is not a thing that I like. What about someone cracking my VPN? Once he is in VPN he can try to attack my RDP port, something that I want to prevent.

To solve this problem I’ve authored a really stupid program to manage hardening of my machine: https://github.com/alkampfergit/StupidFirewallManager This is a proof of concept, but basically it works with a simple concept: if I want to conditionally open TCP port based on a secret, I created a program that is listening on some UDP ports and if it receives a message with a specific secret it will open related TCP port, only for caller IP and only for a certain amount of time.

Here is a configuration:

{
  "Rules" : [
    {
      "Name": "Rdp",
      "UdpPort": 23457,
      "TcpPort": 3389,
      "Secret": "this_is_a_secret"
    }
  ]
}

This means that I want a rule called RDP that will listen on UDP port 23457 and when some other computer sends single UDP packet on that port with the expected secret, it will open 3389 TCP port for that IP for a couple of hours.

You can find the code on the above project, but you need to pay attention, because as soon as the program starts, it will add some rules to close all ports. Please be sure to use this POC only on computer where you have physical access.

image

Figure 1: All ports blocked upon program execution

In Figure 1  you can see that the tool added two rules to deny access to every ports that is not in the controlled range. This basically left only UDP port 23457 opened, and every TCP port except 3389 explicitly closed.

If I try to connect to my machine from another machine in my local network with RDP, I got no response.

image

Figure 2: Cannot access to my workstation with RDP

This happens because the tool explicitly deleted all rules about port 3389 and deny access to every other port. If I want to connect to that machine I need to fire up client and ask for port to open

image

Figure 3: Client program used to ask for port opening

Now if you got correctly both port and secret, a new rule was created.

image

Figure 4: New rule created upon correctly matching port and secret

The name of the rule is unique (it ends with a GUID) and contains in the name the expiration date in form of yyyyMMddHHmm, a timer in server part of the program check firewall rules each minutes to remove all of the rules that matches this pattern and remove them when they are expired.

image

Figure 5: New rule is created scoped for only the computer that sent correct UDP packet with correct secret.

If you look at the new rule it has a scope limited to caller computer

This simple POC allows me to remotely manage my firewall, opening only pre-determined port, for a predetermined amount of time, only for specific ip that knows a specific secret.

This is really only nothing more than a proof of concept, but if you want to apply Zero Trust Security in your environment and you have no commercial or enterprise ready solution, learning on how to manage local firewalls with your code is a good starting point to strongly limit the surface of attack on your local network. These kind of techniques strongly limit lateral movement on your network.

Gian Maria.