Nicely asking our users to update the app through an XSS attack

Nicely asking our users to update the app through an XSS attack

Devbook is a desktop app that allows developers to search Stack Overflow, official documentation, and code on GitHub. It's the first step in building a search engine for developers.

onboarding-so-preview.png

We shared a crude first version of Devbook on Hacker News in December. HN folks seemed to like it. Devbook was on the front page and eventually got to the #5 spot. We were excited! People might actually like what we built!

Feedback and suggestions started coming in. It was time to work on an update. With this momentum, working on the next version was easy. We shipped a new update in the next few days and were ready to hear what people think.

Here came the horrible realization though - we forgot to ship the auto-update functionality in the first version. How will people already using Devbook update to the new version? This isn't a website or a mobile app. Devbook is a desktop app distributed outside of any app store. People don't just receive notifications about a new version.

About 500 users from HN were stuck on the first version, unable to update and we had no way to reach out to them. In the grand scheme of things, 500 users are not that many. For us though, it was crucial to get them to update to the new version and learn what they think. Those 500 users were everything. We had to come up with some way to inform them about a new version.

Time to get creative. We started thinking. The app is communicating with our server every time a user searches. So there might be a way to get our message across. Maybe every time when a user searches in Devbook we could change the content of the first result so it tells the user to update?

Then it hit us. What we were actually sending as a search response from our server was the actual Stack Overflow HTML. In Devbook, we just (dangerously and with great ignorance) injected the HTML into the app's frontend. That's great (if you ignore the security implications of course)! It means we can change the HTML to whatever we want!

Yeah, but how do we know which users should actually receive the custom HTML? We had people using two versions of the app - one without the auto-updater and one with the auto-updater. Well, here comes the ugliness of all this. As I said above, we were injecting the HTML code directly into the app's frontend. This means the HTML code isn't sanitized. With this, we can theoretically run any code we want. We could use Electron's API to find out the version of the app and show the update prompt based on that.

That's exactly what we ended up doing. We injected our custom script to the onerror event listener on the <img/> tag

<img style="display:none" onerror="update_code" src="#"/>

Here's the code prompting users to update

// Cleanup the old download reminder
clearTimeout(window.devbookUpdateHandle);
if (!window.isDevbookNewVersionCheckDisabled) {
  const remote = window.require("electron").remote;
  const appVersion = remote.app.getVersion();
  if (appVersion === "0.0.1") {
    function askForNewVersionDownload() {
      window.isDevbookNewUpdateSilenced = true;
      const shouldDownload = confirm("New Devbook version is available \\n\\n Click OK to download. \\n\\n You must install the new version manually!");
      if (shouldDownload) {
        remote.shell.openExternal("<new_version_url>");
      } else {
        clearTimeout(window.devbookNewUpdateHandle);
        const updateHandle = setTimeout(() => {
          askForNewVersionDownload();
        }, 8 * 60 * 60 * 1000);
        window.devbookNewUpdateHandle = updateHandle;
      }
    }
    if (!window.isDevbookNewUpdateSilenced) {
      setTimeout(() => {
        askForNewVersionDownload();
      }, 4000);
    }
  }
}
window.isDevbookNewVersionCheckDisabled = true;

This is the actual model that a user sees

Screen Shot 2021-03-22 at 3.35.52 PM.png

Eventually, about 150 users updated through this hack to a Devbook version that has an auto-updater.