David Fifield <david@bamsoftware.com>
I initially reported this bug in email to squid-bugs@lists.squid-cache.org. The people there advised me to create a pull request, not mentioning any security issues, which I did as #491. After being merged, security update advisory SQUID-2019:11 was announced. The corresponding CVE is CVE-2019-18679. Later, I made pull request #549 to remove the possibility of recovering a pointer by reversing the hash.
I am looking at commit 50921dfc2aba018c57c522e7281069e9c2632e49.
authenticateDigestNonceNew
and authDigestNonceEncode
create nonces by
base64-encoding the raw bytes of a _digest_nonce_data
struct. The struct
contains a pointer to itself, which could be useful as part of an
exploit chain to help bypass ASLR, for example. A comment in the code
says "our nonce looks like base64(H(timestamp,pointertohash,randomdata))"
(https://github.com/squid-cache/squid/blob/50921dfc2aba018c57c522e7281069e9c2632e49/src/auth/digest/Config.cc#L155),
but in fact there is no hashing step and the bytes are returned to the
client directly; it's actually
base64(timestamp||padding||pointertohash||randomdata||padding).
As a demonstration, make a squid.conf file containing the following:
auth_param digest program src/auth/digest/file/digest_file_auth passwordfile auth_param digest children 20 startup=0 idle=1 auth_param digest realm Squid proxy-caching web server auth_param digest nonce_garbage_interval 5 minutes auth_param digest nonce_max_duration 30 minutes auth_param digest nonce_max_count 50 acl req_digest proxy_auth REQUIRED http_access deny !req_digest http_access allow req_digest http_access deny all
Run squid as:
src/squid -N -f squid.conf
Grab a nonce:
$ echo -n $'GET http://example.com/ HTTP/1.0\r\n\r\n' | nc 127.0.0.1 3128 | grep ^Proxy-Auth Proxy-Authenticate: Digest realm="Squid proxy-caching web server", nonce="FMVHXQAAAACQYmWDbVUAAOVX2lAAAAAA", qop="auth", stale=false
This particular nonce decodes to hexadecimal 14c5475d00000000906265836d550000e557da5000000000. The attached pull-nonces script grabs nonces and recovers the original structure, including the padding between fields:
$ python3 pull-nonces creationtime=5d47c6a4 pad1=00000000 self=0000556d83664e00 randomdata=b0ffca0f pad2=00000000 creationtime=5d47c6a4 pad1=00000000 self=0000556d836635d0 randomdata=fe987b40 pad2=00000000 creationtime=5d47c6a4 pad1=00000000 self=0000556d83664e50 randomdata=be106ebf pad2=00000000 creationtime=5d47c6a4 pad1=00000000 self=0000556d83664d30 randomdata=c134e62a pad2=00000000 creationtime=5d47c6a4 pad1=00000000 self=0000556d83664be0 randomdata=16fd8e71 pad2=00000000 creationtime=5d47c6a4 pad1=00000000 self=0000556d83abcda0 randomdata=fe7639fb pad2=00000000 creationtime=5d47c6a4 pad1=00000000 self=0000556d83abce70 randomdata=5b13c38f pad2=00000000 creationtime=5d47c6a4 pad1=00000000 self=0000556d83abd300 randomdata=756902e9 pad2=00000000 creationtime=5d47c6a4 pad1=00000000 self=0000556d83abd260 randomdata=d90e62ae pad2=00000000 creationtime=5d47c6a4 pad1=00000000 self=0000556d83abd420 randomdata=5ce20f72 pad2=00000000
Besides disclosing pointers, revealing randomdata probably makes it
possible to recover the internal state of the random number generator,
because it is only seeded with a 32-bit timestamp.
https://github.com/squid-cache/squid/blob/50921dfc2aba018c57c522e7281069e9c2632e49/src/auth/digest/Config.cc#L162
It looks like authDigestNonceEncode
should hash its input string before
base64 encoding. It would also be a good idea to include only the fields
of _digest_nonce_data
, not the raw bytes of the struct, which could
possibly contain unrelated heap data in padding.
Here is a draft patch to hash the noncedata before encoding it and returning it to the client.
There's also a second patch to use hex encoding in place of base64 encoding. Note that the hex-encoded hashed nonces are the same length as the base64-encoded unhashed nonces.
I will suggest an alternate implementation that would require some
refactoring but would be simpler and more secure: just use completely
random nonces. I.e., hex-encode 16 bytes from /dev/urandom and make that
the nonce. The reason RFC 2617 includes a timestamp, and hashes an ETag
and a private random value, is to enable an implementation that doesn't
require storing all valid nonces in memory. But squid already remembers
all valid nonces in digest_nonce_cache
, so there's no reason for the
nonce key to contain any meaningful information--all you need is for
nonces not to repeat, and a random nonce gives you that. Even a hashed
nonce doesn't prevent recovering a preimage--it only takes an offline
attack over the bits of entropy in the creationtime
(few), randomdata
(few), and self
pointer (depends on ASLR). Random nonces would prevent
that attack.