Find the security bug in this code
This code try very hard to ensure that the secret key provided to it is eradicated after it is properly saved.
This is because we try to reduce the attack vector for keeping the encryption key in memory.
However, there are at least two different ways that this code is failing in what it is trying to do. Can you find them?
For that matter, how much sense does it make to even attempt what it is doing?
Comments
A GC between
ReadToEnd
andConvert.Frombase64String
might causebase64
to be copied, before becoming 'fixed'. Similar problem withkey
. But the HTTP request body contains this data anyway...Also, StreamReader has a buffer from which you need to remove the key, somehow.
I might be completely wrong here, but can you know that a character in a c# string (utf-16) is the same size as a
char
? if so, the ZeroMemory method might not zero out all the base64 encoded string.ServerStore shall definitely keep the key in memory at least until Commit() is called. If it has some sort of caching, it is going to live a long life in it.
Andreas. ZeroMemoery is called with
sizeof(char)
, so that should be fine.If key.Length != 256 / 8 is false, an exception is thrown without wiping the key.
Full compilation of security risks:
As @Henning noted, when the exception is thrown for invalid key length, the key exists in both
base64
andkey
, and has not been securely zero'd. Solution: Add a try/finally to ensure the memory is zero'd.As @Alex noted,
base64
andkey
are not fixed, so they could be moved between creation and zero'ing, at which point the key is still in memory in an unknown location. Solution: createbase64
andkey
with known sizes and fix them in memory before loading them with data. A custom version ofFromBase64String()
may be required to do this properly.If there is an exception in
AllocateOperationContext()
,OpenWriteTransaction()
, orPutSecretKey()
, then the key still exists inkey
, and has not been securely zero'd. Solution: Add a try/finally to ensure the memory is zero'd.As @Mircea noted,
StreamReader
will buffer the data as it passes through. Solution: You cannot zero this memory, so avoid usingStreamReader
entirely.Securely zeroing the rest does not eliminate all security risk:
HttpContext.Request.InputStream
uses (through several layers of indirection)HttpRawUploadedContent
, which buffers the entire data portion of the HTTP request. Solution: None. This cannot be securely zero'd or avoided.None of this matters if the request is made without HTTPS. I know Raven recommends and encourages the use of HTTPS, but if this request is done via HTTP, then the data will be exposed to the wire during transport. Solution: Reject this method call if it was not done via HTTPS. This requirement may be handled in the code or in configuration.
Basic compile error:
HttpContext.Request.Body
does not exist, it should beHttpContext.Request.InputStream
Stuart, Regarding the compilation error, I think it might be different versions.
Spot on all the rest.
PutSecretKey also needs to be inside a try/finally. And there is no need to call ZeroMemory inside the transactional statement.
I am more interested in Oren's last question ... "how much sense does it make to even attempt what it is doing?"
Comment preview