Background:
This problem only occurs when we are handling an intercepted request on a TCP connection destined to a machine not listed on the Host header domains IP address list. To fix this bug properly we are going to have to wrap untrusted requests in a CONNECT request and pass it to peers. Who are also going to have to be updated to un-wrap the CONNECT and treat the internal request as intercepted traffic themselves. This is turning into a big project all on its own. In order to prevent Squid-3.2 taking another year or two, we have decided to defer the fix to a later Squid series.

The workaround:

This patch re-enables Squid peer selection algorithms for intercepted
traffic which has failed Host header verification.

When host verification fails Squid will use, in order of preference:
 * an already PINNED server connection
 * the client ORIGINAL_DST details
 * cache_peer as chosen by selection algorithms

NOTE: whenever DIRECT is selected by routing algorithms the
      ORIGINAL_DST is used instead.

Peer selection results are updated to display PINNED and
ORIGINAL_DST alongside DIRECT and cache_peer.


SECURITY NOTE:

  At this point Squid will pass the request to cache_peer using the
  non-trusted Host header in their URLs. Meaning that the peers
  may still be poisoned by CVE-2009-0801 attacks. Only the initial
  intercepting proxy is protected.

  Full protection against CVE-2009-0801 can be enjoyed by building
  Squid with the -DSTRICT_HOST_VERIFY compile-time flag. This will
  make the peers unreachable for intercepted traffic where the
  Host verification has failed.

PS. The notion was floated of re-writing the Host header or URL domain to the original destination IP and passing that through. However that method is known to break virtual hosted websites, which unfortunately do overlap a great deal with the hosting
 companies use of Geo-DNS on their CDNs.


Amos
Bug 3478: Allow peer selection

This re-enables Squid peer selection algorithms for intercepted
traffic which has failed Host header verification.

When host verification fails Squid will use, in order of preference:
 * an already PINNED server connection
 * the client ORIGINAL_DST details
 * cache_peer as chosen by selection algorithms

NOTE: whenever DIRECT is selected by routing algorithms the
      ORIGINAL_DST is used instead.

Peer selection results are updated to display PINNED and
ORIGINAL_DST alongside DIRECT and cache_peer.


SECURITY NOTE:

  At this point Squid will pass the request to cache_peer using the
  non-trusted Host header in their URLs. Meaning that the peers
  may still be poisoned by CVE-2009-0801 attacks. Only the initial
  intercepting proxy is protected.

  Full protection against CVE-2009-0801 can be enjoyed by building
  Squid with the -DSTRICT_HOST_VERIFY compile-time flag. This will
  make the peers unreachable for intercepted traffic where the
  Host verification has failed.


=== modified file 'src/cf.data.pre'
--- src/cf.data.pre     2012-07-18 16:21:47 +0000
+++ src/cf.data.pre     2012-07-26 14:05:21 +0000
@@ -1922,9 +1922,9 @@
                 * Intercepted traffic which passes verification is handled
                   normally.
        
-               For now it also forces suspicious requests to go DIRECT to the
-               original destination, overriding client_dst_passthru for
-               intercepted requests which fail Host: verification.
+                * Intercepted requests which fail verification are sent
+                  to the client original destination instead of DIRECT.
+                  This overrides client_dst_passthru.
        
                For now suspicious intercepted CONNECT requests are always
                responded to with an HTTP 409 (Conflict) error page.
@@ -1939,13 +1939,26 @@
        directly to the original client destination IP or seek a faster
        source.
        
-       This option (on by default) prevents cache_peer and alternative DNS
-       entries being used on intercepted traffic. Both of which lead to
-       the security vulnerability outlined below.
-       
-       SECURITY WARNING:
-       
-       This directive should only be disabled if cache_peer are required.
+       Using alternative server DNS entries can provide potentially faster
+       connectivity with a range of failure recovery options.
+       But can also lead to connectivity trouble when the client and
+       server are attempting stateful interactions unaware of the proxy.
+       
+       This option (on by default) prevents alternative DNS entries being
+       located to send intercepted traffic DIRECT to an origin server.
+       The clients original destination IP and port will be used instead.
+       
+       To allow Squid to locate potentially faster destinations or to 
+       recover from connection failures to the clients original destination
+       set this to OFF.
+       
+       Regardless of this option setting, when dealing with intercepted
+       traffic Squid will verify the Host: header and any traffic which
+       fails Host verification will be treated as if this option were ON.
+       
+       see host_verify_strict for details on the verification process.
+       
+       SECURITY NOTE:
        
        As described in CVE-2009-0801 when the Host: header alone is used
        to determine the destination of a request it becomes trivial for
@@ -1957,7 +1970,7 @@
        sandbox only verifies that the applet tries to contact the same IP
        as from where it was loaded at the IP level. The Host: header may
        be different from the connected IP and approved origin.
-
+       
 DOC_END
 
 COMMENT_START

=== modified file 'src/forward.cc'
--- src/forward.cc      2012-07-23 07:02:06 +0000
+++ src/forward.cc      2012-07-27 08:08:26 +0000
@@ -134,12 +134,15 @@
     const bool useOriginalDst = Config.onoff.client_dst_passthru || (request 
&& !request->flags.hostVerified);
     if (isIntercepted && useOriginalDst) {
         selectPeerForIntercepted();
-        // destination "found". continue with the forwarding.
+#if STRICT_ORIGINAL_DST
+        // 3.2 does not suppro re-wrapping inside CONNECT.
+        // our only alternative is to fake destination "found" and continue 
with the forwarding.
         startConnectionOrFail();
-    } else {
-        // do full route options selection
-        peerSelect(&serverDestinations, request, entry, 
fwdPeerSelectionCompleteWrapper, this);
+        return;
+#endif
     }
+    // do full route options selection
+    peerSelect(&serverDestinations, request, entry, 
fwdPeerSelectionCompleteWrapper, this);
 }
 
 /// bypasses peerSelect() when dealing with intercepted requests
@@ -151,19 +154,22 @@
     if (ConnStateData *client = request->pinnedConnection())
         p = client->validatePinnedConnection(request, NULL);
 
-    if (p != NULL && Comm::IsConnOpen(p)) {
-        debugs(17, 3, HERE << "reusing a pinned conn: " << *p);
+    if (Comm::IsConnOpen(p)) {
         /* duplicate peerSelectPinned() effects */
         p->peerType = PINNED;
         entry->ping_status = PING_DONE;     /* Skip ICP */
-    } else {
-        p = new Comm::Connection();
-        p->peerType = ORIGINAL_DST;
-        p->remote = clientConn->local;
-        getOutgoingAddress(request, p);
-        debugs(17, 3, HERE << "opening a new conn: " << *p);
+
+        debugs(17, 3, HERE << "reusing a pinned conn: " << *p);
+        serverDestinations.push_back(p);
     }
 
+    // use client original destination as second preferred choice
+    p = new Comm::Connection();
+    p->peerType = ORIGINAL_DST;
+    p->remote = clientConn->local;
+    getOutgoingAddress(request, p);
+
+    debugs(17, 3, HERE << "using client original destination: " << *p);
     serverDestinations.push_back(p);
 }
 

=== modified file 'src/peer_select.cc'
--- src/peer_select.cc  2012-07-23 07:02:06 +0000
+++ src/peer_select.cc  2012-07-27 03:50:49 +0000
@@ -222,6 +222,33 @@
 {
     FwdServer *fs = psstate->servers;
 
+    // Bug 3243: CVE 2009-0801
+    // Bypass of browser same-origin access control in intercepted 
communication
+    // To resolve this we must force DIRECT and only to the original client 
destination.
+    const HttpRequest *req = psstate->request;
+    const bool isIntercepted = !req->flags.redirected &&
+                               (req->flags.intercepted || 
req->flags.spoof_client_ip);
+    const bool useOriginalDst = Config.onoff.client_dst_passthru || 
!req->flags.hostVerified;
+    const bool choseDirect = fs && fs->code == HIER_DIRECT;
+    if (isIntercepted && useOriginalDst && choseDirect) {
+        // construct a "result" adding the ORIGINAL_DST to the set instead of 
DIRECT
+        Comm::ConnectionPointer p = new Comm::Connection();
+        p->remote = req->clientConnectionManager->clientConnection->local;
+        p->peerType = fs->code;
+        p->setPeer(fs->_peer);
+
+        // check for a configured outgoing address for this destination...
+        getOutgoingAddress(psstate->request, p);
+        psstate->paths->push_back(p);
+
+        // clear the used fs and continue
+        psstate->servers = fs->next;
+        cbdataReferenceDone(fs->_peer);
+        memFree(fs, MEM_FWD_SERVER);
+        peerSelectDnsPaths(psstate);
+        return;
+    }
+
     // convert the list of FwdServer destinations into destinations IP 
addresses
     if (fs && psstate->paths->size() < (unsigned int)Config.forward_max_tries) 
{
         // send the next one off for DNS lookup.
@@ -247,6 +274,10 @@
         for (size_t i = 0; i < psstate->paths->size(); ++i) {
             if ((*psstate->paths)[i]->peerType == HIER_DIRECT)
                 debugs(44, 2, "         DIRECT = " << (*psstate->paths)[i]);
+            else if ((*psstate->paths)[i]->peerType == ORIGINAL_DST)
+                debugs(44, 2, "   ORIGINAL_DST = " << (*psstate->paths)[i]);
+            else if ((*psstate->paths)[i]->peerType == PINNED)
+                debugs(44, 2, "         PINNED = " << (*psstate->paths)[i]);
             else
                 debugs(44, 2, "     cache_peer = " << (*psstate->paths)[i]);
         }

Reply via email to