I just noticed the following possible security flaw:
There are some URLs within the “secure” domain of canister applications that are passed through by the HTTP Gateway and the service worker right to the node. For example, https://identity.ic0.app/api/v2/status
will show you the CBOR value as produced by the replica. This happens both when you have no service worker installed (e.g. with curl
), meaning that the Boundary Node has a forward configured here (And I wish we’d see the code to check), and when you have the service worker installed (due to these lines).
Why is this a problem? It means a single node provider (or even someone controlling the network around the node, possibly) can mount an attack that will break the security of the application, in this case the Internet Identity. My assumption here is that the node provider can either change the replica, or can MITM the connection between boundary node and the replica.
-
They create an innocent looking web site that, maybe in a hidden
iframe
, load one of these pass-through-URLs. -
They make their victim visit that website. This will cause them to load that URL.
The boundary node will round-robin the request, so it may hit one of the other replicas. To make this less easily detectable, maybe choose a URL path that causes a 404 or return plain text.
Repeat this until it hits the malicious node (or include many
iframes
, I guess…) -
The malicious replica, instead of returning a 404 or whatever, it returns a webpage that loads malicious JS code.
-
Now the attacker was able to inject malicious JS that runs in the
https://identity.ic0.app
origin. If they now also trick the user in touching their fingerprint sensor or yubikey, they can otherwise silently sign into the Internet Identity and then interact with Internet Identity enabled applications. For example, stake all ICPs for 8 years, or post bad jokes on DSCVR.
To avoid this:
-
Both service worker and HTTP Gateway must not have any uncertified pass through routes for non-raw URLs. No
/api/…
, no/_
.Instead, the HTTP Gateway must always return the service worker (or, possibly, other trustworthy results, e.g. results it produces itself, or where it checked the certification, or other sanitation).
And the service worker must only allow responses where it could check the certification. Again, no pass-through routes, and also no unredacted error pages (the latter is done correctly, I think.)
-
Whoever needed these paths (maybe the frontend application?) before must access them using a different URL. For
/api/
access, simply usinghttps://ic0.app
(no canister name) might work. This is the defaultIC_GATEWAY
in the rust agent, but not the JS agent, it seems.Although someone really needs to check what happens if a malicious node can inject JS that runs in that origin, and whether that can take over subdomains then.
(I wondered if this issue serious enough to require a non-public disclosure, but it seems not needed. At this point of the project I expect seriously malicious node providers might have better ways to cause havoc. Also, everything that I describe here can be done by a malicious boundary node provider anyways, and that seems unavoidable in the current architecture, so in practice this attack doesn’t change a lot.)