dotnet / dotnet-api-docs

.NET API reference documentation (.NET 5+, .NET Core, .NET Framework)
https://docs.microsoft.com/dotnet/api/
Other
685 stars 1.52k forks source link

Timing vulnerability in sample code? #9856

Open Danghor opened 2 months ago

Danghor commented 2 months ago

https://github.com/dotnet/dotnet-api-docs/blob/2d0e5e61b8635b57bdb26b42d879382252edfc62/snippets/csharp/System.Security.Cryptography/HMACSHA256/Overview/hmacsha256.cs#L110C17-L116C18

storedHash is directly taken from the source file, i.e. the potentially manipulated file. Would this loop not cause a timing vulnerability? The attacker could guess the first byte of the MAC, send it together with the manipulated file to the server and observe for which guess the server takes slightly longer to process. This guess can be assumed to be the correct first ~key~ MAC byte. Then the attacker can proceed to the next byte etc., essentially brute forcing the MAC one byte after another.

I think creating a hash value of both the expected and the actual MAC value and then comparing the hash value could potentially solve this.

Am I missing something?

dotnet-issue-labeler[bot] commented 2 months ago

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

colejohnson66 commented 2 months ago

The timing attack you are describing is one where the first failed comparison breaks out of the loop:

public static bool CheckHash(byte[] fromUser, byte[] expected)
{
    // validation elided
    for (int i = 0; i < fromUser.Length; i++)
    {
        if (fromUser[i] != expected[i])
            return false;
    }
    return true;
}

If you look at that snipped, you can see that it doesn't do that. It instead sets a flag, then continues to check each and every byte before exiting the loop.

https://github.com/dotnet/dotnet-api-docs/blob/2d0e5e61b8635b57bdb26b42d879382252edfc62/snippets/csharp/System.Security.Cryptography/HMACSHA256/Overview/hmacsha256.cs#L110-L128

This is the proper way to do a hash comparison short of constant-time algorithms.

Regardless, documentation examples are not supposed to be industry-standards. After all, they're examples.

Danghor commented 2 months ago

I see, the loop indeed compares all the bytes of the hashes. However, when only guessing the first byte, wouldn't the function run slightly faster for the correct guess, since one assignment is omitted (while the other bytes of the MAC stay the same)?

colejohnson66 commented 2 months ago

Yes. A more "constant time" algorithm would look something like:

for (int i = 0; i < storedHash.Length; i++)
    err |= computedHash[i] != storedHash[i];

Then the value is updated for each byte, not just when it's wrong.

ManickaP commented 2 months ago

@dotnet/area-system-security

bartonjs commented 2 months ago

the correct thing for modern code would be to call CryptographicOperations.FixedTimeEquals, but I don't know that we can update the sample to that; so it might have to just get the inlined version.

colejohnson66 commented 2 months ago

Why would the sample not be able to use FixedTimeEquals?

bartonjs commented 2 months ago

It's not available for .NET Framework / .NET Standard 2.0. So it would require bifurcating the sample, which is.... frustrating :smile: