Bug 1376459 - Prevent Offline Application Cache fallback namespace and fallback page be from a parent directory relative to the manifest. r=jduell
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -191,16 +191,60 @@ Hash(const char *buf, nsACString &hash)
NS_ENSURE_SUCCESS(rv, rv);
rv = hasher->Finish(true, hash);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
+bool
+IsInSubpathOfAppCacheManifest(nsIApplicationCache *cache, nsACString const& uriSpec)
+{
+ MOZ_ASSERT(cache);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri, &rv));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsAutoCString directory;
+ rv = url->GetDirectory(directory);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> manifestURI;
+ rv = cache->GetManifestURI(getter_AddRefs(manifestURI));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURL> manifestURL(do_QueryInterface(manifestURI, &rv));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsAutoCString manifestDirectory;
+ rv = manifestURL->GetDirectory(manifestDirectory);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return StringBeginsWith(directory, manifestDirectory);
+}
+
} // unnamed namespace
// We only treat 3xx responses as redirects if they have a Location header and
// the status code is in a whitelist.
bool
nsHttpChannel::WillRedirect(nsHttpResponseHead * response)
{
return IsRedirectStatus(response->Status()) &&
@@ -3498,16 +3542,22 @@ nsHttpChannel::ProcessFallback(bool *wai
NS_ENSURE_SUCCESS(rv, rv);
if (fallbackEntryType & nsIApplicationCache::ITEM_FOREIGN) {
// This cache points to a fallback that refers to a different
// manifest. Refuse to fall back.
return NS_OK;
}
+ if (!IsInSubpathOfAppCacheManifest(mApplicationCache, mFallbackKey)) {
+ // Refuse to fallback if the fallback key is not contained in the same
+ // path as the cache manifest.
+ return NS_OK;
+ }
+
MOZ_ASSERT(fallbackEntryType & nsIApplicationCache::ITEM_FALLBACK,
"Fallback entry not marked correctly!");
// Kill any offline cache entry, and disable offline caching for the
// fallback.
if (mOfflineCacheEntry) {
mOfflineCacheEntry->AsyncDoom(nullptr);
mOfflineCacheEntry = nullptr;
@@ -4597,16 +4647,27 @@ nsHttpChannel::OnOfflineCacheEntryAvaila
// ... and if there were an application cache entry,
// we would have found it earlier.
return NS_ERROR_CACHE_KEY_NOT_FOUND;
}
if (namespaceType &
nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
+
+ nsAutoCString namespaceSpec;
+ rv = namespaceEntry->GetNamespaceSpec(namespaceSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This prevents fallback attacks injected by an insecure subdirectory
+ // for the whole origin (or a parent directory).
+ if (!IsInSubpathOfAppCacheManifest(mApplicationCache, namespaceSpec)) {
+ return NS_OK;
+ }
+
rv = namespaceEntry->GetData(mFallbackKey);
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
--- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp
+++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp
@@ -796,16 +796,47 @@ nsOfflineManifestItem::AddNamespace(uint
NS_ENSURE_SUCCESS(rv, rv);
rv = mNamespaces->AppendElement(ns, false);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
+static nsresult
+GetURIDirectory(nsIURI* uri, nsACString &directory)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->GetDirectory(directory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+static nsresult
+CheckFileContainedInPath(nsIURI* file, nsACString const &masterDirectory)
+{
+ nsresult rv;
+
+ nsAutoCString directory;
+ rv = GetURIDirectory(file, directory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool contains = StringBeginsWith(directory, masterDirectory);
+ if (!contains) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ return NS_OK;
+}
+
nsresult
nsOfflineManifestItem::HandleManifestLine(const nsCString::const_iterator &aBegin,
const nsCString::const_iterator &aEnd)
{
nsCString::const_iterator begin = aBegin;
nsCString::const_iterator end = aEnd;
// all lines ignore trailing spaces and tabs
@@ -940,16 +971,35 @@ nsOfflineManifestItem::HandleManifestLin
if (NS_FAILED(rv))
break;
if (NS_FAILED(DropReferenceFromURL(fallbackURI)))
break;
rv = fallbackURI->GetAsciiSpec(fallbackSpec);
if (NS_FAILED(rv))
break;
+ // The following set of checks is preventing a website under
+ // a subdirectory to add fallback pages for the whole origin
+ // (or a parent directory) to prevent fallback attacks.
+ nsAutoCString manifestDirectory;
+ rv = GetURIDirectory(mURI, manifestDirectory);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ rv = CheckFileContainedInPath(namespaceURI, manifestDirectory);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ rv = CheckFileContainedInPath(fallbackURI, manifestDirectory);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
// Manifest and namespace must be same origin
if (!NS_SecurityCompareURIs(mURI, namespaceURI,
mStrictFileOriginPolicy))
break;
// Fallback and namespace must be same origin
if (!NS_SecurityCompareURIs(namespaceURI, fallbackURI,
mStrictFileOriginPolicy))