Stealing Local Files using the Web Share API (in Safari)

One of the features of the Web Share API is that it allows you to share files along with your share intent:

if (navigator.canShare && navigator.canShare({ files: filesArray })) {
  navigator.share({
    files: filesArray,
    title: 'Vacation Pictures',
    text: 'Photos from September 27 to October 14.',
  })
  .then(() => console.log('Share was successful.'))
  .catch((error) => console.log('Sharing failed', error));
} else {
  console.log(`Your system doesn't support sharing files.`);
}

As unearthed by redteam.pl there’s a big security issue with it in Safari/MobileSafari:

The problem is that file: scheme is allowed and when a website points to such URL unexpected behavior occurs. In case such a link is passed to the navigator.share function an actual file from the user file system is included in the shared message which leads to local file disclosure when a user is sharing it unknowingly. The problem is not very serious as user interaction is required, however it is quite easy to make the shared file invisible to the user. The closest comparison that comes to mind is clickjacking as we try to convince the unsuspecting user to perform some action.

In their tests they’ve successfully stolen the local /etc/passwd or even the entire MobileSafari history (which is nothing more than a .sqlite file).

Apple first stated that they are only going to fix it in the Spring 2021 (!) Security Update, but after the post went public it now looks that they’ve changed course, as by now a fix has been committed into the Webkit Source:

+static Optional<URL> shareableURLForShareData(ScriptExecutionContext& context, const ShareData& data)
+{
+    if (data.url.isNull())
+        return WTF::nullopt;
+
+    auto url = context.completeURL(data.url);
+    if (!url.isValid())
+        return WTF::nullopt;
+    if (!url.protocolIsInHTTPFamily() && !url.protocolIsData())
+        return WTF::nullopt;
+
+    return url;
+}
+
 
-    Optional<URL> url;
-    if (!data.url.isNull()) {
-        url = context.completeURL(data.url);
-        if (!url->isValid())
-            return false;
-    }
+    if (!data.url.isNull() && !shareableURLForShareData(context, data))
+        return false;
+

Bonus points for naming that class WTF 😅

Apart from that quick fix in the code, the issue is also being discussed at the spec level in order to prevent other implementers from making the same mistake.

🤔 What about Chrome you might ask? Google Chrome (on Android) has an allowlist of permitted file extensions in place.

Stealing local files using Safari Web Share API →

Via Thomas Steiner (@tomayac) on Twitter

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.