Shai-Hulud kötü amaçlı yazılım saldırısı: Tinycolor ve 40'tan fazla NPM paketi enfekte oldu
(stepsecurity.io)- NPM ekosisteminde popüler olan @ctrl/tinycolor dahil 40'tan fazla pakete kendini yayan kötü amaçlı yazılım enjekte edildi; bu da geliştirme ortamlarındaki gizli bilgilerden CI/CD kimlik bilgilerine kadar zincirleme enfeksiyona yol açabilen bir tedarik zinciri saldırısına neden oldu. Enfekte sürümler npm'den kaldırıldı
- Saldırı yükü, npm kurulum sürecinde Webpack bundle'ını (
bundle.js, ~3.6MB) eşzamansız olarak çalıştırıyor ve ortam değişkenleri, dosya sistemi ve bulut SDK'ları üzerinden geniş kapsamlı kimlik bilgisi toplama gerçekleştiriyor - Kötü amaçlı mantık, NpmModule.updatePackage ile diğer paketleri zorla yamalayıp yayımlayarak kademeli yayılım sağlıyor; ayrıca GitHub Actions'a shai-hulud workflow'unu enjekte ederek kurumsal secret'ları toJSON(secrets) ile sızdırıyor
- Toplanan veriler, halka açık GitHub deposu 'Shai-Hulud' oluşturularak dışarı aktarılıyor; süreç meşru geliştirme faaliyetleri gibi gizlendiği için tespit edilmesi zor
- AWS/GCP/Azure/NPM/GitHub token'larına ve metadata endpoint'lerine erişim ile TruffleHog tabanlı secret taraması gibi teknikler gizlice kullanılıyor
- Paketlerin derhal kaldırılması, depoların temizlenmesi ve tüm kimlik bilgilerinin değiştirilmesinin yanı sıra CloudTrail/GCP Audit loglarının incelenmesi, webhook'ların engellenmesi, branch protection/Secret Scanning/cooldown politikalarının devreye alınması gerekiyor
Affected Packages
- Toplam 195 paket/sürüm raporlandı. Öne çıkanlar arasında @ctrl/tinycolor(4.1.1, 4.1.2), çok sayıda @ctrl/ namespace paketi, @crowdstrike/ modül grubu, ngx-bootstrap/ngx-toastr/ng2-file-upload/ngx-color gibi Angular/web UI ekosistemi genelindeki paketler, @nativescript-community/ ve @nstudio/ mobil yığını, teselagen/ yaşam bilimleri araç zinciri, ember-*, koa2-swagger-ui, pm2-gelf-json, wdio-web-reporter yer alıyor
- Her paket için tam sürümler kaynak tablodan kontrol edilmeli ve bu sürümlerin kullanılıp kullanılmadığı dikkatle çapraz doğrulanmalı
- Örnek:
@ctrl/ngx-emoji-mart 9.2.1, 9.2.2,@ctrl/qbittorrent 9.7.1, 9.7.2,ngx-bootstrap 18.1.4, 19.0.3–20.0.5,ng2-file-upload 7.0.2–9.0.1gibi geniş bir etki alanı söz konusu
- Örnek:
Immediate Actions Required
Identify and Remove Compromised Packages
- Projede enfekte paketlerin bulunup bulunmadığını kontrol edin:
npm ls @ctrl/tinycolorgibi komutlarla inceleyin - Enfekte paketleri hemen kaldırın:
npm uninstall @ctrl/tinycolorgibi komutları çalıştırın - Bilinen bundle.js hash'lerini arayarak yerel izleri kontrol edin:
sha256sum | grep 46faab8a...kullanın
Clean Infected Repositories
- kötü amaçlı GitHub Actions workflow'unu silin:
.github/workflows/shai-hulud-workflow.ymldosyasını kaldırın - Uzakta oluşturulan shai-hulud branch'ini tespit edip silin:
git ls-remote ... | grep shai-huludardındangit push origin --delete shai-huludkomutunu çalıştırın
Rotate All Credentials Immediately
- NPM token'ları, GitHub PAT/Actions secret'ları, SSH anahtarları, AWS/GCP/Azure kimlik bilgileri, DB bağlantı dizeleri, üçüncü taraf token'ları, CI/CD secret'ları dahil olmak üzere her şeyin tamamen değiştirilmesi gerekiyor
- AWS Secrets Manager/GCP Secret Manager içinde saklanan öğeler dahil tam rotasyon gerekli
Audit Cloud Infrastructure for Compromise
- AWS: CloudTrail içinde
BatchGetSecretValue,ListSecrets,GetSecretValueçağrılarının zamanını ve desenlerini inceleyin; IAM Credential Report ile olağandışı anahtar oluşturma ve kullanımını kontrol edin - GCP: Audit Logs üzerinden Secret Manager erişim kayıtlarını inceleyin, CreateServiceAccountKey olaylarının olup olmadığını doğrulayın
1 yorum
Hacker News yorumu
npm’de barındırılan paketleri kullanan biri olarak, tüm bağımlılıkları ve onların bağımlılıklarını doğrudan izlemek gerçekçi bir yöntem gibi gelmiyor; ayrıca TypeScript/JavaScript uzmanı da değilim, bu yüzden saldırganın gizlediği kötü amaçlı kodu kolayca fark edebileceğimi sanmıyorum. Son dönemde düşündüğüm şey, güncellemeleri “gecikmeli mod” ile yapmak; yani en son sürüme değil, yalnızca belirli bir süre geçmiş sürümlere güncellemek. Bir paketin yaklaşık 6 hafta boyunca dış dünyaya açık kalması durumunda kötü amaçlı kodun ortaya çıkarılmış olma ihtimalinin yüksek olduğunu varsaymak gibi. Elbette bu kusursuz bir yöntem değil; güvenlik sorunu olduğunda istisnai olarak en güncel güncellemeyi hemen uygulamaya da izin veren bir araç olsa iyi olurdu
Makalede doğrudan bahsedilen yöntem olarak NPM Package Cooldown Check diye bir özellik var. Kurumun belirlediği süre içinde (varsayılan 2 gün) yayımlanmış bir paket sürümü pull request’e eklenirse derleme otomatik olarak başarısız oluyor. Tedarik zinciri saldırılarının büyük kısmı 24 saat içinde tespit edildiği için, çok kısa bir bekleme bile güvenlik maruziyetini azaltabiliyor
Tüm bağımlılıkları baştan sona incelemek zor olduğundan, mümkün olduğunca bağımlılık sayısını azaltmak ve yalnızca iyi bilinen, güvenilir paketleri kullanmak gerektiğini savunmak istiyorum. Hatta tüm yazarlarına güvenecek kadar kontrollü bir ortam yoksa, bir miktar 'not-invented-here' yaklaşımını korumak mantıklı bir tercih
package.jsoniçinde sürümleri açıkça pin’leyip,package-lock.jsoniçinde belirtilen sürümler dışında bir şey kurmamak içinnpm cikullanma alışkanlığım var. CI’danpm auditçalıştırıp pakette zafiyet ortaya çıkarsa uyarı alıyorum. Bunu yapınca paketler neredeyse “dondurulmuş” hale geliyor ve yalnızca paketin yaşı bile bulaşma olasılığını azaltıyorBen bunun da ötesine geçip bağımlılıkları yalnızca bir hata gerçekten kendi kullanım ortamımı etkilediğinde güncelliyorum. Güvenlik açığı olsa bile beni etkilemiyorsa olduğu gibi bırakıyorum. Çoğu geliştirici bağımlılıkları gereksiz yere çok sık güncelliyor; bence doğru olan, sadece gerçekten gerektiğinde yapmak. Eğer güncelleme sık ya da karmaşıksa, o paketi hiç kullanmamak ya da benim ölçütüme göre “dondurmak” daha iyi
Python’un
uvaracıyla da benzer şekilde güncellemeleri sınırlamak mümkün. Örneğinuv lock --exclude-newer $(date --iso -d "2 days ago")komutuyla son 2 gün içinde yayımlanan sürümleri hariç tutabilirsinizBu tür sorunlar, yeni paketler ve sürümler yeterince izlenmediği için ortaya çıkıyor. Debian’daki gibi, yalnızca güvenlik yamaları ve hata düzeltmelerinin uygulandığı kararlı bir dağıtımı, paket bakımcılarının gözettiği testing/unstable dağıtımından ayrı işletmek en iyi yaklaşım olurdu. Merkezi açık paket depolarında (NPM, Python, Rust vb.) çalışan herkes aynı sorunla karşı karşıya
Geliştirici kültüründe bir sorun var. Yüzlerce (geçişli) bağımlılığa sahip olup bunları düşünmeden otomatik güncellemek başlı başına problem. Bu kadar çok üçüncü taraf kodunu derleme/çalıştırma ortamınıza maruz bırakma kararının bir sorumluluğu var
Dağıtımlar da bakımını üstlenmek zorunda oldukları paket miktarı yüzünden giderek daha fazla zorlanıyor. Aslında dil bazlı ekosistemler (ör. CPAN, Maven, RubyGems vb.) de bu yüzden gelişti; yalnızca Linux dağıtımları kullanıcıların istediği uygulamaları sunmakta yetersiz kalınca freshmeat, linuxbrew, flatpak, PPA gibi pek çok yol ortaya çıktı. Hiçbir topluluğun sayısız farklı kütüphanenin birden fazla dalını izleyip destekleyecek kapasitesi olduğunu sanmıyorum
Bir Debian geliştiricisi olarak, upstream kodu içeri almadan önce giderek artan “gürültü” yüzünden — özellikle yalnızca stil değişiklikleri ve araç güncellemeleri — gerçek değişiklikleri fark etmek çok zorlaşıyor. Bu tür değişiklikler, gerçekten insan incelemesi gerektiren refactor’lar, hata düzeltmeleri, özellik eklemeleri veya sorunlu olabilecek kodu bulmaya yönelik araç çıktıları değilse, keşke daha az yapılsa
Rust tarafında
cargo vetdiye bir sistem var. Google ve Mozilla gibi şirketler buna katılarak paketleri ortak biçimde otomatik olarak inceliyor ve doğruluyorMerkezsiz yapıyı korurken yine de belli güvenlik önlemleri koymanın yolları olduğunu düşünüyorum. Örneğin belli büyüklüğün üzerindeki paketlerde 2FA kullanan iki hesabın onayı zorunlu olabilir ya da popüler paketlerin npm’e yüklenmesi yalnızca yeniden üretilebilir derleme sistemi üzerinden mümkün kılınabilir. Tam dağıtık yapıdan vazgeçmeden, yalnızca büyük projelerde biraz ek uğraş gerektiren çözümler düşünülebilir
Son dönemde art arda gelen tedarik zinciri saldırıları yüzünden sunucu taraflı render’ı (JavaScript olmadan) daha ciddi düşünmeye başladım. HTMX sayesinde JavaScript olmadan da gerçekten çok ileri gidilebildiğini fark ettim. Böyle olursa uygulama daha hızlı ve daha kararlı bile olabilir
Geleneksel JS ortamının aslında en güvenli sandbox ortamı olduğunu vurgulamak isterim. Yaklaşık 30 yıldır milyarlarca cihazda güvenilmeyen JS kodu çalışıyor ama tarayıcı motorlarına karşı büyük ölçekli başarılı saldırı örnekleri çok az. Buna karşılık NodeJS ve npm ortamı güvenlik açısından baştan aşağı yeniden tasarlanmalı.
leftpadgibi olaylar, basit kod parçacıklarının bile npm’e paket olarak yüklenmesi kültüründen kaynaklanıyorBu saldırıların otomatik olarak yalnızca belirli bir ortama, yani JavaScript’e özgü bir meseleye indirgenmesi garip geliyor. Asıl daha büyük sorun, npm’de bulunan güvenlik güçlendirmelerinin bile başka ortamlarda (PyPI, Crates vb.) hiç bulunmaması gibi görünüyor
Vendoring maruziyeti azaltabilir ama bunun kökten çözüm olduğunu düşünmüyorum. NPM güvenliği gerçekten ciddiye alıyorsa, publish işlemi için 2FA’yı ve paket ön taramasını zorunlu kılmalı, hatta donanım anahtarıyla imzalamayı bile şart koşmalı.
semverya da CRC yeterli değil. Bunların hepsi paket yönetim sisteminin içine varsayılan olarak yerleşik gelmeliAslında bu saldırılar JavaScript’e özgü değil; geliştiricilerin yeni bağımlılık eklerken yeterince denetim yapmamasından doğuyor. Aynı şey Rust veya Go gibi diğer dil ekosistemleri için de geçerli olabilir
Paket yöneticilerine çok bağımlı olan ve standart kütüphanesi zayıf kalan tüm diller benzer şekilde savunmasız. Uzun vadede yeniden vanilla JavaScript kullanma yönüne dönmek gerektiğini düşünüyorum. Rust da aynı biçimde paket bağımlılığı açısından yüksek risk taşıyor. Hatta bu konuda Go daha örnek bir karşı örnek gibi duruyor
Güvenilir anahtarlarla commit/release imzalayan, kurulum ve doğrulamayı da yapan, hafif kodu izleyebilen bir sisteme ihtiyaç olduğunu düşünüyorum. Zaten sigstore kullanan npm provenance yaklaşımı mevcut, ancak henüz yaygın değil ve daha çok yayıncı doğrulamasıyla sınırlı gibi görünüyor
Bu zafiyet aslında 2016’da NPM’e rapor edilmişti (CERT tavsiyesi), ama NPM’in yanıtı WAI (working as intended) olmuştu
WAI’nin ne anlama geldiğini bilmeyenler için: Genelde “working as intended” kısaltmasıdır
postinstallbetiği hiç olmasa bile, derleme sürecinde, sunucu başlatılırken ya da testlerde modül import edildiği anda kötü amaçlı kodun yine çalışacağını düşünüyorum. Sonuçtanpm installsonrasında bir noktada gerçekten bir şey çalıştırmanız gerekecek...left-padolayında burada gördüğüm bir yorumu hatırladım; tanınmış bir npm bakımcısının 600 npm paketi ve 1.200 satır JavaScript kodu olduğundan bahsediliyordu. Örnek alınması gereken vaka bence esbuild; neredeyse hiç dış bağımlılığı yok ve yalnızca Go standart kütüphanesini kullanıyor“Yeni nesil” denen diğer projelerin bağımlılık zincirlerine bakınca biomejs ve swc de oldukça az görünüyor. Ama Rust kaynak koduna bakıldığında biomejs ve swc sonuçta yine çok sayıda bağımlılık taşıyor. Bu tür projeler yaygınlaşırsa cargo ekosisteminin de aynı yola gireceğini tahmin ediyorum. Eğer esbuild gibi katı bir tarzla yazılmış büyük projeler bilen varsa, öneri duymak isterim
Go’ya geçme nedenlerimden biri de
puregotarzı kütüphane eğilimiydi. Genelde sadece standart kütüphane vegolang.org/xkullanıyorlar, CGO olmadan derlenebildikleri için taşınabilirlikleri yüksek.go mod vendorkısa vadeli risk yönetimi sağlayabilir ama kökten çözüm değil. Go tarafında da paket doğrulaması (imza/anahtar kontrolü vb.) uçtan uca sağlanmadığı için sonuçta açık kalıyor. Özellikle çok şey CI/CD altyapısında yoğunlaşmış durumda; imza anahtarlarını aktarmadan da derleme ve dağıtım yapılabiliyorsa güvenlik daha da artabilir diye düşünüyorum. Paket yöneticileri GPG imzalarını teşvik etmeli, git commit’lerinde de imza kullanımı yaygınlaştırılmalıeslintvakası özellikle can sıkıcı geliyor. Bağımlılık grafiğine bakınca devasa olduğu görülüyor; bakımcılar bağımlılık azaltımını önceliklendirmiyorsa sonunda başka bir çözüme (oxlint) geçmekten başka çare kalmıyorBasit özellikleri kendin yazıp dış bağımlılıkları azaltmak çözümün bir parçası. Genelde bunu yapmak bile toplam bağımlılıkların 2/3’ünü azaltabiliyor. Özellikle
left-padgibi basit şeyleri kendin yazıp küçük birimler ve testlerle kendi kontrolünde tutarsan bakım yükü de çok artmıyor. Gereksiz bağımlılıklar kararlılıkla dışarıda bırakılmalıBir Rust projesinin kök
Cargo.tomldosyasında görülenler tüm workspace içindir; her crate’in gerçek bağımlılıkları çok daha sığ olabilir. Gerçek bağımlılık yapısını anlamak için daha derine bakmak gerekirBunun kötü yanı, artık JavaScript projelerini denetlemek için Golang da okumak zorunda olmanız. Üstelik
post-installsırasında yinenode install.jsçalışıyor; sonuçta ya tamamen güveneceksiniz ya da tüm kodu okuyacaksınıznpm’in hâlâ varsayılan olarak tüm bağımlılıkların
postinstallbetiklerini çalıştırıyor olmasına inanamıyorum. Pnpm veya Bun bunu yalnızca izin listesine alınmışsa çalıştırıyor; Composer ise bağımlılıklar için lifecycle script’lerini hiç çalıştırmıyor. Derleme ya da geliştirme ortamında bağımlı paketlerin taşıdığı risk yüzünden bu yaklaşımın daha güvenli olduğunu düşünüyorumBu kadar büyük saldırıların diğer paket yöneticilerinde (ör. Rust
build.rs, Python, Java vb.) neden bu kadar sık duyulmadığını merak ediyorum.postinstallbir yana, aslında neredeyse tüm ekosistemlerde ilke olarak mümkün; ama olaylar sanki özellikle npm etrafında yoğunlaşıyorPnpm’in varsayılanının betikleri engelleyecek şekilde değiştiğini gördüm. Topluluğun buna tepkisini — betik izni verildiğinde kullanıcı deneyimi,
allowkomutunun kötüye kullanımı vb. — merak ediyorum. Python paketleme topluluğunda da wheel variants ile ilgili benzer tartışmalar sürüyor; başka ekosistemlerin deneyimlerinden faydalanmak isterimBu saldırı 180’den fazla pakete yayılmış durumda; bkz. Aikido Security blogu
Bu saldırıyı ilk kimin keşfettiğini merak ediyorum. Farklı bloglar krediyi farklı yerlere veriyor, bu da ilginç. Aikido “büyük ölçekli saldırıyı biz keşfettik” diyor; Socket, Ox, Safety, Phoenix, Semgrep vb. de olayı kendi biçimlerinde anlatıyor
Ben Aikido’da çalışan Mackenzie’yim. Bu konuyu ilk bildiren kişi geliştirici Daniel Pereira’ydı; kendisi bunu Socket’e iletti ve Socket ilk 40 paketi ve kötü amaçlı yazılımı analiz etti. Sonrasında Aikido ek 147 paket ile Crowdstrike paketini de buldu. Aslında kötü amaçlı yazılımın kendi kendine yayılan bir worm olduğunu ilk fark eden de Step’ti. Birden fazla kuruluşun bağımsız biçimde farklı roller üstlenmiş olması ilginç
Görünüşe göre birçok geliştirici bunu neredeyse aynı zamanlarda fark etti ve Step ile Socket farklı kişileri anıyor. Sonuçta sektörün güvenlik sağlayıcıları bunu kendi yöntemleriyle yakaladı: yapay zeka kod analizi (Socket, Aikido) ya da eBPF pipeline izleme (Step) gibi
Bu kadar çok sağlayıcı bunu bağımsız olarak tespit edebildiyse, acaba bu teknikleri doğrudan npm ile paylaşsalar kötü amaçlı paketlerin kaydedilmesi en baştan engellenebilir miydi diye düşünüyorum. O zaman erken uyarı sistemlerini satamaz hale gelecekleri için paylaşmıyor olabilirler
OP’deki makale doğrudan şu ifadeyi alıntılıyor: “@franky47 bu olguyu fark edip topluluğu hızla bir GitHub sorunu üzerinden bilgilendirdi”
Saldırganın seçtiği “Shai Hulud” adını epey zekice buluyorum; dev bir solucanın adını gerçek bir worm zararlısına vermiş. Ana
bundle.jsdosyası da 3.6MB ile oldukça devasa. Varyant kötü amaçlı yazılım bile npm usulü gereğinden büyük hale gelmişYakında bir tedarik zinciri saldırısının tesadüfen başka bir tedarik zinciri saldırısını da tetikleyeceğine dair bir his var içimde
Zararlı yazılımlar da Moore yasasına uyuyor; 1991’deki tequila virus 2.6KB idi, şimdi ise birkaç MB boyutundalar