WebToApp

Starting in Edge 77 (and Chrome 77), the prompt shown when launching an AppProtocol from the browser was changed to remove the “Always allow” checkbox. That change was made, in large part, because this prompt is the only thing standing between every arbitrary site on the Internet (loaded inside your browser’s sandbox) and a full-trust application on your computer (running outside of the browser’s sandbox). See the prior blog post for details on why AppProtocols are so scary.

After Edge 77, when you try to launch a Microsoft Teams meeting, for instance, you’ll see a UI like this:

Unfortunately, there’s a downside to this security improvement.

The same prompt that protects users from malicious content on https://BadGuy.example also shows every single time the legitimate Microsoft Teams website tries to open its related application. Users complain that the security prompt feels redundant, and IT departments have howled that they’ll have to retrain users and field helpdesk calls.

Starting in Edge 82.0.425.0 Canary, a new flag is available:

Visit edge://flags/#edge-exclude-schemes-per-origin, set the flag to Enabled, and restart the browser. After doing so, you’ll see that the prompt now includes a new checkbox: “Always allow <hostname> to open links of this type in the associated app”:

By storing exemptions on a per-site, per-scheme basis, attack surface is significantly reduced, because only sites you’ve specifically allowed in the past are permitted to bypass the prompt.

Some notes on this change:

  • Exemptions are stored on a per-scheme, per-origin basis (e.g. “Allow teams: from https://teams.microsoft.com“, so if multiple origins use the same scheme, you’ll need to exempt each one.
  • Stored exemptions are origin specific: “https://site.example&#8221; and “https://www.site.example” and “http://site.example” are all different origins.
  • Stored exemptions are only available for HTTP and HTTPS origins.
  • In a future release, we expect to turn this flag on by default.
  • At present, there is no Group Policy for an admin to push exemptions to the client.
  • To clear stored exemptions, you may continue to use the “Cookies and other site data” checkbox in the Clear Browsing Data dialog box. Note that you can set the time range to anything you like– all Origin+Scheme exemptions will be cleared.

You can experiment with this feature using the AppProtocol test page.

-Eric

In recent posts, I’ve explored mechanisms to communicate from web content to local (native) apps, and I explained how web apps can use the HTML5 registerProtocolHandler API to allow launching them from either local apps or other websites.

In today’s post, we’ll explore how local apps can launch web apps in the browser.

It’s Simple…

In most cases, it’s trivial for an app to launch a web app and send data to it. The app simply invokes the operating system’s “launch” API and passes it the desired URL for the web app.

Any data to be communicated to the web app is passed in the URL query string or the fragment component of the URL.

On Windows, such an invocation might look like this:

ShellExecute(hwnd, "open", "https://bayden.com/echo.aspx?DataTo=Pass#GoesHere", 0, 0, SW_SHOW);

Calling this API results in the user’s default browser being opened and a new tab navigated to the target URL.

This same simple approach works great on most operating systems and with virtually any browser a user might have configured as their default.

…Unless It’s Not

Unfortunately, this well-lit path adjoins a complexity cliff— if your scenario has requirements beyond the basic [Launch the default browser to this URL], things get much more challenging. The problem is that there is no API contract that provides a richer feature set and works across different browsers.

For instance, consider the case where you’d like your app to direct the browser to POST a form to a target server. Today, popular operating systems have no such concept– they know how to open a browser by passing it a URL, but they expose no API that says “Open the User’s browser to the following URL, sending the navigation request via the HTTP POST method and containing the following POST body data.

Over the years, a few workarounds have been used (e.g. see StackOverflow1 and StackOverflow2).

For instance, if the target webservice simply requires a HTTP POST and you cannot change it, your app could launch the browser to a webpage you control, passing the required data in the querystring component of a HTTP GET. Your web server could then reformat the data into the required POST body format and either proxy that request (server-side) to the target webservice, or it could return a web page with an auto-submitting form element with a method of POST and and action attribute pointed at the target webservice. The user’s browser will submit the form, posting the data to the target server.

Similarly, a more common approach involves having the app write a local HTML file in a temporary folder, then direct the Operating System to open that file using the appropriate API (again ShellExecute, in the case of Windows). Presuming that the user’s default HTML handler is also their default HTTPS protocol handler, opening the file will result in the default browser opening, and the HTML/script in the file will automatically submit the included form element to the target server. This “bounce through a local temporary form” approach has the advantage of making it possible to submit sizable of data to the server (e.g. the contents of a local file), unlike using a GET request’s size-limited querystring.

Caveats:

  • Unfortunately it is generally not possible to construct a HTML form that will submit a data field that exactly matches what you would get when sending an <input type=file> control. If the web service demands a format that was generated by a file upload control, you may not be able to emulate that.
  • Some browsers will not run JavaScript in local files by default.
  • Don’t forget to delete the temporary file!

If your scenario requires uploading files, an alternative approach is to:

  1. Upload the files directly from your app to a web service
  2. Have that web service return a secret token associated with the upload
  3. Have your app spawn a browser with a GET request whose querystring contains that secret token

Browser-Specific Approaches

Back in the Windows 7 days, the IE8 team created a very cool feature called Accelerators that would allow users to invoke web services in their browser from any other application. Interestingly, the API contract supported web services that required POST requests.

Because there was no API in Windows that supported launching the default browser with anything other than a URL, a different approach was needed. A browser that wished to participate as a handler for accelerators could implement a IOpenServiceActivityOutputContext::Navigate function which was expected to launch the browser and pass the data. The example implementation provided by our documentation called into Internet Explorer’s Navigate2() COM API, which accepted as a parameter the POST body to be sent in the navigation. As far as I know, no other browser ever implemented IOpenServiceActivityOutputContext.

These days, Accelerators are long dead, and no one should be using Internet Explorer anymore. In the intervening years, no browser-agnostic mechanism to transfer a POST request from an app to a browser has been created.

Perhaps the closest we’ve come is the W3C’s WebDriver Standard, designed for automated testing of websites across arbitrary browsers. Unfortunately, at present, there’s still no way for mainstream apps to take a dependency on WebDriver to deliver a reliable browser-agnostic solution enabling rich transfers from a local app to a web app. Similarly, Puppeteer can be used for some web automation scenarios in Chrome or Edge.

Future Possibilities

While the current picture is bleak, the future is a bit brighter. That’s because a major goal of browsers’ investment in Progressive Web Apps is to make them rich enough to take the place of native apps. Today’s native apps have very rich mechanisms for passing data and files to one another and PWAs will need such capabilities in order to achieve their goals.

Perhaps one day, not too far in the future, your OS and your browser (regardless of vendor) will better interoperate.

-Eric

I’ve previously written about Web-to-App communication via Application Protocols. App Protocols allow web content to invoke a native application outside of the browser.

WebApp advocates (like me!) want to continue to close the native/browser gaps that prevent web applications from becoming full-fledged replacements for native apps. To that end, I’ve recently spent some time looking at how the web platform allows JavaScript registration of a protocol handler, where the handling “app” is a same-origin web page.

Currently supported by Firefox and Chromium-based browsers (on platforms other than Android), the function navigator.registerProtocolHandler(scheme, url_template, description) enables a website to become a handler for a URL scheme.

Real-World Usage

The canonical use-case for this is web based email clients like Gmail. Gmail would like to be able to be the user’s handler for mailto links. When the user clicks a mailto link, the content of the link should be sent to a handler page on mail.google.com which responds accordingly (e.g. by creating a new email to the specified addressee).

https://mail.google.com/mail/u/0/?extsrc=mailto&url=mailto:u@example.com

The registerProtocolHandler API isn’t limited to the mailto scheme, however. It presently supports a short list of allowed schemes1, and any scheme named web+{one-or-more-lowercaseASCII}.

User Experience

I’ve built a page containing a number of test cases here. When you push the button to register a protocol handler, you receive a permission prompt from Chrome/Edge or Firefox:

ChromePrompt

FirefoxPrompt

To avoid annoying users, if the user declines Chrome’s prompt, the site is blocked from re-requesting permission to handle the protocol. A user must manually visit the settings page to unblock permission.

User-Gesture Requirements

If a page attempts to call registerProtocolHandler() on load or before the user has interacted with the page (a so called “gesture”), then Chromium-based browsers will not pop the permission prompt. Instead, an overlapping-diamonds icon is shown at the right-hand side of the address bar, with the text “This page wants to install a service handler.” Here’s what this looks like on Gmail:

RegisterServiceHandler

Settings

Within Chrome, you can view your currently registered handlers (and sites blocked from asking to become the registered handler) by visiting chrome://settings/content/handlers.

ProtocolSettings

Operating System Integration

One particularly interesting aspect of allowing web-based registration of protocol handlers is that it is desirable for the rest of the operating system outside of the browser to respect those protocol handler associations.

For example, clicking a mailto link in some other application should launch the browser to the web-based handler if registered. However, having the browser change system state in this manner is complicated, especially on modern versions of Windows whereby various protections have been put in place to try to prevent “hijacking” of file type and protocol handler associations.

Edge and Chrome will only attempt to become the systemwide handler for a protocol when running a channel that offers to become the default browser (e.g. Stable). On such a channel, if the browser wasn’t already the handler for the protocol, after the user clicks “Allow” on the Chrome permission bubble, a Windows UAC dialog is shown:

UAC

If the user accepts by clicking “Yes”, the RegisterChromeForProtocol function silently updates the registry:

RegistryWrites

Other Things I’ve Learned

  • Chrome, Edge, and Firefox disallow registration of protocol handlers in Private/Incognito/InPrivate modes.
  • With my patch landed, Chrome, Edge, and Firefox disallow registration of protocol handlers from non-secure contexts (e.g. HTTP). Due to the same-origin requirement for the handler URL, this effectively prevents the use of a non-secure page as a handler.
  • An upcoming patch proposes blocking registration from cross-origin subframes.
  • Chromium-based browsers enable IT admins to set default scheme-to-web mappings using Group Policy.
  • Chrome presently fails to enforce specified limits on web+ protocol names. Firefox does enforce the limits.
  • Firefox does not support targeting a RPH registered protocol as the target of a form POST request; it silently drops the POST body.
  • Firefox does not implement the unregisterProtocolHandler API. Users must manually unregister protocol handlers using the browser UI.
  • On Windows at least, neither Firefox Stable nor Firefox Nightly seems to try to become the systemwide handler for a scheme.
  • If you have a custom scheme link in a subframe, you probably want to add a target=_blank attribute on it. Otherwise, your protocol handler web page might navigate within that subframe and get blocked due to privacy settings or X-Frame-Options directives

-Eric

1 The permitted schemes are bitcoin, geo, im, irc, ircs, magnet, mailto, mms, news, nntp, openpgp4fpr, sip, sms, smsto, ssh, tel, urn, webcal, wtai, xmpp.

Note: This post is part of a series about Web-to-App Communication techniques.

Background

Typically, if you want your website to send a document to a client application, you simply send the file as a download. Your server indicates that a file should be treated as a download in one of a few simple ways:

  • Specifying a nonwebby type in the Content-Type response header.
  • Sending a Content-Disposition: attachment; filename=whatever.ext response header.
  • Setting a download attribute on the hyperlink pointing to the file.

These approaches are well-supported across browsers (via headers for decades, via the download attribute anywhere but IE since 2016).

The Trouble with Plain Downloads

However, there’s a downside to traditional downloads — unless the file itself contains the URL from which the download originated, the client application will not typically know where the file originated, which can be a problem for:

  • Security – “Do I trust the source of this file?
  • Functionality – “If the user makes a change to this file, to where should I save changes back?“, and
  • Performance – “If the user already had a copy of this 60mb slide deck, maybe skip downloading it again over our expensive trans-Pacific link?

Maybe AppProtocols?

Rather than sending a file download, a solution developer might instead just invoke a target application using an App Protocol. For instance, the Microsoft Office clients might support a syntax like:

ms-word:ofe|u|https://example.com/docx.docx

…which directs Microsoft Word to download the document from example.com.

However, the AppProtocol approach has a shortcoming– if the user doesn’t happen to have Microsoft Word installed, the protocol handler will fail to launch and either nothing will happen or the user may get a potentially confusing error message. That brokenness will occur even if they happen to have another client (e.g. WordPad) that could handle the document.

DirectInvoke

To address these shortcomings, we need a way to instruct the browser: “Download this file, unless the client’s handler application would prefer to just get its URL.”

While a poorly-documented precursor technology existed as early as the 1990s, Windows 8 reintroduced this feature as DirectInvoke. When a client application registers itself indicating that it supports receiving URLs rather than local filenames, and when the server indicates that it would like to DirectInvoke the application using the X-MS-InvokeApp response header:

DirectInvoke

…then the download stream is aborted and the user is instead presented with a confirmation prompt:

UIPrompt

If the user accepts the prompt, the handler application is launched, passing the URL to the web content.

Now, for certain types, the server doesn’t even need to ask for DirectInvoke behavior via the X-MS-InvokeApp header. The FTA_AlwaysUseDirectInvoke bit can be set in the type’s EditFlags registry value. The bit is documented on MSDN as: 

FTA_AlwaysUseDirectInvoke 0x00400000
Introduced in Windows 8. Ensures that the verbs for the file type are invoked with a URL instead of a downloaded version of the file. Use this flag only if you’ve registered the file type’s verb to support DirectInvoke through the SupportedProtocols or UseUrl registration.

Microsoft’s ClickOnce deployment technology makes use of the FTA_AlwaysUseDirectInvoke flag.

A sample registry script for a type that should always be DirectInvoke’d might look like this:

To test it, first set up the registry, install a handler to C:\Windows, and then click this example link

TraditionalVsDI.png

Caveats

In order for this architecture to work reliably, you need to ensure a few things.

App Should Handle Traditional Files

First, your application needs to have some reasonable experience if the content is handled as a traditional download, as it would be using Chrome or Firefox, or on a non-Windows operating system.

By way of example, it’s usually possible to construct a ClickOnce manifest that works correctly after download. Similarly, Office applications work fine with regular files, although the user must take care to reupload the files after making any edits.

App Should Avoid Depending On Browser State

If your download flow requires a cookie, the client application will not have access to that cookie and the download will fail. The client application probably will not be able to prompt the user to login to otherwise retrieve the file.

If your download flow requires HTTP Authentication or HTTPS Client Certificate Authentication, the client application might work (if it supports NTLM/Negotiate) or it might not (e.g. if the server requires Digest Auth and the client cannot show a credential prompt.

App Should Ensure URL Support

Many client applications have limits in the sorts of URLs that they can support. For instance, the latest version of Microsoft Excel cannot handle a URL longer than 260 characters. If a .xlsx download from SharePoint site attempts to DirectInvoke, Excel will launch and complain that it cannot retrieve the file.

App Should Ensure Network Protocol Support

Similarly, if the client app registers for DirectInvoke of HTTPS URLs, you should ensure that it supports the same protocols as the browser. If a server requires a protocol version (e.g. TLS/1.2) that the client hasn’t yet enabled (say it only enables TLS/1.0), then the download will fail.

Server Must Not Send |Content-Disposition: attachment|

As noted in the documentation, a Content-Disposition: attachment response header takes precedence over DirectInvoke behavior. If a server specifies attachment, DirectInvoke will not be used.

Note: If you wish to use a Content-Disposition header to name the file, you can do so using Content-Disposition: inline; filename=”fuzzle.fuzzle”

Conclusion

As you can see, there’s quite a long list of caveats around using the DirectInvoke WebToApp communication scheme, but it’s still a useful option for some scenarios.

In future posts, I’ll continue to explore some other alternatives for Web-to-App communication.

-Eric

Note: The current version of Edge 79 does not yet support FTA_AlwaysUseDirectInvoke. We expect to fix this in the future.

Note: I have a few test cases.

 

Note: This post is part of a series about Web-to-App Communication techniques.

Just over eight years ago, I wrote my last blog post about App Protocols, a class of URL schemes that typically1 open another program on your computer instead of returning data to the web browser. 

App Protocols2 are both simple and powerful, allowing client app developers to easily enable the invocation of their apps from a website. For instance, ms-screenclip is a simple app protocol built into Windows 10 that kicks off the process of taking a screenshot:

    ms-screenclip:?delayInSeconds=2

When the user invokes this url, the handler waits two seconds, then launches its UI to collect a screenshot. Notably, App Protocols are fire-and-forgetmeaning that the handler has no direct way to return data back to the browser that invoked the protocol.

The power and simplicity of App Protocols comes at a cost. They are the easiest route out of browser sandboxes and are thus terrifying, especially because this exploit vector is stable and available in every browser from legacy IE to the very latest versions of Chrome/Firefox/Edge/Safari.

What’s the Security Risk?

A number of issues make App Protocols especially risky from a security point-of-view.

Careless App Implementation

The primary security problem is that most App Protocols were designed to address a particular scenario (e.g. a “Meet Now” page on a videoconferencing vendor’s website should launch the videoconferencing client) and they were not designed with the expectation that the app could be exposed to potentially dangerous data from the web at large.

We’ve seen apps where the app will silently reconfigure itself (e.g. sending your outbound mail to a different server) based on parameters in the URL it receives. We’ve seen apps where the app will immediately create or delete files without first confirming the irreversible operation with the user. We’ve seen apps that assumed they’d never get more than 255 characters in their URLs and had buffer-overflows leading to Remote Code Execution when that limit was exceeded. The list goes on and on.

Poor API Contract

In most cases3, App Protocols are implemented as a simple mapping between the protocol scheme (e.g. “alert”) and a shell command, e.g. 

AlertProtocol

When the protocol is invoked by the browser, it simply bundles up the URL and passes it on the command line to the target application. The app doesn’t get any information about the caller (e.g. “What browser or app invoked this?“, “What origin invoked this?“, etc) and thus it must make any decisions solely on the basis of the received URL.

Until recently, there was an even bigger problem here, related to the encoding of the URL. Browsers, including Chrome, Edge, and IE, were willing to include bare spaces and quotation marks in the URL argument, meaning that an app could launch with a command line like:

alert.exe "alert:This is an Evil URL" --DoSomethingDangerous --Ignore=This"

The app’s code saw the –DoSomethingDangerous “argument”, failed to recognize it as a part of the URL, and invoked dangerous functionality. This attack led to remote code execution bugs in App Protocol handlers many times over the years. 

Chrome began %-escaping spaces and quotation marks8 back in Chrome 64, and Edge 18 followed suit in Windows 10 RS5.

You can see how your browser behaves using the links on this test page.

Future Opportunity: A richer API contract that allows an App Protocol handler to determine how specifically it was invoked would allow it to better protect itself from unexpected callers. Moving the App Protocol URL data from the command line to somewhere else (e.g. stdin) might help reduce the possibility of parsing errors.

Sandbox

The application that handles the protocol typically runs outside of the browser’s sandbox. This means that a security vulnerability in the app can be exploited to steal or corrupt any data the user can access, install malware, etc. If the browser is running Elevated (at Administrator), any App Protocol handlers it invokes are launched Elevated; this is part of UAC’s design.

Because most apps are written in native code, the result is that most protocol handlers end up in the DOOM! portion of this diagram:

RuleOfTwo

Prompting

In most cases, the only4 thing standing between the user and disaster is a permission prompt.

In Internet Explorer, the prompt looked like this:

IEPermission
As you can see, the dialog provides a bunch of context about what’s happening, including the raw URL, the name of the handler, and a remark that allowing the launch may harm the computer.

Such information is lacking in more modern browsers, including Firefox:

FirefoxPermission

…and Edge/Chrome:

ChromePermission

Browser UI designers (reasonably) argue that the vast majority of users are poorly equipped to make trust decisions, even with the information in the IE prompt, and so modern UI has been greatly simplified5

Unfortunately, lost to evolution is the crucial indication to the user that they’re even making a trust decision at all [Issue].

Eliminating Prompts

Making matters more dangerous, everyone hates security prompts that interrupt non-malicious scenarios. A common user request can be summarized as “Prompt me only when something bad is going to happen. In fact, in those cases, don’t even prompt me, just protect me.

Unfortunately, the browser cannot know whether a given App Protocol invocation is good or evil, so it delegates control of prompting in two ways:

In Internet Explorer and Edge (version<=18), the browser respects a per-protocol WarnOnOpen boolean in the registry, such that the App itself may tell the browser: “No worries, let anyone launch me, no prompt needed.

In Firefox, Chrome, and Edge (version >= 76), the browser instead allows the user to suppress further prompts with an “Always open links of this type in the associated app” checkbox.

If the user selects this option, the protocol will silently launch in the future without the browser first asking permission.

However, Edge/Chrome version 77.0.3864 removed the “Always open these types of links in the associated app” checkbox.

NoCheckbox
The stated reason for the removal is found in Chrome issue #982341:

No obvious way to undo “Always open these types of links” decision for External Protocols.

We realized in a conversation around issue 951540 that we don’t have settings UI
that allows users to reconsider decisions they’ve made around external protocol
support. Until we work that out, and make longer-term decisions about the
permission model around the feature generally, we should stop making the problem
worse by removing that checkbox from the UI.

A user who had ticked the “Always open” box has no way to later review that decision6, and no obvious way to reverse it. Almost no one figured out that using the “Clear Browsing Data > Cookies and other site data” dialog box option directs Chrome to delete all previous “Always open” decisions for the user’s profile. 

Particularly confusing is that the “Always open” decision wasn’t made on a per-site basis– it applies to every site visited by the user in that browser profile.

Update 1 of 2An Enterprise policy for v79+ allows administrators to restore the checkbox. End users can import this registry script.

Future Opportunity: Much of the risk inherent in open-without-prompting behavior comes from the site that any random site (http://evil.example.com) can abuse ambient permission to launch the protocol handler. If browsers changed the option to “Always allow this site to open this protocol”, the risk would be significantly reduced, and a user could reasonably safely allow, e.g. https://teams.microsoft.com to open the msteams protocol without a prompt.

Update 2 of 2: Microsoft Edge 82 introduced this change.

Alternatively, perhaps the Registry-based provisioning of a protocol handler should explicitly list the sites allowed to launch the protocol, akin to the SiteLock protection for legacy ActiveX controls.

For some schemes7 , Chrome will not even show a prompt because the protocol is included on a built-in allow or deny list.

Some security folks have argued that browsers should not provide any mechanism for skipping the permission prompt. Unfortunately, there’s evidence to suggest that such a firm stance might result in vendors avoiding the prompt by choosing even riskier architectures for Web-to-App communication. More on this in a future post.

Zero-Day Defense

Even when a zero day vulnerability in an App Protocol handler is getting exploited in the wild (e.g. this one), browsers have few defenses available to protect users. 

Unlike, say, file downloads, where the browser has multiple mechanisms to protect users against newly-discovered threats (e.g. file type policies and SmartScreen/SafeBrowsing), browsers do not presently have rapid update mechanisms that can block access to known vulnerable App Protocol handlers.

Future Opportunity: Use SafeBrowsing/SmartScreen or a file-type-policies style Component Update to supply the client with a list of known-vulnerable protocol handlers. If a page attempts to invoke such a protocol, either block it entirely or strongly caution the user.

To improve the experience even further, the blocklist could contain version information such that blocking/additional warnings would only be shown if the version of the handler app is earlier than the version number of the app containing the fix. 

Antivirus programs typically do monitor all calls to ShellExecute and could conceivably protect against malicious invocation of app protocol handlers, but I am not aware of any having ever done so.

IT Administrators can block users from launching protocols by listing them as rules in URLBlocklist policy:

REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\URLBlockList" /v "1" /t REG_SZ /d "exampleBlocked:*" /f

Privacy Concerns Prevent Protocol Detection

One of the most common challenges for developers who want to use App Protocols for Web-to-App communication is that the web platform does not expose the list of available protocol handlers to JavaScript. This is primarily a privacy consideration: exposing the list of protocol handlers to the web would expose a significant amount of fingerprintable entropy and might even reveal things about the user’s interests and beliefs (e.g. a ConservativeNews App or a LGBTQ App might expose a protocol handler for app-to-app communication).

Internet Explorer and Edge <= 18 supply a non-standard JavaScript function msLaunchUri that allows a web page to detect that a user didn’t have a to-be-invoked protocol handler installed, but this function is not available in other browsers.

UX When a Protocol Isn’t Installed

Browser behavior varies if the user attempts to invoke a link with a scheme for which no protocol handler is registered.

Firefox shows an error page: FirefoxNotInstalled

On Windows 8 and later, IE and Edge<=18 show a prompt that offers to take the user to the Microsoft Store to search for a protocol handler for the target scheme:

Win10NotInstalled

Unfortunately, this search is rarely fruitful because most apps are not available in the Microsoft Store.

Interestingly, Chrome and Edge76+ show nothing at all when attempting to invoke a link for which no protocol handler is installed. Surprisingly, there’s no notice even in the Developer Tools console; a particularly thorough debugger will only see a “(canceled)” request in the DevTool’s Network tab.

Upcoming change – Require HTTPS to Invoke

Chrome is looking at requiring that a page be served over HTTPS in order for it to invoke an application protocol.

 

In future posts, I’ll explore some other alternatives for Web-to-App communication.

-Eric


Notes

1 In some browsers, it’s possible to register web-based handlers for “AppProtocols” (e.g. maps: and mailto: might go to Google Maps and GMail respectively). This mechanism is relatively little-used.

2 Within Chromium, App Protocols are called “External Protocols.”

3 There are other ways to handle protocols, including COM and the Windows 10 App Model’s URI Activation mechanism but these are uncommon.

4 As an anti-abuse mechanism, the browser may require a user-gesture (e.g. a mouse click) before attempting to launch an App Protocol, and may throttle invocations to avoid spamming the user with an infinite stream of prompts.

5 Chrome’s prompt used to look much like IE’s.

6 Short of opening the Preferences for the profile in Notepad or another text editor. E.g. after choosing “Always open” for Microsoft Teams and Skype for Business, the JSON file %localappdata%\Microsoft\Edge SxS\user Data\default\preferences contains

“protocol_handler”:{“excluded_schemes”:{“msteams”:false, “ms-sfb”: false}}

To see the list in IE/Edge<=18, you can run a registry query to find protocols with WarnOnOpen set to 0:

reg query "HKCU\SOFTWARE\Microsoft\Internet Explorer\ProtocolExecute" /s
reg query "HKLM\SOFTWARE\Microsoft\Internet Explorer\ProtocolExecute" /s

7 Hardcoded schemes:

kDeniedSchemes[] = {“afp”,”data”,”disk”,”disks”,”file”,”hcp”,”ie.http”,”javascript”,”ms-help”,”nntp”,”res”,”shell”,”vbscript”,”view-source”,”vnd.ms.radio”}
kAllowedSchemes[] = {“mailto”, “news”, “snews”};

The EscapeExternalHandlerValue function:

// Escapes characters in text suitable for use as an external protocol handler command. // We %XX everything except alphanumerics and -_.!~*'() and the restricted // characters (;/?:@&=+$,#[]) and a valid percent escape sequence (%XX). EscapeExternalHandlerValue()

 

This is an introduction/summary post which will link to individual articles about browser mechanisms for communicating directly between web content and native apps on the local computer.

This series aims to provide, for each mechanism, information about:

  • On which platforms is it available?
  • Can the site detect that the app/mechanism is available?
  • Can the site send more than one message to the application without invoking the mechanism again, or is it fire-and-forget?
  • Can the application bidirectionally communicate back to the web content via the same mechanism?
  • What are the security implications?
  • What is the UX?

Application Protocols

Blog Post

tl;dr: Apps can register protocol schemes. Browsers will spawn the apps when navigating to the scheme.

Characteristics: Fire-and-Forget. Non-detectable. Supported across all browsers for decades, supported on desktop platforms, but typically not mobile platform. Prompts on launch by default, but usually can be disabled.

Native Messaging via Extensions

Blog Post – Coming someday. For now, see nativeMessaging API.

tl;dr: Browser extensions can communicate with a local native app using stdin/stdout passing JSON between the app and the extension. The extension may pass information to/from web content if desired.

Characteristics: Bi-directional communications. Detectable. Supported across most modern browsers; not legacy IE. Dunno about Safari. Prompts on install, but not required to use.

File downloads (Traditional)

Blog Post – Coming soon.

tl;dr: Apps can register to handle certain file types. User may spawn the app to open the file.

Characteristics: Fire-and-Forget. Non-detectable. Supported across all browsers. Prompts for most file types, but some browsers allow disabling.

File downloads (DirectInvoke)

Blog Post

Internet Explorer/Edge support DirectInvoke, a scheme whereby a file handler application is launched with a URL instead of a local file.

Characteristics: Fire-and-Forget. Non-detectable. Windows only. Supported in Internet Explorer, Edge 18 and below, and Edge 78 and above. Degrades gracefully into a traditional file download.

Local Web Server

Blog Post – Coming someday.

tl;dr: Apps can run a HTTP(S) server on localhost and internet webpages can communicate with that server using fetch/XHR/WebSocket/WebRTC etc.

Future restrictions: Will require caller be a secure context, will require internal website to respond to a CORS pre-flight.

Characteristics: Bi-directional communications. Detectable. Supported across all browsers. Not available on mobile. Complexities around Secure Contexts / HTTPS, and loopback network protections in Edge18/IE AppContainers.

Local Web Server- Challenges with HTTPS

In many cases, HTTPS pages may not send requests to HTTP URLs (depending on whether the browser supports the new “SecureContexts” feature that treats HTTP://localhost as a secure context, and not as mixed content. As a result, in some cases, applications wish to get a HTTPS certificate for their local servers. This is complex and error-prone to do securely. Many vendors used a hack, whereby they’d get a publicly-trusted certificate for a hostname (e.g. loopback.example.com) for which they would later use DNS to point at 127.0.0.1. However, doing things this way requires putting the certificate’s private key on the client (where anyone can steal it). After the private key is released, anyone can abuse it to MITM connections to servers using that certificate. In practice, this is of limited interest (it’s not useful for broadly attacking traffic) but compromise of a private key means that the certificate must be revoked per the rules for CAs. So that’s what happens. Over and over and over.

The inimitable Ryan Sleevi wrote up a short history of the bad things people do here: https://twitter.com/sleevi_/status/1202046844240572416?s=20 after Atlassian got dinged for doing this wrong.

Prior to Atlassian, Amazon Music’s web exposure can be found here: https://medium.com/0xcc/what-the-heck-is-tcp-port-18800-a16899f0f48f

—–

Notes: https://wicg.github.io/cors-rfc1918/#mixed-content

Andrew (@drewml) tweeted at 4:23 PM on Tue, Jul 09, 2019:
The @zoom_us vuln sucks, but it’s definitely not new. This was/is a common approach used to sidestep the NPAPI deprecation in Chrome. Seems like a @taviso favorite:
Anti virus, logitech, utorrent. (https://twitter.com/drewml/status/1148704362811801602?s=03)

Bypass of localhost CORS protections by utilizing GET request for an Image
https://bugs.chromium.org/p/chromium/issues/detail?id=951540#c28

View at Medium.com

There exist WebRTC tricks to bypass HTTPS requirements https://twitter.com/sleevi_/status/1177248901990105090?s=20

How to do this right? There’s a writeup of how Plex got HTTPS certificates for their local servers.

 

Variant: Common Remote Server as a Broker

An alternative approach would be to communicate indirectly. For instance, a web application and a client application using HTTPS/WebSockets could each individually communicate to a common server which brokers messages between them.

HTML5 getInstalledRelatedApps()

While not directly an API to communicate with a local app, the getInstalledRelatedApps() method allows your web app to check whether your native app is installed on a user’s device. Learn more.

AppLinks in Edge/Windows

Allow navigation to certain namespaces (domains) to be handed off to a native application on the local device.

Legacy Plugins/ActiveX architecture

Please no!

Characteristics: Bi-directional communications. Detectable. Support has been mostly removed from most browsers. Generally not available on mobile. One of the biggest sources of security risk in web platform history.

Android Intents

Similar to App Protocols, a web page can launch an application to handle a particular task. Learn more.

Android Instant Apps

Basically, the idea is that navigating to a website can stream/run an Android Application. Learn more.

As we rebuild Microsoft Edge atop the Chromium open-source platform, we are working through various scenarios that behave differently in the new browser. In most cases, such scenarios also worked differently between 2018’s Edge (aka “Spartan”) and Chrome, but users either weren’t aware of the difference (because they used Trident-derived browsers inside their enterprise) or were aware and simply switched to a Microsoft-browser for certain tasks.

One example of a behavioral gap is related to running ClickOnce apps. Update: ClickOnce support is now available in Edge 77+. See the end of this post.

ClickOnce is a Microsoft application deployment framework that aims to allow installation of native-code applications from the web in (around) one click.

Chrome and Firefox can successfully install and launch ClickOnce’s .application files if the .application file specifies a deploymentProvider element with a codebase attribute (example):

InstallPrompt

Installation prompt when opening an .application file.

However, it’s also possible to author and deploy an .application that doesn’t specify a deploymentProvider element (example). Such files launch correctly from Internet Explorer and pre-Chromium Edge, but fail in Firefox and Chrome with an error message:

ApplicationCannotBeStarted

ClickOnce fails for a downloaded .application file.

So, what gives? Why does this scenario magically work in Edge Spartan but not Firefox or Chrome?

The secret can be found in the EditFlags for the Application.Manifest ProgId (to which the .application filename extension and application/x-ms-application MIME type are mapped):

ApplicationManifestRegistry

Registry settings for the Application.Manifest ProgId.

The EditFlags contain the FTA_AlwaysUseDirectInvoke flag, which is documented on MSDN as 

FTA_AlwaysUseDirectInvoke 0x00400000
Introduced in Windows 8. Ensures that the verbs for the file type are invoked with a URL instead of a downloaded version of the file. Use this flag only if you’ve registered the file type’s verb to support DirectInvoke through the SupportedProtocols or UseUrl registration.

If you peek in the Application.Manifest’s Shell\Open\Command value, you’ll find that it calls for running the ShOpenVerbApplication function inside dfshim.dll, passing along the .application file’s path or URL in a parameter (%1):

“C:\Windows\System32\rundll32.exe” “C:\Windows\System32\dfshim.dll”,ShOpenVerbApplication %1

And therein lies the source of the behavioral difference.

When you download and open an Application.Manifest file from Edge Spartan, it passes the source URL for the .application to the handler. When you download the file in Firefox or Chrome, it passes the local file path of the downloaded .application file. With only the local file path, the ShOpenVerbApplication function doesn’t know how to resolve the relative references in the Application Manifest’s XML and the function bails out with the Cannot Start Application error message.

Setting FTA_AlwaysUseDirectInvoke also has the side-effect of removing the “Save” button from Edge’s download manager:

NoSave

…helping prevent the user from accidentally downloading an .application file that won’t work if opened outside of the browser from the Downloads folder (since the file’s original URL isn’t readily available to Windows Explorer).

Advice to Publishers

If you’re planning to distribute your ClickOnce application from a website, specify the URL in Visual Studio’s ClickOnce Publish Wizard:

Manifest

Specify “From a Web site” in the ClickOnce Publish Wizard.

This will ensure that even if DirectInvoke isn’t used, ShOpenVerbApplication can still find the files needed to install your application.

Workarounds

A company called Meta4 offers a Chrome browser extension that aims to add fuller support for ClickOnce to Chrome. The extension comes in two pieces– a traditional JavaScript extension and a trivial “native” executable (written in C#) that simply invokes the ShOpenVerbApplication call with the URL. The JavaScript extension launches and communicates with the native executable running outside of the Chrome sandbox using Native Messaging.

Unfortunately, the extension is a bit hacky– it installs a blocking onBeforeRequest handler which watches all requests (not just downloads), and if the target URL’s path component ends in .application, it invokes the native executable. Alas, it’s not really safe to make any assumptions about extensions in URLs (the web is based on MIME types, rather than filenames).

Edge 77+ Implementation

ClickOnce support is available in the new Edge 77+. It’s off-by-default, but can be enabled via edge://flags/#edge-click-once.

Note that the ClickOnce implementation in Edge 77+ will always prompt the user before the handler is invoked. In Edge 18/IE, sites in your Intranet/Trusted Zone could spawn the .application handler without any prompt. That’s because these older browsers respect the FTA_OpenIsSafe bit in the EditFlags for the application.manifest progid. The new Edge doesn’t really use Windows Security Zones as a primitive, and it thus does not support the FTA_OpenIsSafe bit.

 

Do you use ClickOnce to deploy your applications? If so, are you specifying the deployment URL in the manifest file?

-Eric

PS: Notably, Internet Explorer doesn’t rely upon the DirectInvoke mechanism; removing the EditFlags value entirely causes IE to show an additional prompt but the install still succeeds. That’s because IE activates the file using a MIME handler (see the CLSID subkey of Application.Manifest) much like it does for .ZIP files. The DirectInvoke mechanism was invented, in part, to replace the legacy MIME handler mechanism.