templates/ERR_CACHE_ACCESS_DENIED \
templates/ERR_CACHE_MGR_ACCESS_DENIED \
templates/ERR_CANNOT_FORWARD \
+ templates/ERR_CONFLICT_HOST \
templates/ERR_CONNECT_FAIL \
templates/ERR_DIR_LISTING \
templates/ERR_DNS_FAIL \
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html><head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title>ERROR: The requested URL could not be retrieved</title>
+<style type="text/css"><!--
+ %l
+
+body
+:lang(fa) { direction: rtl; font-size: 100%; font-family: Tahoma, Roya, sans-serif; float: right; }
+:lang(he) { direction: rtl; }
+ --></style>
+</head><body id=%c>
+<div id="titles">
+<h1>ERROR</h1>
+<h2>The requested URL could not be retrieved</h2>
+</div>
+<hr>
+
+<div id="content">
+<p>The following error was encountered while trying to retrieve the URL: <a href="%U">%U</a></p>
+
+<blockquote id="data">
+<pre>URI Host Conflict</pre>
+</blockquote>
+
+<p>This means the domain name you are trying to access apparently no longer exists on the machine you are requesting it from.</p>
+
+<p>Some possible problems are:</p>
+<ul>
+<li>The domain may have moved very recently. Trying again will resolve that.</li>
+<li>The website may require you to use a local country-based version. Using your ISP provided DNS server(s) should resolve that.</li>
+</ul>
+
+<p>Your cache administrator is <a href="mailto:%w%W">%w</a>.</p>
+<br>
+</div>
+
+<hr>
+<div id="footer">
+<p>Generated %T by %h (%s)</p>
+<!-- %c -->
+</div>
+</body></html>
bool
HttpRequest::cacheable() const
{
+ // Intercepted request with Host: header which cannot be trusted.
+ // Because it failed verification, or someone bypassed the security tests
+ // we cannot cache the reponse for sharing between clients.
+ // TODO: update cache to store for particular clients only (going to same Host: and destination IP)
+ if (!flags.hostVerified && (flags.intercepted || flags.spoof_client_ip))
+ return false;
+
if (protocol == AnyP::PROTO_HTTP)
return httpCachable(method);
DOC_START
Regardless of this option setting, when dealing with intercepted
traffic, Squid always verifies that the destination IP address matches
- the Host header domain or IP (called 'authority form URL'). Squid
- responds with an HTTP 409 (Conflict) error page and logs a security
- warning if there is no match.
-
- When set to ON, Squid verifies that the destination IP address matches
- the Host header for forward-proxy and reverse-proxy traffic as well. For
- those traffic types, Squid also enables the following checks, comparing
- the corresponding Host header and Request-URI components:
-
- * The host names (domain or IP) must be identical,
- but valueless or missing Host header disables all checks.
- For the two host names to match, both must be either IP or FQDN.
-
- * Port numbers must be identical,
- but if a port is missing, the scheme-default port is assumed.
+ the Host header domain or IP (called 'authority form URL').
This enforcement is performed to satisfy a MUST-level requirement in
RFC 2616 section 14.23: "The Host field value MUST represent the naming
authority of the origin server or gateway given by the original URL".
+
+ When set to ON:
+ Squid always responds with an HTTP 409 (Conflict) error
+ page and logs a security warning if there is no match.
+
+ Squid verifies that the destination IP address matches
+ the Host header for forward-proxy and reverse-proxy traffic
+ as well. For those traffic types, Squid also enables the
+ following checks, comparing the corresponding Host header
+ and Request-URI components:
+
+ * The host names (domain or IP) must be identical,
+ but valueless or missing Host header disables all checks.
+ For the two host names to match, both must be either IP
+ or FQDN.
+
+ * Port numbers must be identical, but if a port is missing
+ the scheme-default port is assumed.
+
+
+ When set to OFF (the default):
+ Squid allows suspicious requests to continue but logs a
+ security warning and blocks caching of the response.
+
+ * Forward-proxy traffic is not checked at all.
+
+ * Reverse-proxy traffic is not checked at all.
+
+ * 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.
+
+ For now suspicious intercepted CONNECT requests are always
+ responded to with an HTTP 409 (Conflict) error page.
DOC_END
NAME: client_dst_passthru
void
ClientRequestContext::hostHeaderVerifyFailed(const char *A, const char *B)
{
+ // IP address validation for Host: failed. Admin wants to ignore them.
+ // NP: we do not yet handle CONNECT tunnels well, so ignore for them
+ if (!Config.onoff.hostStrictVerify && http->request->method != METHOD_CONNECT) {
+ debugs(85, 3, "SECURITY ALERT: Host header forgery detected on " << http->getConn()->clientConnection <<
+ " (" << A << " does not match " << B << ") on URL: " << urlCanonical(http->request));
+
+ // NP: it is tempting to use 'flags.nocache' but that is all about READing cache data.
+ // The problems here are about WRITE for new cache content, which means flags.cachable
+ http->request->flags.cachable = 0; // MUST NOT cache (for now)
+ // XXX: when we have updated the cache key to base on raw-IP + URI this cacheable limit can go.
+ http->request->flags.hierarchical = 0; // MUST NOT pass to peers (for now)
+ // XXX: when we have sorted out the best way to relay requests properly to peers this hierarchical limit can go.
+ return;
+ }
+
debugs(85, DBG_IMPORTANT, "SECURITY ALERT: Host header forgery detected on " <<
http->getConn()->clientConnection << " (" << A << " does not match " << B << ")");
debugs(85, DBG_IMPORTANT, "SECURITY ALERT: By user agent: " << http->request->header.getStr(HDR_USER_AGENT));
clientStreamNode *node = (clientStreamNode *)http->client_stream.tail->prev->data;
clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
assert (repContext);
- repContext->setReplyToError(ERR_INVALID_REQ, HTTP_CONFLICT,
+ repContext->setReplyToError(ERR_CONFLICT_HOST, HTTP_CONFLICT,
http->request->method, NULL,
http->getConn()->clientConnection->remote,
http->request,
} else {
// Okay no problem.
debugs(85, 3, HERE << "validate passed.");
+ http->request->flags.hostVerified = 1;
http->doCallouts();
}
safe_free(hostB);
HttpRequestMethod method = request->method;
const wordlist *p = NULL;
+ // intercepted requests MUST NOT (yet) be sent to peers unless verified
+ if (!request->flags.hostVerified && (request->flags.intercepted || request->flags.spoof_client_ip))
+ return 0;
+
/*
* IMS needs a private key, so we can use the hierarchy for IMS only if our
* neighbors support private keys
ERR_INVALID_URL,
ERR_ZERO_SIZE_OBJECT,
ERR_PRECONDITION_FAILED,
+ ERR_CONFLICT_HOST,
/* FTP Errors */
ERR_FTP_DISABLED,
// 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.
- if (Config.onoff.client_dst_passthru && request && !request->flags.redirected &&
- (request->flags.intercepted || request->flags.spoof_client_ip)) {
+ const bool isIntercepted = request && !request->flags.redirected && (request->flags.intercepted || request->flags.spoof_client_ip);
+ const bool useOriginalDst = Config.onoff.client_dst_passthru || (request && !request->flags.hostVerified);
+ if (isIntercepted && useOriginalDst) {
Comm::ConnectionPointer p = new Comm::Connection();
p->remote = clientConn->local;
p->peerType = ORIGINAL_DST;
struct request_flags {
- request_flags(): range(0),nocache(0),ims(0),auth(0),cachable(0),hierarchical(0),loopdetect(0),proxy_keepalive(0),proxying(0),refresh(0),redirected(0),need_validation(0),fail_on_validation_err(0),stale_if_hit(0),accelerated(0),ignore_cc(0),intercepted(0),spoof_client_ip(0),internal(0),internalclient(0),must_keepalive(0),chunked_reply(0),stream_error(0),sslBumped(0),destinationIPLookedUp_(0) {
+ request_flags(): range(0),nocache(0),ims(0),auth(0),cachable(0),hierarchical(0),loopdetect(0),proxy_keepalive(0),proxying(0),refresh(0),redirected(0),need_validation(0),fail_on_validation_err(0),stale_if_hit(0),accelerated(0),ignore_cc(0),intercepted(0),
+ hostVerified(0),
+ spoof_client_ip(0),internal(0),internalclient(0),must_keepalive(0),chunked_reply(0),stream_error(0),sslBumped(0),destinationIPLookedUp_(0) {
#if USE_HTTP_VIOLATIONS
nocache_hack = 0;
#endif
}
unsigned int range:1;
- unsigned int nocache:1;
+ unsigned int nocache:1; ///< whether the response to this request may be READ from cache
unsigned int ims:1;
unsigned int auth:1;
- unsigned int cachable:1;
+ unsigned int cachable:1; ///< whether the response to thie request may be stored in the cache
unsigned int hierarchical:1;
unsigned int loopdetect:1;
unsigned int proxy_keepalive:1;
#endif
unsigned int accelerated:1;
unsigned int ignore_cc:1;
- unsigned int intercepted:1; /**< transparently intercepted request */
+ unsigned int intercepted:1; ///< intercepted request
+ unsigned int hostVerified:1; ///< whether the Host: header passed verification
unsigned int spoof_client_ip:1; /**< spoof client ip if possible */
unsigned int internal:1;
unsigned int internalclient:1;