CVS Discretely Shares Your Location with 40+ Other Sites
Recently, we decided to take a closer look at apps in our database that are sharing location data. This is a concern, because it could be used to track someone over time. One app in particular stood out, just based on the sheer number of data recipients. The “CVS/pharmacy” app appears to be sending the user’s GPS coordinates to over 40 different entities! Some of these include:
Since we could not imagine a legitimate reason for why an app would share location data with so many third parties, we were worried that this was a mistake in our analysis tools. We double checked our logs and even manually re-tested the app. It wasn’t an error; we were able to reproduce this result every time, on multiple versions of the app.
What’s going on here?
What we discovered was that all of these entities appear to have HTML code embedded in CVS’s store locator webpage (at least the one accessed from within the app). This is known as “third-party content.” You see, when you use the store locator functionality, it opens a web page in what is called a “WebView.” This is essentially a slimmed down web browser, and is used by developers to seamlessly display webpages within their mobile apps (this saves the user the trouble of having to switch back and forth between the device’s default web browser and an app that wants to include web content). You may notice in apps like Facebook or Twitter, that when you click links in posts, they open within those apps, rather than switching you to your normal web browser; these are WebViews.
All of the entities above have content embedded in CVS’s store locator page that loads within the WebView: some of these appear to be servers owned by CVS (e.g., “depservices.cvs.com“), though many appear to be advertising and analytics providers (i.e., customer profiling). Looking in our analysis logs, we can clearly see our geographic coordinates being sent (in this example, the WebView is trying to load “/oct.js” from static.ads-twitter.com):
GET /oct.js HTTP/1.1
User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; AOSP on BullHead Build/MASTER; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/44.0.2403.119 Mobile Safari/537.36 CVS_ANDROID_APP 3.4.1,lat:37.XXXXXXXXXXXXXXX,long:-122.XXXXXXXXXXXXXXX
Accept-Encoding: gzip, deflate
Whenever web content is loaded, the requesting software (e.g., your browser, or in this case, the CVS app’s WebView) also sends various information about itself, which is used by the web server to decide how to send content back. For instance, your web browser might indicate that it’s running on a mobile device, signaling the website to send the mobile-optimized version. One such piece of information that always gets sent by web browser software is the
User-Agent string, which lists the type of software being used to request the web page, including version numbers of both the operating system and the browser being used. This information gets sent every time you visit a website, regardless of the device used.
By itself, the User-Agent string isn’t meant to be an identifier. However, in the case above, the User-Agent string has been modified to additionally include GPS coordinates! (We removed some of the information in the example above, replacing it with Xs.) What this means is that every time the CVS app’s WebView loads content from another source (be it CVS’s own servers, advertisements from Google, etc.), it is sending those servers your GPS coordinates, every single time.
Why would anyone do this?
Honestly, we have no idea. The most likely explanation is simply really poor software engineering practices. We literally cannot think of any legitimate reason for doing this.
What is the proper way of sharing location information with a website loaded within a WebView?
The problem with the CVS app, as written, is that any third party content loaded into the WebView is going to receive GPS coordinates, so long as that information is stored in the User-Agent string. It is
likely that their intent is to only share the data with their own website, in order to look up nearby CVS stores. If that’s the case, what they should have done is simply passed the location information to that single website as an HTTP POST variable. This would allow the WebView to only share the data with a single party, the primary page it is trying to load, while still loading the other third-party content (but preventing the third-a.
Your website shows this as being a problem with an older version (3.4.1) of the CVS app. Given that you don’t show as much location sharing for newer versions, have they fixed the problem?
As of this writing, the answer is no. When we automatically test apps, we simulate user input. What this means is that we run each app on an instrumented phone, but instead of having a real person play with the app, we have automated the process of randomly pushing buttons and swiping things on the screen. Doing this over a prolonged period of time allows us to encounter
most of the functionality that a real human would encounter. However, it’s not perfect.
Given that the input is random, sometimes it might miss things. Looking over our testing logs, we found that the testbed only triggered the store locator functionality when testing the 3.4.1 version, and never triggered that functionality in newer versions. That’s why our website doesn’t show as much location sharing in the newer versions. Upon manually testing the most recent version (as of this writing), 3.7, we can confirm that the problem still exists.
Why do you say they share it “discretely,” in the title of this post?
That was partially tongue-in-cheek, but also partially factual: given that most of the recipients of the location data likely aren’t expecting to receive GPS coordinates via the User-Agent string, it’s likely that many aren’t aware that they’re even receiving them from CVS app users. Of course, they likely have the data stored somewhere, and could possibly be using it. We can’t know for sure.