Security Advisory: Sign-In with Ethereum/Bitcoin/Solana (SIW E/B/S) prone to Phishing

:warning: Important Disclaimer / Confidentiality
The advisory was responsibly disclosed to all known users of SIW* on July 1st, 2025. It is now made available for everyone since the embargo period of 90 days has ended.

TL;DR

The IC-SIW* implementations allow ICP users to sign into dapps using wallets like Metamask, Phantom, etc. Unfortunately, many of these login flows are prone to phishing attacks, where a malicious entity may gain control over a victim’s identity. This advisory publicly discloses the phishing issue, assesses the risk, and gives recommendations for hardening security.

Given the high risk, it is strongly recommended that users of IC-SIW* harden their implementations against phishing, along the lines of the recommendations in this advisory.


Introduction

The IC-SIW* (ic-siwb, ic-siwe, ic-siws) implementations allow ICP users to sign into dapps using their wallet for another blockchain by signing a specific message using that wallet. An ICP principal is derived from the external wallet address/public key which only the wallet owner will have access to. This proof of possession is provided by the signature.

Specifically, the IC-SIW* standard follows this general implementation:

  1. The IdP canister exposes prepare_login, login and get_delegation methods
  2. The user (frontend) initiates the login by calling prepare_login. The canister returns a message compatible with the SIW* standard of the respective ecosystem.
  3. The message needs to be signed by the user’s wallet on the other blockchain network.
  4. The signed message is sent by the frontend to the canister’s login function.
  5. The IdP canister verifies the signature and checks if the public key retrieved is the one used during prepare_login.
  6. If successful, the canister generates a new delegation for the user.
  7. The user can retrieve the delegation via get_delegation and sign messages in the IC with a principal bound to the wallet address.

Issue

IC-SIWB, IC-SIWE, and IC-SIWS all take a similar approach: they map an external crypto wallet address to an ICP principal, regardless of which website (e.g. an ICP dapp) requested the sign-in. This results in a shared identity across different origins (e.g. domains of ICP dapps) for the same user.

This is like using “Login with Google” but if you do it, you get access to any other app where the user logs in with Google, strongly violating a user’s security expectations. This opens the door to Phishing attacks, as we explain in the following.

On ICP, Internet Identity adopts a per-dapp identity model, which enhances privacy and security for the end user. In this model, a user receives a different principal depending on the origin (i.e., the domain) of the dapp requesting the sign-in. As a result, dApps cannot correlate user behavior across different dapps. Additionally, malicious dapps attempting to trick users into signing in are prevented from reusing that identity elsewhere, helping to mitigate certain types of phishing attacks.

IC-SIWB / IC-SIWE / IC-SIWS that don’t offer this isolation are susceptible to these traditional web2-style phishing attacks. This is due to the fact that the identity provider canister doesn’t include the request origin in the delegation derivation.

This behavior could be motivated from a UX perspective whereby the user receives the same IC principal across multiple applications and there exists a one-to-one mapping between an IC principal and the corresponding BTC / ETH / SOL address. However, this opens up the user to phishing attacks. In this attack, the attacker uses a seemingly indistinguishable domain (for example, h0nest.com) and tricks the user to sign in for honest.com and extort a successful delegation for the user’s IC principal. With that delegation, the attacker would have access to funds in all the ledgers corresponding to the delegation’s IC principal. Also, if this principal has permissions on any web app (e.g. full account permissions as in II), the attacker gains all of the permissions associated with the principal.


Risk

An honest user can be tricked by a malicious actor (e.g. owning h0nest.com) to sign a login message for honest.com, and on receiving a successful delegation, can steal all the funds owned by the IC principal associated with the BTC / ETH / SOL address.

If the login mechanism is used by a DeFi application, the loss of funds could be very significant.

To make such phishing attacks succeed, the malicious actor could e.g. advertise an airdrop on h0onest.com on social media to trick users to sign in. Not every user might fall for it, but it is likely that some do, as is known from e.g. password phishing attacks in web2.


Risk Analysis

Risk Level: High

  • Likelihood: Likely
    An attacker can just create a frontend dapp which clones a popular DeFi website using the SIW* mechanism and phish users and extort funds.

  • Impact: Major
    This would result in loss of funds held in that IC address and no chance of recovery.


Recommendations

We discuss three approaches to address the issue. However, depending on the application, there may be other solutions as well.


1. Verifying the app origin in the wallet

This recommendation is not possible if your wallet doesn’t verify the origin specified in the message. Please see the second and third recommendations below. See Wallet compatibility matrix to understand which wallets support the verification.

The recommendation is only secure if and only if 1 and 2 are implemented properly.

  1. Wallets implement the Verifying the Request Origin part of the standard correctly. However, based on our analysis, only Phantom and Metamask seem to support this standard fully for Solana and Ethereum respectively.
  2. The SIW* IdP canister includes the origin in the delegation derivation. This requires adapting the SIW* IdP canister.

Flow:

  • The frontend calls the IdP canister method siwe_prepare_login with payload (Address, frontend origin). It receives a SIWE message from the canister. The SIWE message specifies the domain as the frontend origin.
  • The frontend initiates the call to the MetaMask wallet to sign the SIWE message. According to the specification, the wallet will verify the domain in the message is the one requesting the signature (frontend origin).
  • The frontend calls siwe_login with the signature.
  • The IdP canister verifies the signature and includes the frontend origin in the principal derivation.

Phishing flow outcome:
If h0nest.com prepares a SIWE message for honest.com and requests a signature from MetaMask, it wouldn’t sign it since the request origin doesn’t match.

From ERC-4361 — Verifying the Request Origin

Wallet implementers MUST prevent phishing attacks by verifying the origin of the request against the scheme and domain fields in the SIWE Message. For example, when processing the SIWE message beginning with “example.com wants you to sign in…”, the wallet checks that the request actually originated from https://example.com.

The origin SHOULD be read from a trusted data source such as the browser window or over WalletConnect (ERC-1328) sessions for comparison against the signing message contents.

Wallet implementers MAY warn instead of rejecting the verification if the origin is pointing to localhost.

SIWS specification (message verification snippet)
In Message verification,

// verify if parsed domain is same as the expected domain
if (data.domain !== expectedURL.host) {
  errors.push(VerificationErrorType.DOMAIN_MISMATCH);
}

// verify if parsed uri is same as the expected uri
if (data.uri && data.uri.origin !== expectedURL.origin) {
  errors.push(VerificationErrorType.URI_MISMATCH);
}

2. Verifying the app origin using a passkey (as a second factor)

This recommendation does not hold if the dapps holds the funds directly in the principal associated with SIW. delegation. Please see recommendation three in this case.

Alternatively, employ passkeys which bind key material to the app origin to make this secure, e.g. using one of these approaches:

  1. For any token transaction or privileged operation, in addition to using the SIW* principal, require a second step passkey authorization. Recovery (if you lose the passkey) could e.g. be addressed by allowing to transfer all assets to a predefined “rescue” account even with the SIW* delegation only.
  2. If an entire “session” should be authenticated, create an entirely new principal in the SIW* canister that requires both the wallet signature AND a passkey authorization. However, this poses issues with recovering passkeys which could lead to significant complexity.

3. Get user consent on every transaction (ICRC21 standard)

This recommendation provides a pathway for building flows where the wallet in use does not support the SIW standard, and the funds are directly held by the principal associated with the SIW* delegation.

In this case, we recommend considering the ICRC21 call consent messages standard. Here, the permission level of the SIW* IdP canisters are downgraded—from being able to sign delegations that can perform all transactions, to signing a specific canister call that can be safely displayed and signed by the corresponding wallet. The SIW* canister takes the role of the ICRC21 signer. The rough flow is as follows:

  1. The frontend (relying party) builds the transaction (e.g., transferring funds from the ledger) and calls the SIW* canister.
  2. The SIW* canister calls the target canister to fetch a consent message for the transaction via the ICRC-21 standard. The signer bears the responsibility of validating whether the rendered consent message matches the canister call parameters provided by the frontend. If the validation succeeds, the consent message is forwarded to the frontend.
  3. The frontend prompts the user to sign the ICRC-21 consent message using their wallet. The user is expected to validate the consent message, which details the transaction.
  4. The frontend submits the bundle to the sign_transaction_with_x (“sign-transaction with X”) method of the SIW* canister.
  5. If the signature of the consent message is valid, the SIW* canister signs the transaction and returns it to the frontend.
  6. The frontend submits the signed transaction, completing it successfully.

While phishing is still possible, the advantages of using ICRC21 in this setting are:

  1. A phishing attempt, rather than getting a delegation that can control all the funds associated with the principal, can only aim at signing individual transactions. While this can still be risky, it limits the scope of a potential attack.
  2. The wallet is used to sign individual transactions that contain explicit, human readable consent messages about transactions. It would hopefully be more likely (compared to signing delegations) that a victim verifies and spots a malicious transaction and rejects it.

Wallet Compatibility Matrix

Wallet Ecosystem Does it support SIW standard? Displays domain / URI Performs domain check Risk of phishing
Coinbase Wallet Bitcoin Not supported Not supported Not supported Not valid
Unisat Bitcoin No Yes No Medium
Bitget Bitcoin No Yes No Medium
Xverse Bitcoin No No No High
Phantom Bitcoin No Yes No Medium
Okx Bitcoin No Yes No Medium
Phantom Solana Yes Yes Yes Low
Coinbase Wallet Solana No Yes No Medium
Solflare Solana No Yes No Medium
Trust Wallet Solana No Yes No Medium
Metamask Ethereum Yes Yes Yes Low
Phantom Ethereum No Yes No Medium
Coinbase Wallet Ethereum No Yes No Medium
Bitget Ethereum No Yes No Medium

Relevant Links

  1. https://github.com/AstroxNetwork/ic-siwb
  2. https://github.com/kristoferlund/ic-siwe
  3. https://github.com/kristoferlund/ic-siws
  4. https://github.com/AstroxNetwork/ic-siwb/security/advisories/GHSA-xqx6-6wr2-m9pc
  5. https://eips.ethereum.org/EIPS/eip-4361
  6. https://github.com/ChainAgnostic/CAIPs/pull/122
  7. https://github.com/phantom/sign-in-with-solana
  8. https://github.com/dfinity/wg-identity-authentication/blob/main/topics/ICRC-21/icrc_21_consent_msg.md

7 Likes

Thank you for disclosing this! It does seem like a very severe issue. IIUC, even if there is no phishing attempt, even just another legitimate app using these libraries could control the user’s accounts on any other app. Even if the other developers stay honest, it means an XSS vulnerability on one app would give an attacker access to all apps.

I was actually just looking into Sign in with Ethereum the other day, so this is good timing. Beyond what was mentioned above, I see two more issues with the ic-siwe library (haven’t looked at the others):

  1. The session key is not part of the signed message, making the system vulnerable to MITM attacks, as far as I can tell. This is not a problem in web2 with HTTPS, but it means a malicious HTTP gateway or boundary node could take the signature from the user and replace the session key with a different key controlled by them.
  2. There seems to be no logout method or way to revoke the sesion keys. I guess this is why the default expiration time is set relatively low to half an hour?

I was actually thinking of implementing my own SIWE flow as follows:

  1. Instead of generating a random nonce in the canister, just generate the SIWE message client side with the session key’s public key as nonce. While you lose replay protection, this is not an issue at all, as the login only authorizes the session keys securely stored on the client.
  2. Rather than three methods (prepare_login, login, get_delegate) you now just need a single login method that checks the various fields of the sign in message (including issued-at and expiry).
  3. The canister does need to keep a list of valid session keys now that is checked on every request. But that’s a feature, not a bug, as you can now logout by revoking session keys. It’s a non-issue in a single canister setup, but if you have multiple canisters and want to avoid querying the auth canister, then you can follow the usual JWT flow: Have long living refresh keys that are signed as above (e.g. living for a year or 30 days since last action), and then use the refresh keys to generate short lived session keys (e.g. 5 minutes) that are signed by the auth canister and can’t be revoked.

Would be curious to hear feedback on that flow and whether I missed anything. IIUC ic-siwe also gives you some guarantees of having consistent principal between apps? That you would lose here, but it sounds like the reason for the vulnerabilities above and I also don’t see why you wouldn’t just pass the ETH address as identifier.

For the two issues mentioned,

  1. This should be solved here but the new version is not released. v0.2.0 by kristoferlund · Pull Request #21 · kristoferlund/ic-siwe · GitHub
  2. Yes, this is the case. The delegation issued by the siwe canister will be valid until expiry time and it is irrespective of any changes done to the canister (it can’t revoke an issued delegation for the session). This is the nature of how delegations work currently in the IC.

As for the alternative proposal, I would like to clarify how the proposed solution would solve the phishing attack? In both scenarios, the wallet can be tricked by a malicious website into signing a message for that ETH address.

1 Like

This should be solved here but the new version is not released. v0.2.0 by kristoferlund · Pull Request #21 · kristoferlund/ic-siwe · GitHub

Great, thank you!

As for the alternative proposal, I would like to clarify how the proposed solution would solve the phishing attack? In both scenarios, the wallet can be tricked by a malicious website into signing a message for that ETH address.

My alternative solution was more to address the other issues I raised. Having to log in again every 30min (or have unsafe long living session keys) does not seem a great UX to me.

To me the phishing issue is simply solved by verifying the origin for which the message was signed in the canister. I do get your point that only browser based wallets can actually verify the origin on the client side, but in any case the user is still shown the message text and has the opportunity to verify that the origin in the message matches the site they came from. If the user signs a malicious message, it’s the same as if they entered their bank username and password on a phishing site today. With great power comes great responsibility :slight_smile:

I’d also like to point out that especially in a web3 context, the origin might differ from the resource I want to access. I might want to access the resource cool-site.eth, but the origin is actually a gateway cool-site.eth.limo. If you have a strict 1:1 matching from origin to resource, you’re either depending on DNS (which is not decentralized), or you require the user to have a browser that can resolve decentralized names like ENS or IPFS.

Your alternative approaches (2) and (3) defeat the purpose of Sign-in-with-web3 in my opinion. If you’re signing every message anyways, you can just connect your wallet instead of signing in, as you do on most web3 apps today. And if you also require a passkey, why not just use the passkey to sign in then?

This is something that affects Toko and Catcha as we were planning to enable sign in (and hidden account creation) from Ethereum, Bitcoin and Solana.

Watching this thread with interest.

Cheers! I am the developer of IC-SIWE and IC-SIWS (developed outside of my DFINITY employment). A short first comment from me at this time, I might return for a longer post later.

In general, treat signing in with an Ethereum or Solana wallet as you would treat username/password logins. That is, just like on any website that uses username/password, there exists a phishing risk. A malicious website looking like the real one can always trick you into sharing your credentials and then misuse them. That is why companies like Google and Facebook employ a whole host of mitigation strategies to avoid this scenario. For instance, Chrome, the web browser, maintains a blocklist of malicious domains that have similar domain names to the Google account services. Most modern web services that accept username and password as a sign in method quickly prompt you to upgrade your security once you have logged in. This type of hardening should be performed when you use sign in with Ethereum or Solana as well.

In practice, the only sign-in method I’m aware of that fully mitigates this type of phishing risk is passkey authentication.

The core issue is establishing reliable assurance that you are interacting with the correct domain. With passkeys, that assurance is handled by the browser and operating system. The passkey is cryptographically bound to a specific domain, and you rely on the browser and underlying platform to enforce that binding and prevent it from being used on a spoofed site.

When using Sign-In with Ethereum or any authentication method other than passkeys, prompt users to upgrade their security after logging in by enabling some form of two-factor authentication. That makes phishing significantly more difficult.

4 Likes