Another zero-day critical vulnerability in the Argent-X StarkNet Wallet – The story

This bug enables any malicious website to fully control the wallet and perform transactions without the user's approval — We explained.
Account Abstraction
• Dec 7, 2022
7 min read
Another zero-day critical vulnerability in the Argent-X StarkNet Wallet – The story

TL;DR

The Argent-X Wallet for StarkNet had another critical vulnerability, this time on the application side. Once the wallet is unlocked, the vulnerability allows any website, without any user interaction, to steal all user’s funds from all Argent-X accounts.
This bug enables any malicious website, without asking consent from the user, to fully control the wallet, read all account information and perform transactions without user’s approval.

This vulnerability seems to exist at least since May 2022 and all Argent-X users on StarkNet mainnet were at risk for this entire time.

We have alerted the Argent team about this issue a couple of weeks ago and they’ve released a fix to the Chrome store within 18 hours. Since then enough time has passed for the vast majority of users to get the update.
This vulnerability makes it impossible to distinguish between legitimate and malicious transactions, but thankfully, Argent claims no funds were lost.

In this post we will unravel the story of how we discovered this vulnerability, as well as demonstrate the importance of a secure dApp<->wallet communication mechanism.

For those who want to follow the steps below and replay the hack at home, you can download any Argent-X version prior to 5.0.7 (for instance — this one), and run the examples yourself.

Background

As we have seen just two weeks ago in this zero-click vulnerability discovered and reported by Yoav Gaziel from our team here at Braavos, the Argent-X account contract had a critical security flaw. But that was the contract code that had the bug. So let’s assume that the Argent-X contract will be audited (again) and will be free of bugs. Is that enough for a wallet to be considered secure? What about the wallet client code? Recently, we at Braavos decided to kick its tires a bit and see.

The following is a description of that eventful afternoon-evening.

Messages, Messages

Going back in time a full year: When writing our first dApp on Starknet, we used the only wallet available back then — the Argent-X Wallet. (Today you can, and should, use Braavos).
While inspecting some messages sent inside our dApp, we saw a bunch of messages which we did not send, and seem to have originated from the Argent-X Wallet itself. We didn’t think much of it at the time.

But remembering that, sitting down that afternoon we thought we should take a look at the Argent-X messaging framework. Downloading and compiling the wallet code was easy — hats off to the Argent team for a good job there. One thing we noticed off the bat — they are still using Manifest V2. We will spare you the details, but let’s just say that the Chrome Store encourages (and soon will enforce) all extensions to move to Manifest V3, which is considered more secure by Google.

Now for the unboxing of the Argent-X messaging system.

A few words on how the Argent-X wallet works: it is a browser extension running in an isolated environment (isolated from the web page you are browsing in). Communication from the dApp to the wallet is done through messages, in a piece of code injected to each and every web page you visit. So far, this is pretty standard.

But when implementing such a scheme, you need to be very mindful of security.

You do NOT WANT to allow malicious messages from unknown origins to infiltrate your lines.

Looking at the Argent-X wallet code, we found the event types used by it pretty quickly:

Argent-X wallet code - event types

We dived right into the most intriguing message there — MiscenalleousMessage. Not only due to the typo (It’s ok, nobody can spell that word), but because, from our experience, developers tend to put the ‘good stuff’ in those catch-all classes.

Briefing through background/miscellaneousMessaging.ts, A specific message immediately caught our attention (will leave you hanging for a few more sentences as to which one). So we went ahead and compiled the Argent-X wallet and installed it on a chrome browser. It immediately opened a browser tab asking to set up the wallet, and after the usual password dance, the wallet automatically generated an account on testnet.

Then we opened a console and hacked this piece of code:

msg = { type: “RESET_ALL” }

extId = document.getElementById(“argent-x-extension”)?.getAttribute(“data-extension-id”)

window.postMessage({ …msg, extensionId: extId }, window.location.origin)

What we are doing here, is sending a RESET_ALL event to the Argent-X wallet from an unauthorized web page. This kind of hack should be stopped by the wallet security framework.

So we executed this piece of code in the browser console and opened the Argent-X wallet from the toolbar.

Boom 💥

The wallet has forgotten the setup we’ve just performed and is back to setup mode, opening a new tab just like the one it opens on sign-up.

Welcome to Argent X

Let’s recap what just happened: we sent a RESET_ALL message from a random web page to the Argent-X wallet, and it went all the way through.

At this point, we started to debate what we should try next. Going over the messaging source code, it was quite clear there is no security mechanism whatsoever, although the wallet is fully controlled by messages.

Reading All Accounts

The next thing we decided to explore was, can an unauthorized web page read all your account information.

For that, it’s not enough to send messages to the wallet. You would also need to receive some messages back. Of course, a well-secured wallet should not send this kind of information to an unauthorized web page.

Argent wallet account information

Looking at the code in background/accountMessaging.ts, we saw this GET_ACCOUNTS message which we planned to use. What caught our attention is the sendToTabAndUi() call with the GET_ACCOUNTS_RES result. We realized that the UI is the Argent-X UI that needs to get the account information, most likely to display the UI to the user while browsing the Argent-X popup. But why send this to the tab? This looks like a security flaw.

We reinstalled a fresh Argent-X wallet, and then hacked this piece of code in the console:

function print_event(e) {

console.log(e)

}

window.addEventListener(“message”, print_event)

msg = { type: “GET_ACCOUNTS” }

extId = document.getElementById(“argent-x-extension”)?.getAttribute(“data-extension-id”)

window.postMessage({ …msg, extensionId: extId }, window.location.origin)

Yet another Boom 💥 :

Argent wallet hack issue

The result of this flow is that any website (news, governmental, etc.) can read all your Argent-X addresses. But wait, the wallet was unlocked. Maybe a locked wallet would prevent this flaw? We locked the wallet from the UI and tried again.
Same result. Even a locked wallet returns, to any website, the list of all your accounts.
Looking into the Argent-X manifest, we saw they inject their extension code into any website. http, https., local… Any of those could do just this.

The Holy Grail — Executing Transactions

We will shorten the story here and briefly describe how the next few hours unfolded.

Looking in the natural place — the file background/transactions/transactionMessaging.ts,
the first message there is EXECUTE_TRANSACTION.

This is the holy grail.
Took us a few minutes to understand the transaction format required, but pretty soon, we had it:

msg = { type: “EXECUTE_TRANSACTION”, data:{transactions:{contractAddress:”0x049D36570D4e46f48e99674bd3fcc84644DdD6b96F7C741B1562B82f9e004dC7″,entrypoint:”transfer”,calldata:[“0x07074236724072a4949b7B170E5D5825b8195Ec278ED45E4BE9975186758EDa7”,”10000000000″,”0″]}} }

This message requests a transfer of 1e-9 ETH to some arbitrary address (our own address in this case. We were cheap, those Goerli ETH are hard to come by!).

We sent this message in the usual way, but guess what… even though we got back a EXECUTE_TRANSACTION_RES response from the wallet, no transaction seemed to take place.
Opening the wallet UI, we immediately understood why.

Argent wallet review send

As can be seen, the wallet accepted the message but is still asking the user to confirm this transaction. Will this line of defense prevent a hacker from delivering this transaction?

Looking at the response the wallet sent, it seems there is some parameter there called actionHash.
This actionHash must stand for something.

Argent wallet actionHash

Doing the natural thing, we dived into background/actionMessaging.ts, and there we saw this message:

Argent wallet issue message

We called this message, with the above actionHash, and…

💥 BOOM BOOM BOOM 💥

Argent wallet unauthorized ETH transaction

See this ‘1’ badge?
That’s the unauthorized, unsolicited ETH transfer transaction we’ve generated.

Note that this flow is possible when the user is browsing the web and the wallet is unlocked. Once unlocking the wallet, it remains unlocked for a long time, often till the user closes the browser.

Putting it all together

After taking a deep breath, we sat down for an hour to hack a little POC — a javascript page that does all that we’ve just described, with a sprinkle on top — using the message CONNECT_ACCOUNT to switch to any account in the wallet (including mainnet accounts).

It was pretty late already, but we realized we could not sit on this one. We contacted our Braavos fellows, briefed them and asked them to try out the POC. They were shocked but followed through. And it worked on all of their machines.

While one of us hacked the POC, the other one browsed through the git history of the Argent-X wallet, trying to estimate how long this has been going on, and it seems that it has been going on from day one. All Argent-X wallets have been vulnerable like that at least from May when Argent-X introduced StarkNet mainnet support.

From here on, we followed the Responsible Disclosure protocol, just like we did merely a week earlier when exposing the zero-click contract bug to the same Argent team.

Although this exploration caused us to miss the fantastic game between Spain and Costa-Rica (7–0!! What??) and the game between Belgium and Canada (1–0).
We are happy that we were able to assist the StarkNet ecosystem and ensure users’ funds are no longer in jeopardy.

Motty Lavie

Motty Lavie

Be The First To Know

Subscribe now and receive monthly updates and interesting news about Braavos and Starknet ecosystem.