Protection from Account Thieves

in #utopian-io6 years ago (edited)

Repository

https://github.com/r351574nc3/we-resist-bot

Motivation

Recently, a number of accounts have had funds stolen immediately by using an active/master key in a transfer memo. It's immediate because there is a bot that waits for keys to appear in a memo or comment, and then transfers funds from the account immediately. See for example

New Features

Below is a sample post that is made to notify the account owner of the compromise.

I wanted to provide a way to protect people from this. Many of us in spite of being careful not to post keys, do it anyway. A bot will automatically detect this mistake and prey upon people before they can correct it. I wanted to provide a way to lock down a user's account before the bot can get to it. It does this by

  • Adding @the-resistance as an account manager
  • Remove existing keys from the account
  • Change the password

Details

Added check for transfer that have keys in them

case "transfer":
    try {
        const private_key = operation.memo
        let public_key = steem.auth.wifToPublic(private_key)
        wif_is_valid = steem.auth.wifIsValid(private_key, public_key)
        if (wif_is_valid && operation.from == 'perpetuator') {
            return processTransfer(operation, private_key, public_key)
        }
    }
    catch (error) {
            if (error.message.indexOf("Non-base58 character") < 0
                && error.message.indexOf("Expected version") < 0
                && error.message.indexOf("Index out of range") < 0) {
            console.log("Rethrowing ", error)
            throw error // rethrow
        }
    }
    break;

Prove it's a private key by generating a public key and verifying it. If this is the case, we then process the transfer.

Add a function for processing transfers

Assuming the transfer has a key in the memo

function processTransfer(transfer, private_key, public_key) {
    const password = steem.formatter.createSuggestedPassword();
    const account_name = transfer.from
    const new_active_keypair = generate_keys(account_name, password, "active");

A new password and set of keys are created

Make sure a record of the transfer and temporary keys is persisted in case anything goes wrong

Keys are temporary, so it's ok to store them

    // Save keys to datastore
    models.Recovery.create({ 
        username: transfer.from, 
        password: password, 
        memo: transfer.memo,
        privateKey: new_active_keypair.private_key,
        publicKey: new_active_keypair.public_key })
        .then((recovery) => {
            console.log("Recovery saved for ", transfer.from)
        })

Add authorities to the compromised account and remove the old keys

    // Add the-resistance as manager
    return addAccountAuth(private_key, transfer.from, "the-resistance", "active", 10000)
        .then((results) => {
            // Extra key for management
            return addKeyAuth(private_key, transfer.from, new_active_keypair.public_key, "active", 10000)
        })
        .then((results) => {
            // Remove the old key so things can't be stolen
            return removeKeyAuth(private_key, transfer.from, public_key, "active")
        })
        .then((results) => {

This makes it so that @the-resistance now controls the account on behalf of the user and has removed the key that was compromised.

Notification of compromised account

            // Post something to let the account holder know what to do.
            const context = {
                owner: transfer.from
            }
            return loadTemplate(path.join(__dirname, '..', 'templates', 'hijack.hb'))
                .then((template) => {
                    var templateSpec = Handlebars.compile(template)
                    return templateSpec(context)
                })
                .then((message) => {
                    var new_permlink = 'this-account-is-protected' 
                        + '-' + new Date().toISOString().replace(/[^a-zA-Z0-9]+/g, '').toLowerCase();
                    console.log("Commenting on ", transfer.from, new_permlink)

                    return steem.broadcast.commentAsync(
                        wif,
                        "", // Leave parent author empty
                        "abuse", // Main tag
                        transfer.from, // Author
                        new_permlink, // Permlink
                        "This Account is Protected by @the-resistance",
                        message, // Body
                        { tags: ['the-resistance'], app: 'we-resist-bot/0.1.0' }
                    ).then((results) => {
                        console.log(results)
                        return results
                    })
                    .catch((err) => {
                        console.log("Error ", err.message)
                    })
                })

The above notifies via steemit post of the compromise. The intent is that the user notices the post was made in his/her own account and attempts to contact @the-resistance for recovery

New methods for handling adding/removing authorities

+                active,
+                posting,
+                userAccount.memo_key,
+                userAccount.json_metadata
+            )
+        });
+}
+
+/**
+ * Removes an authority using a public key
+ * @param {*} signingKey 
+ * @param {*} username 
+ * @param {*} authorizedKey 
+ * @param {*} role 
+ */
+function removeKeyAuth(signingKey, username, authorizedKey, role) {
+    return steem.api.getAccountsAsync([username])
+        .map((userAccount) => {
+            const updatedAuthority = userAccount[role];
+            const totalAuthorizedKey = updatedAuthority.key_auths.length;
+            for (let i = 0; i < totalAuthorizedKey; i++) {
+                const user = updatedAuthority.key_auths[i];
+                if (user[0] === authorizedKey) {
+                    updatedAuthority.key_auths.splice(i, 1);
+                    break;
+                }
+            }
+
+            /** Release callback if the key does not exist in the key_auths array */
+            if (totalAuthorizedKey === updatedAuthority.key_auths.length) {
+                return null;
+            }
+
+            const owner = role === 'owner' ? updatedAuthority : undefined;
+            const active = role === 'active' ? updatedAuthority : undefined;
+            const posting = role === 'posting' ? updatedAuthority : undefined;
+
+            return steem.broadcast.accountUpdateAsync(
+                signingKey,
+                userAccount.name,
+                owner,
+                active,
+                posting,
+                userAccount.memo_key,
+                userAccount.json_metadata
+            );
+        });
+}

Added template loading to post a notification that the account was compromised

+
+function loadTemplate(template) {
+    return fs.readFileAsync(template, 'utf8')
+}
 
 function current_voting_power(vp_last, last_vote) {
     var seconds_since_vote = moment().add(7, 'hours').diff(moment(last_vote), 'seconds')
@@ -297,12 +303,204 @@ function processComment(comment) {
         })
 }

Added a function for generating keys from a new password

+function generate_keys(account, password, role) {
+    const private_key = steem.auth.toWif(account, password, role);
+    const public_key = steem.auth.wifToPublic(private_key);
+    return { private_key: private_key, public_key: public_key };
 }

GitHub Account

https://github.com/r351574nc3

Sort:  
  • Code is nice and readable, great commit and pr format.
  • Do you intend anyone to be running the bot or this is for the resistance only?

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Did this end up unapproved then?

Are the rewards declined?

I sent this before discussing in the discord help channel. Turns out it was a bot issue, but still no upvote.

The software is not exclusive to @the-resistance. It is open, free, and configurable for anyone. If there is another that wants to run this, that would be great ^_^

Messages are handlebars templates https://github.com/r351574nc3/we-resist-bot/tree/master/app/helpers/templates

The configuration could be more elegant. https://github.com/r351574nc3/we-resist-bot/blob/master/app/config/index.js

The bot is intended to recognize downvote abuse and defend against it through upvoting and automated comments.

We are expanding into other abuse areas like phishing which is the reason for this change. Unfortunately, the bot is not configurable to what type of abuse to manage. I think that would be a good suggestion for the roadmap.

@r351574nc3 Thank you, that answers that!

Good luck with the project. 👍

Vote r351574nc3 for witness.

This is awesome. Is there a way you can make sure that your bot is faster than the other one who is trying to steal the funds?

Actually, in hindsight, it might be far less intrusive to just move stuff into savings and leave a post instead of locking down the account.

Yes, it is less intrusive not to lock down the account. On the other hand, I think the most important thing is that the account gets protected.​

I think that's the consensus I've been hearing.

I have noticed that the bot stealing the funds seems to do it within a minute, and this has responded faster than that. I'm pretty confident it's faster. I think some improvements I can make are to

  1. Move funds into savings
  2. Accomplish all tasks in a single transaction

I run a bot with similar motive which recently got its first success. It is good to see more people are joining to help mitigate this issue.

Viva la resistance !

FD

sneaky-ninja-sword-xs.jpg
Sneaky Ninja Attack! You have just been defended with a 14.28% upvote!
I was summoned by @r351574nc3. I have done their bidding and now I will vanish...

woosh
A portion of the proceeds from your bid was used in support of youarehope and tarc.

Abuse Policy
Rules
How to use Sneaky Ninja
How it works
Victim of grumpycat?

Coin Marketplace

STEEM 0.32
TRX 0.11
JST 0.034
BTC 66004.40
ETH 3243.40
USDT 1.00
SBD 4.19