Squid bugs

David Fifield <david@bamsoftware.com>

Digest nonces leak heap addresses

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.