-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Exposing cross-origin resource size #31
Comments
@tomvangoethem just to be clear, your research and proposed solution are very much appreciated. I'm trying to get some feedback from implementers to see if they agree. |
F2F:
|
Would browsers having different tactics not make those with the worst tactics simply more vulnerable? |
@annevk The tactics could be the same (bucketing + randomization) but the parameters could/should differ to make it harder to write code that performs a timing attack in different browsers. |
If that is the case they should probably also differ for a single browser (i.e., be some kind of random input). Otherwise market leaders or niche markets are still vulnerable. And I doubt attackers would hesitate to add some browser sniffing into the mix. |
When the opaque response is compressed, and the |
@tomvangoethem if we fix this and cannot find a fix for the HEIST issue (other than asking everyone to use same-site cookies...) are we actually making progress? |
@annevk HEIST provides the attacker with the response size after compression, this one provides the attacker with the uncompressed response size. Knowing either is bad, knowing both is worse. While I'd much rather see a generic solution such as disabling 3rd-party cookies by default (or something that provides a transition there, as is being done with the transition to HTTPS), this issue is independent from HEIST, and if there's no viable generic solution, the issues should be mitigated one by one. In addition, I'd say this issue is easier to exploit as it's more stable and convenient (especially when |
Thank you! |
https://bugs.webkit.org/show_bug.cgi?id=177552 Patch by Youenn Fablet <youenn@apple.com> on 2017-10-09 Reviewed by Alex Christensen. Source/WebCore: Tests: http/wpt/cache-storage/cache-quota.any.html Storing padded opaque response body sizes within FetchResponse and CacheStorageConnection. See whatwg/storage#31 for the rationale about this padding. Storing in CacheStorageConnection is needed for handling cloned network fetched created responses. Storing in FetchResponse is needed for handling cloned cache-storage created opaque responses. Adding internals to query and set the fuzzed size of a response. * Modules/cache/CacheStorageConnection.cpp: (WebCore::computeRealBodySize): (WebCore::CacheStorageConnection::computeRecordBodySize): (WebCore::CacheStorageConnection::setResponseBodySizeWithPadding): (WebCore::CacheStorageConnection::responseBodySizeWithPadding const): * Modules/cache/CacheStorageConnection.h: * Modules/cache/DOMCache.cpp: (WebCore::DOMCache::toConnectionRecord): (WebCore::DOMCache::updateRecords): * Modules/cache/DOMCache.h: * Modules/cache/DOMCacheEngine.cpp: (WebCore::DOMCacheEngine::errorToException): (WebCore::DOMCacheEngine::Record::copy const): * Modules/cache/DOMCacheEngine.h: * Modules/cache/WorkerCacheStorageConnection.cpp: (WebCore::toCrossThreadRecordData): (WebCore::fromCrossThreadRecordData): * Modules/fetch/FetchResponse.cpp: (WebCore::FetchResponse::clone): (WebCore::FetchResponse::BodyLoader::didReceiveResponse): * Modules/fetch/FetchResponse.h: * Modules/fetch/FetchResponse.idl: * testing/Internals.cpp: (WebCore::Internals::setResponseSizeWithPadding): (WebCore::Internals::responseSizeWithPadding const): * testing/Internals.h: * testing/Internals.idl: Source/WebKit: Adding support for quota checking in CacheStorage::Caches. It is passed to NetworkProcess at creation time. Default quota size is configured to 400Ko by origin per default. This value is suitable for testing. Future patch should raise this default value and allows configuring it. Quota is computed based on the response body size. This size is padded at WebCore for opaque responses. Size is stored persistently as opaque response padded size should remain stable. See whatwg/storage#31 for the rationale about this padding. In case of putting several records at the same time, the size of all records is computed so that all records will be written or rejected together. Sending QuotaExceeded error when quota is exceeded. Future effort should allow asking UIProcess for quota extension. * NetworkProcess/NetworkProcess.cpp: (WebKit::NetworkProcess::cacheStoragePerOriginQuota const): * NetworkProcess/NetworkProcess.h: * NetworkProcess/NetworkProcessCreationParameters.cpp: (WebKit::NetworkProcessCreationParameters::encode const): (WebKit::NetworkProcessCreationParameters::decode): * NetworkProcess/NetworkProcessCreationParameters.h: * NetworkProcess/cache/CacheStorageEngine.cpp: (WebKit::CacheStorage::Engine::readCachesFromDisk): * NetworkProcess/cache/CacheStorageEngineCache.cpp: (WebKit::CacheStorage::Cache::toRecordInformation): (WebKit::CacheStorage::isolatedCopy): (WebKit::CacheStorage::Cache::open): (WebKit::CacheStorage::Cache::storeRecords): (WebKit::CacheStorage::Cache::put): (WebKit::CacheStorage::Cache::writeRecordToDisk): (WebKit::CacheStorage::Cache::updateRecordToDisk): (WebKit::CacheStorage::Cache::removeRecordFromDisk): (WebKit::CacheStorage::Cache::encode): (WebKit::CacheStorage::Cache::decodeRecordHeader): (WebKit::CacheStorage::Cache::decode): * NetworkProcess/cache/CacheStorageEngineCache.h: * NetworkProcess/cache/CacheStorageEngineCaches.cpp: (WebKit::CacheStorage::Caches::Caches): (WebKit::CacheStorage::Caches::initialize): (WebKit::CacheStorage::Caches::initializeSize): (WebKit::CacheStorage::Caches::requestSpace): (WebKit::CacheStorage::Caches::writeRecord): (WebKit::CacheStorage::Caches::removeRecord): (WebKit::CacheStorage::Caches::removeCacheEntry): * NetworkProcess/cache/CacheStorageEngineCaches.h: (WebKit::CacheStorage::Caches::create): (WebKit::CacheStorage::Caches::hasEnoughSpace const): * NetworkProcess/cache/NetworkCacheStorage.cpp: (WebKit::NetworkCache::Storage::traverse): * NetworkProcess/cocoa/NetworkProcessCocoa.mm: (WebKit::NetworkProcess::platformInitializeNetworkProcessCocoa): * Shared/WebCoreArgumentCoders.cpp: (IPC::ArgumentCoder<DOMCacheEngine::Record>::encode): (IPC::ArgumentCoder<DOMCacheEngine::Record>::decode): * UIProcess/API/APIProcessPoolConfiguration.cpp: (API::ProcessPoolConfiguration::copy): * UIProcess/API/APIProcessPoolConfiguration.h: * UIProcess/WebProcessPool.cpp: (WebKit::WebProcessPool::ensureNetworkProcess): LayoutTests: * http/wpt/cache-storage/cache-quota.https.any-expected.txt: Added. * http/wpt/cache-storage/cache-quota.https.any.html: Added. * http/wpt/cache-storage/cache-quota.https.any.js: Added. git-svn-id: http://svn.webkit.org/repository/webkit/trunk@223073 268f45cc-cd09-0410-ab3c-d52691b4dbfc
Size-exposing attacks
In its current form, the Storage specification makes it very likely for user agents to develop an implementation that allows attackers to leak the size of opaque Responses. There are at least 3 methods that could be used to do this:
Estimate usage and quota
The Fetch standard describes a method (
estimate()
) that returns a "rough estimate of the amount of bytes used" and "a conservative estimate of the amount of bytes available". As the terms "rough estimate" and "conservative estimate" are not strictly defined, and security considerations are not mentioned in the standard, all bets are off as to what user agents might come up with (Chrome's current implementation, which is currently still behind a flag, seems to return the exact usage instead of an estimate).An attack that leaks the exact resource size is straightforward, even when a "rough estimate" would be given:
Response
in cacheIn case the estimate is used to obscure the resource size, repeat steps (3) and/or (4). E.g. if
estimate()
is implemented to round to the nearest kB value, storing the resource 1k times will give you the exact size.Per-site quota
Each site has their own fixed quota, and when trying to store something that doesn't fit in storage, this will obviously fail. These features can be abused to leak the response size in the following way:
Response
in cache5MB - num_fill_bytes
Global quota
Similar to the per-site quota, there's also a global quota, and user agents will free up space by first clearing non-persistent boxes. This provides the same properties as above to obtain the exact resource size. An attack looks as follows:
Response
in cachesize_first_evicted_box - num_fill_bytes
Compared to the previous attacks, this one is slightly harder to exploit (especially since the global quota can be substantial), but given the high storage speeds (especially with SSD) the attack is still very practical.
_Note:_ For all browsers that already implement one of the above (i.e. virtually every browser), we managed to devise an attack that exposes the exact size of any resource.
Consequences
Being able to determine the resource size of arbitrary
Response
objects poses various privacy and security issues. For example, we found that by knowing the exact size of just 5 resources ontwitter.com
(i.e.https://twitter.com/following
,https://twitter.com/followers
, …) it is possible to uniquely identify a user from a large set. In an experiment on 500k user profiles, we found that a user could be uniquely identified in 97.62% of the cases. Of course, since virtually every website is sending state-specific responses, the consequences are not just limited to this example, and are applicable to a large number of web services.Mitigation
Having a usable solution that completely eradicates all size-exposing vectors seems unlikely. Instead, I think it's best to have a solution that limits the practicability that of to existing attacks (i.e. timing attacks). As such, I'd like to suggest an approach where "virtual padding" is applied on
Response
objects. More concretely: upon creation of aResponse
object, for instance as the result of afetch()
operation, choose a random valuer
between0
andrMax
. Next, round upResponse.size + r
to a multiple ofD
. The "virtual padding" is then the rounded up value minusResponse.size
. Note that when aResponse
object is cloned, it should inherit the same padding value.The padding is virtual in the sense that it is not actually written do disk. Instead, the user agent just uses it as part of its storage bookkeeping, and will also use these values to provide usage/quota estimates.
This method works quite well as it prevents an attacker to quickly obtain many measurements (for each measurement, a
fetch()
operation is required).For values of
rMax
andD
, I'd suggest 100kb and 20kb respectively. Even after 50 measurements, these values seem to obfuscate the actual size somewhere in the range of 4.5kb (for 10 measurements, this is approximately 10kb). I made a little script that allows you to play around with the values a bit, but note that most likely a better method can be used to improve the accuracy of attacks, so these values should only be seen as an upper bound.The text was updated successfully, but these errors were encountered: