Perkakas Developer

Node2Shell: npm audit tak bisa melihat RCE Brotli Node.js

CVE-2026-111497 ada di runtime Node.js, bukan di dependensi Anda. Di mana Express 5 dan fetch() membuka celah, plus audit singkat dan solusi sementara sebelum rebuild.

Rows of grey data cubes feed a server; one cracked cube leaks orange fluid past a small tray into memory cells
Rows of grey data cubes feed a server; one cracked cube leaks orange fluid past a small tray into memory cells

Bug di runtime, dengan exploit yang berjalan

Node.js merilis pembaruan keamanan pada Rabu, 21 Oktober: v22.23.4, v24.22.1 (Latest LTS) dan v26.12.1 (Latest Release). Rilis ini menambal 10 CVE, empat di antaranya berperingkat High. Tambalannya baru berumur dua hari. Yang berubah dalam 24 jam terakhir: ada yang berhasil mengubah CVE terparah di antaranya menjadi eksekusi kode di build standar.

Pada 22 Oktober, Ron Masas dari Imperva menerbitkan Node2Shell: kode kiriman penyerang berjalan di image resmi Node.js 24.22.0 untuk Linux arm64, di kontainer baru, tanpa debugger dan tanpa binary yang diinstrumentasi. Node.js 24.22.1 menolak input yang sama. Namanya merujuk ke PHP2Shell minggu ini, dan pelajarannya pun sama: kode yang rentan ada di mesinnya, bukan di aplikasi Anda.

Di bagian inilah kebanyakan tim akan keliru. Masalah ini tidak bisa diperbaiki lewat package.json. Celahnya ada di dekoder Brotli yang dibundel di dalam binary Node, jadi npm audit, Dependabot dan semua pemindai lockfile tidak punya apa pun untuk ditandai. Perbaikannya adalah runtime baru di setiap host, image dan runner CI yang Anda kelola.

Apa bugnya, menurut advisory

Advisory Node.js menjelaskan CVE-2026-111497 dengan lugas: penyerang yang bisa memasok data Brotli yang didekompresi aplikasi lewat node:zlib dapat merusak memori proses, yang bisa berujung pada crash, bocornya isi memori proses, atau eksekusi kode jarak jauh. Lini yang terdampak adalah 26.x, 24.x dan 22.x. Advisory itu mencatat "OpenAI Codex Security - Khai Tran and Sylvie Mayer" sebagai pelapor, serta mcollina dan aduh95 sebagai penambal. Tulisan Imperva menyebut pelapornya secara berbeda; advisory adalah catatan resminya, jadi pegang yang itu.

Dua kalimat lain di advisory itu penting kalau Anda masih menjalankan versi lama. Versi End-of-Life selalu dianggap terdampak setiap kali ada rilis keamanan. Dan buletin Vercel lebih terus terang: Node.js 20 tidak akan menerima perbaikan ini. Kalau masih ada sistem Anda yang berjalan di 20, sistem itu kini menjalankan bug kerusakan memori yang sudah diketahui, di dekoder yang mungkin diumpani data dari orang asing. Perbaikannya adalah pindah ke lini yang masih didukung.

The Hacker News melaporkan bahwa advisory tidak mencantumkan workaround apa pun di dalam Node.js. Tidak ada flag yang bisa dimatikan, tidak ada variabel lingkungan untuk menonaktifkan dekodernya. Pilihannya cuma dua: byte-nya tidak pernah sampai ke node:zlib, atau Anda menjalankan binary yang sudah ditambal.

Anda terpapar dari dua arah

Imperva membuktikan dua jalur serangan. Keduanya kode biasa yang tidak akan dipermasalahkan siapa pun saat code review.

Masuk: Express 5 mendekode Brotli untuk Anda

Jalur pertama adalah body request yang didekode oleh Express 5.0.0–5.3.0 atau body-parser 2.0.0–2.3.0. Imperva berhasil menjalankan kode lewat raw() maupun json(). Imperva juga berhati-hati menegaskan bahwa server HTTP Node.js polos tidak mendekode body Brotli yang masuk hanya karena menerimanya. Harus ada yang mendekompresi body itu. Di Express 5, yang melakukannya adalah parser bawaan.

Perubahan ini masuk diam-diam. Panduan migrasi Express 5 menyebut bahwa express.json(), express.urlencoded(), express.text() dan express.raw() kini mendukung dekompresi Brotli (Content-Encoding: br) untuk body request yang masuk, tanpa langkah migrasi apa pun untuk itu. Kalau Anda upgrade dari Express 4, kemungkinan besar Anda tidak pernah memutuskan untuk menerima body request Brotli dari internet. Tapi Anda tetap menerimanya. Klien mana pun yang memasang Content-Encoding: br akan membuat payload-nya didorong melewati dekoder yang rentan sebelum route handler Anda melihat satu byte pun.

Keluar: fetch() mendekode setiap respons

Jalur kedua berjalan ke arah sebaliknya: respons Brotli yang dibaca lewat fetch() bawaan Node. Penyerang tidak perlu mengirim apa pun ke Anda. Mereka cukup berada di ujung lain dari request yang Anda buat. Pengiriman webhook, pratinjau tautan, pengimpor URL, discovery metadata OAuth, scraper, apa pun yang mengambil URL kiriman pengguna: kalau server yang Anda panggil membalas dengan body Brotli rekaan, proses Anda yang akan mendekodenya.

Arah inilah yang tidak bisa ditolong proteksi di edge. Vercel memasang aturan WAF pada 21 Oktober yang memblokir jalur masuk, dan menyatakan aturan itu tidak mencakup respons yang diambil fungsi Anda dari server lain. WAF duduk di depan aplikasi Anda. Respons berbahaya datang dari belakangnya. Saran Vercel untuk pengguna self-hosted, kontainer dan CI: upgrade ke 22.23.4, 24.22.1 atau 26.12.1 sekarang juga, terlepas dari proteksi WAF.

Seberapa serius ini

Ini gambaran jujurnya, karena liputan media terbelah soal ini.

Exploit-nya belum andal. Imperva sendiri mengakuinya: pembuktiannya tidak berhasil di setiap percobaan. Mereka hanya menguji build arm64 dan hanya pada setup aplikasi mereka sendiri. Menurut The Hacker News, proof of concept publik sebelumnya hanya mencapai eksekusi kode di build yang sengaja dilemahkan, dan per 22 Oktober belum terlihat eksploitasi di dunia nyata.

Semua itu tidak membuat aman untuk menunggu. Exploit yang kadang berhasil di image resmi standar tetaplah exploit yang berfungsi. Kalau percobaan yang gagal hanya membuat satu worker crash, orchestrator Anda akan menyalakannya lagi dan penyerang bisa mencoba lagi. Advisory juga mencantumkan bocornya memori proses sebagai salah satu dampak, dan di layanan Node, memori itu bisa berisi token, kunci sesi dan kredensial database. Soal keandalan dan arsitektur lain biasanya cepat terpecahkan begitu metodenya publik, dan sekarang metodenya sudah publik.

Audit 10 menit, lalu solusi sementara

Ini urutan kerja yang saya sarankan. Sebagian besar diambil dari checklist Aikido.

1. Versi runtime di setiap host, runner dan image. Lockfile tidak akan memberi tahu Anda soal ini. Yang perlu Anda lihat adalah binary sebenarnya di setiap tempat kode berjalan. Di host dan runner CI, jalankan node --version. Untuk setiap image yang Anda deploy, bukan hanya yang di-build minggu ini, jalankan perintahnya di dalam image:

node --version
docker run --rm "$IMAGE" node --version

Apa pun di bawah 22.23.4, 24.22.1 atau 26.12.1 pada lininya masing-masing berarti rentan, dan apa pun di 20 atau lebih lama akan tetap rentan selamanya. Imperva mengingatkan bahwa inventaris paket bisa melewatkan runtime lama yang dibundel di dalam kontainer, jadi periksa image-nya langsung, jangan hanya percaya SBOM.

2. Base image yang di-pin. Kalimat Aikido soal ini layak ditempel di channel tim Anda: "A Dockerfile pinned to node:24.22.0 stays vulnerable no matter how often you rebuild it." Dockerfile yang terkunci ke tag itu tetap rentan seberapa sering pun di-rebuild. Naikkan tag-nya, rebuild, deploy ulang. Tag mengambang hanya membantu kalau proses rebuild benar-benar menarik image baru.

3. Versi mayor Express dan body-parser. Cari tahu layanan mana yang menerima body request Brotli:

npm ls express body-parser

Express 5 atau body-parser 2 di layanan yang menghadap publik berarti jalur masuk terbuka sampai runtime-nya ditambal.

4. Pemanggilan Brotli langsung. Cari brotliDecompress dan createBrotliDecompress di kode Anda. Setiap temuan adalah tempat Anda mendekode Brotli sendiri, dan Anda perlu tahu dari mana byte-byte itu berasal.

5. Fetch keluar ke host yang tidak Anda kendalikan. Daftar setiap jalur kode yang memanggil fetch() ke URL kiriman pengguna atau pihak ketiga. Jalur-jalur ini butuh runtime yang ditambal lebih dulu, karena tidak ada proxy di depan Anda yang melindunginya.

6. Runtime lain dan paket distro. Bun sudah diperbaiki di 1.4.4. Kalau Anda memasang Node dari distribusi Linux, paket itu mungkin menautkan library Brotli milik sistem, bukan yang dibundel. Dalam kasus itu, perbaikannya datang lewat paket Brotli distro, bukan lewat update Node. Periksa keduanya.

Solusi sementara untuk jalur masuk

Kalau sebagian rebuild butuh waktu berhari-hari, tolak body request berenkode Brotli di reverse proxy sampai rebuild-nya selesai. Periksa dulu access log Anda untuk klien sah yang mengirim Content-Encoding: br; kalau tidak ada, memblokirnya tidak merugikan apa-apa. Aturan sementara di nginx:

if ($http_content_encoding ~* "br") {
    return 415;
}

Pahami betul apa yang Anda dapat dari langkah ini. Aturan ini menutup jalur masuk lewat Express dan sama sekali tidak berpengaruh pada fetch() keluar. Layanan yang memanggil URL kiriman pengguna tetap butuh binary yang sudah ditambal, tidak ada jalan lain.

Rilis Node.js yang sama juga memperbarui sertifikat CA bawaan terkait kasus eMudhra yang kami bahas sebelumnya hari ini. Itu satu alasan lagi untuk rebuild dengan runtime baru hari ini, alih-alih menunggu penyegaran image rutin berikutnya.

Bacaan terkait

Komentar (0)

Belum ada komentar. Mulai percakapannya.

Tidak pernah ditampilkan atau dikirimi email — hanya disimpan sebagai hash.