Stealing app private keys on Blockstack

Blockstack is a blockchain-based ecosystem where users retain control over their identities and data. But the security of your data lies with the developers of the decentralised applications being used. Is it possible for a third party to access private user data by abusing the application; and if so, what can be done about it? The goal of this article is to show that it is in fact possible to steal app private keys and access such data even if the app developer(s) had the best intentions. Some popular Blockstack applications will be evaluated for this purpose. What follows herein is not an in-depth analysis but rather a quick assessment to expose what could potentially be a more widespread issue.

One must first understand how Blockstack authentication works. For those new to the subject, refer to the Blockstack documentation to read the specifics. The basic concept is simple:

  • Users have so-called Blockstack identities. These identities live inside the Blockstack browser (browser.blockstack.org by default) and are derived from a master private key. It is important that the master and identity private keys never leave the browser.
  • Blockstack identities are used to sign into decentralised applications. When a user authenticates with an application, an application private key is derived from the identity private key and passed to the application.
  • The app private key is then used by the application to interact with Gaia, and to decrypt and sign data.

Thus, in order to access another user’s data, one of the aforementioned private keys must be obtained. Stealing the master or identity keys from the Blockstack Browser is beyond the scope of this article. Instead, the focus will be on the app private keys as these are readily accessible while a user is signed into a Blockstack application.

Two Blockstack identities are created for this experiment: keythief.id.blockstack and keyvictim.id.blockstack. The goal is to use “Keythief” to steal the app private key from “Keyvictim”. A simple command-line utility was built and used to make it easier to interact with Gaia data buckets directly.

To be able to steal a user’s app private key from within a Blockstack application, it needs to fulfil two requirements: it uses Gaia for data storage, and offers some sort of file sharing capability — that is, the ability to access and display another user’s data. A few candidates will be examined.

Sigle

Sigle describes itself as a “beautiful decentralized & open source blog maker” and is featured on the official Blockstack website. It uses Slate to provide users with a rich text editor to write blog posts. The editor is rather locked down and only allows for a few elements — some headings, lists, and images, and so on — to be embedded. Links can be added; however, so what happens if the age-old javascript:alert("I hope you cannot see this") is inserted?

It works, one can navigate to the blog post and invoke the script by clicking on the link. Can it be used to steal the app private key of Keyvictim? The key is found inside the user data object retrieved by calling loadUserData()on the Blockstack session object. But there is no need go that far, as the user data is also stored in the browser’s local storage under “blockstack” or “blockstack-session”. Try it out by authenticating with a Blockstack application and pasting the following in the browser console: localStorage.getItem('blockstack-session'). Substitute “blockstack-session” with “blockstack” if nothing shows up.

Going back to Sigle, all that is needed is a basic snippet that grabs the key from the browser storage and sends it to an external server when the link is clicked.

A private post is then created by Keyvictim — those are encrypted and only accessible by the owner. It will be used to confirm the key theft.

Now that the stage is set, one merely need to click on the dangerous link in Keythief’s Sigle post. Surely enough, the server is hit and Keyvictim’s app private key for Sigle is leaked:

Using the aforementioned command-line utility, Keyvictim’s bucket is accessed and all files listed. The encrypted private post is decrypted and read as if it were one’s own.

Bitpatron

Bitpatron is a decentralised Bitcoin alternative to Patreon, a platform for content creators to generate revenue. Users can set up paid subscriptions and post content only subscribers can see. Just like Sigle, it is featured on the official Blockstack website. The rich text editor itself is Froala, which generates HTML. The same trick as for Sigle can be used to embed script using a javascript: link. But this time, more interesting avenues are available. After examining the raw files on Gaia, it is found that posts are stored in “myFiles.json”.

Simply adding JavaScript in a script element does not work because of the way the posts are added to the page — the code will merely be inserted but not executed. Still, there are many ways to get JavaScript code to run. It is possible, for instance, to insert a bogus image with an “onerror” event handler that will grab the app private key from the browser storage and send it to an external server. The final result looks like this:

After the file is modified and uploaded to Gaia, one only needs to open Keythief’s post for the app private key to be stolen. The way it works is that as soon as the page is loaded, the browser will attempt to load the bogus image. Since it does not exist, it will execute the code of the error handler. A broken-image icon shows up on the page, but it only requires one additional directive to hide it and make the attack truly invisible. The server log gains another entry:

Anything pertaining to Keyvictim’s Bitpatron page can now be changed. Inserting the key-stealing snippet into the latest posts is an obvious move. All of the premium content can likewise be accessed freely.

Graphite Docs

Graphite Docs’ tagline reads: “[c]ollaborate on documents, share files, collect information. All private. All encrypted. All with Graphite.” It is promoted as being a decentralised Google Docs replacement and is also featured on the official Blockstack website. Users can collaborate on documents, share them via public links, and more. It, too, uses Slate as the text editor. A key difference with Sigle, however, is that Graphite Docs appears to store public read-only documents as pre-rendered HTML on Gaia.

A quick edit confirms that the content is displayed on the page as-is. It is therefore trivial to employ any of the prior techniques to steal a visiting user’s app private key. Another bogus image element with malicious error handler is inserted and uploaded back to Gaia.

Once it is done, a private document is created by Keyvictim to serve as the target.

It works exactly like with Bitpatron: once the public document is opened in the browser, the app private key is stolen automatically. Keyvictim’s key thus appears in the server log:

All documents created by Keyvictim using Graphite Docs, be it public or private, can now be accessed.

Xordrive

The next one on the list is Xordrive, an encrypted decentralised cloud storage application. It allows users to securely share files with other users or by generating a public link, much like many centralised cloud storage providers. An intriguing feature is the file preview window which displays files on the same page. It seems to cover all bases as anything scriptable is displayed as plain text. Looking at the underlying files on Gaia also does not immediately provide anything useful to work with.

Nonetheless, a closer look at the code reveals that SVG image files (Scalable Vector Graphics) are displayed using an inline frame. It is possible for an SVG file to contain scripts but they will only be executed if the file is loaded as a document. An inline frame does precisely this, but since the SVG is loaded as a data URL, it has no access to the top frame due to the browser’s same-origin policy. The app private key can therefore not be extracted through these means. Something that can be done is to redirect the frame to another page.

But one can question how useful it really is. A phishing page of some sort can be created, although that still does not make it possible to steal the app private key.

The fact that Xordrive is using data URLs might have been a happy accident. (A fact later confirmed by the developer.) Things would have been much different if the file was loaded as a blob instead. Blobs adopt the same origin as the effective script origin, making it possible to retrieve the app private key using a specially crafted SVG image. To illustrate, upload any SVG file to Xordrive, open it in a preview window and paste the following in the browser console:

The example code will create an SVG file as a blob and set it as the frame source. Once executed, the top frame’s browser storage appears in the console. (It does not post anything to an external server and is safe to run.) Fortunately, Xordrive does not use blobs in conjunction with inline frames but other Blockstack applications might. In the end, stealing Keyvictim’s app private key for Xordrive proved unsuccessful.

Other applications

The last app featured on the official Blockstack website that fulfils the requirements is Debut, a social media platform. There do not appear to be any distinct issues. On top of that, it also uses Radiks, a central indexer that retains copies of user data. The indexer makes it a bit more tricky to modify files as what is stored on Gaia is only kept as a reference for the user itself. It is certainly possible to post files to the indexer but given the added work, it can be kept as a future topic of analysis. A few other Blockstack applications built on Radiks were also skipped for this reason.

Further mentions:

  • Arcane Docs was of particular interest given the magnitude of the application. On closer inspection, documents look to be stored in a binary format. It was decided to skip it as well for now.
  • Dpage uses subdomains that never authenticate the user (and it would be a different app domain either way). Embedding inline frames to the top domain does not help much due to browser cross-origin policies. In the future, the proposed document.requestStorageAccess() could be used to trick an unsuspecting user into granting access to the browser storage and thus making it possible to steal the app private key.
  • pDrive uses data URLs to display images and a canvas to draw SVG files, making it impossible to run embedded scripts.

Preventing the attack

The methods described in this article are not unique to the Blockstack ecosystem and are the result of improperly sanitising user input. Still, such cross-site scripting (XSS) attacks are more severe for Blockstack applications because it leads to a total and permanent compromise of the victim’s Gaia data bucket. The generation of the app private key is deterministic, which means that signing in with the same identity always produces the same app private key. In other words, once the app private key for a particular identity is stolen, that identity can never safely be used again for that particular application. The Blockstack Browser currently does not offer a way to blacklist a compromised app private key so that the same identity may be used with a fresh key.

What could the application developers have done to prevent app private keys from getting stolen? The Blockstack model is arguably a new paradigm for developers. With conventional web applications, the server is the ultimate authority when it comes to data entry. With Blockstack and Gaia, on the other hand, everything happens on the client-side. One of the cornerstones of the Blockstack ecosystem is that the user retains control of the data. A Blockstack application might sanitise user input before it is uploaded to Gaia but nothing prevents the user from modifying that data directly. It is therefore paramount that data validation and sanitising happens after the data is retrieved.

Browser security models like the same-origin policy and the content security policy (CSP) can additionally be used to harden the application. With the right CSP set up, all script sources that are not whitelisted will be blocked. Inline resources such as javascript: links will also be blocked unless explicitly allowed. Note that if content delivery networks (CDNs) are used, those will have to be whitelisted as well.

Which results in the following security error when clicking javascript: links:

Another valid option is to display all user content in sandboxed iframes with scripts (or more) turned off. Inline frames create a new execution context which can be restricted using the sandbox attribute.

Setting the sandbox attribute is crucial; without it, the code will be executed. Running the above example, the browser console reports:

The last security feature worth mentioning is subresource integrity (SRI), especially if content is delivered from different domains. It enables the browser to verify the integrity of external resources and will reject them if they are manipulated.

Conclusion

The assessment revealed issues with popular Blockstack applications which made it possible to inject malicious code to steal app private keys of unsuspecting users. Leaked keys completely compromise the Gaia app buckets in question. Users who had a key leaked can no longer safely use that identity for that particular application as the Blockstack authentication mechanism does not provide a way to generate a fresh app private key.

Several methods to prevent these cross-site scripting (XSS) attacks exist and should be considered by developers creating Blockstack applications. Apart from checking data coming from Gaia for integrity, the content security policy (CSP) and sandboxed iframes provide security for the cases where sanitising the data fails or proves infeasible.

By shedding light on these issues, the aim is to make developers more aware of the added risks in developing applications for the Blockstack ecosystem and to offer different ways to address them.

Responsible disclosure: the developers of the applications mentioned in this article were contacted and made aware of the issues before this article was released.

Author’s Note

I theorised the possibility of stealing app private keys in the manner discussed in this article when I was introduced to the Blockstack ecosystem. After developing a few applications of my own, I realised the threat arbitrary Gaia data poses for multi-user applications. I am currently working on a novel approach to tackle this and other issues which I hope to reveal in the near future. If you are interested in these developments, feel free to contact me on Twitter via @marvinjanssen.

Marvin works at the Stacks Foundation to bring about the user-owned internet and is a co-founder of Ryder, the first identity hardware wallet of its kind.