Mert Özen Her Satırda Daha İleri
Güvenlik Web Güvenliği (OWASP Top 10) — #06

OWASP A03: Software Supply Chain Failures — NuGetAudit, npm audit ve SRI

Mert Özen 6 Kas 2026 11 dk 4 görüntülenme
OWASP A03: Software Supply Chain Failures — NuGetAudit, npm audit ve SRI

A03:2025 Software Supply Chain Failures neden ayrı bir kategori? event-stream, Log4Shell, xz, polyfill.io ve 2025 npm olaylarından dersler; net10.0'da transitive bağımlılıkları da tarayan NuGetAudit, build'i kıran uyarılar, lock dosyaları, npm audit ve npm ci, CDN script'i için SRI, CycloneDX ile SBOM, provenance ve Dependabot.

Sürüm notu: Bu yazı OWASP Top 10:2025'e göre yazılmıştır; kodlar ASP.NET Core 10 (.NET 10) ile denenmiştir.

Önce Tahmin Et

Bookstore API'nin .csproj dosyasında yalnızca iki paket var ve ikisinin de sürümünü sen seçtin. Projeyi derlediğinde NuGet seni kaç paket için uyaracak: sıfır, bir, yoksa hiç adını duymadığın paketler için de mi? Peki uyarı geldiğinde build kırılır mı?

Cevabı birazdan gerçek bir restore çıktısında göreceğiz. İpucu: projene eklediğin paket sayısı, projende çalışan paket sayısıyla aynı değil.

Tedarik Zinciri Neden Ayrı Bir Kategori?

A03:2025 Software Supply Chain Failures (yazılım tedarik zinciri hataları), 2021 listesindeki A06 Vulnerable and Outdated Components kategorisinin genişlemiş hâli. Yeni isim zincirin tamamına bakıyor: paketler, onların bağımlılıkları, paket kaynağı, build makinen, CI/CD hattın ve dağıtım. Kategoriye eşlenen CWE sayısı az (6), ama 2025 listesinde topluluk anketiyle seçilen iki kategoriden biri: veride küçük, sahada büyük.

Bir benzetmeyle düşün: restoranın mutfağı tertemiz olabilir, ama sebzeyi aldığın tedarikçi bozuk mal gönderirse yemek yine bozuk çıkar. Senin yazdığın kod çoğu projede toplamın küçük bir kısmı; geri kalanı NuGet ve npm'den gelir ve senin uygulamanın yetkileriyle çalışır.

Bu yazı tamamen savunma tarafında, ama serinin kuralı geçerli: bu teknikleri yalnız sahibi olduğun ya da yazılı izin aldığın sistemlerde dene.

Olaylardan Çıkan Dersler

Her olay farklı bir halkanın koptuğunu gösteriyor. npm'deki event-stream olayında paketin bakımı gönüllü yeni bir maintainer'a devredildi. Yeni maintainer pakete flatmap-stream adlı kötü niyetli bir bağımlılık ekledi ve bu kod belirli bir bitcoin cüzdan uygulamasını (Copay) hedefliyordu. Ders: bir paketin güvenilirliği, onu bugün kimin yönettiğiyle ilgilidir.

Aralık 2021'deki Log4Shell (CVE-2021-44228), Java'nın Log4j 2 library'sindeki JNDI özelliği üzerinden uzaktan kod çalıştırmaya izin veren bir zafiyetti. CVSS skoru 10.0'dı, 2.0-beta9 ile 2.14.1 arasındaki sürümleri etkiliyordu ve ilk düzeltme 2.15.0 ile geldi, ardından 2.16 ve 2.17 izledi. OWASP'ın A03:2025 sayfası da bu olayı örnek veriyor. Ders: çoğu ekip Log4j'yi başka bir paketin bağımlılığı olarak kullanıyordu ve varlığından habersizdi.

Mart 2024'te ortaya çıkan xz-utils arka kapısı (CVE-2024-3094), "Jia Tan" adını kullanan ve yıllarca güven kazanan bir maintainer kimliği tarafından xz/liblzma'nın 5.6.0 ve 5.6.1 sürümlerine yerleştirildi. Andres Freund, sshd'deki beklenmedik gecikmenin peşine düşünce fark etti. Ders: kötü kod kaynak kodda değil, build sürecinin içinde saklanabilir.

polyfill.io olayında alan adı ve GitHub repo'su Şubat 2024'te Funnull adlı şirkete satıldı. 25 Haziran 2024'te yayımlanan Sansec raporuna göre CDN, bu script'i sayfasına doğrudan ekleyen çok sayıda siteye kötü niyetli yönlendirme kodu dağıtmaya başladı. Ders: başka bir alan adından gelen <script>, o alan adının sahibine sayfanda kod çalıştırma yetkisi verir.

2025 npm açısından yoğun geçti. 8 Eylül 2025'te oltalama ile hesabı ele geçirilen bir maintainer'ın 18 paketine (chalk, debug, ansi-styles, supports-color ve diğerleri) tarayıcıda çalışan bir kripto para hırsızı eklendi. 15–16 Eylül'de Shai-Hulud adıyla bilinen ilk kendi kendine yayılan npm worm'u @ctrl/tinycolor gibi popüler paketler üzerinden yayıldı: npm ve GitHub token'larını çalıp kurbanın diğer paketlerini yeniden yayımladı. OWASP A03:2025 sayfası 500'den fazla paket sürümünün etkilendiğini söylüyor. 24 Kasım 2025'te ikinci dalga (Shai-Hulud 2.0) bu kez preinstall script'iyle geldi. Bunun üzerine GitHub 9 Aralık 2025'te classic npm token'larını kalıcı olarak iptal etti.

NuGet Tarafı: NuGetAudit

Lab için küçük bir Bookstore API projesi kuralım. Eski bir koddan taşınmış gibi düşün: JSON için eski bir Newtonsoft.Json sürümü, raporlama için de eski bir SQL Server sürücüsü var.

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.5" />
    <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
  </ItemGroup>

</Project>
using Newtonsoft.Json;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/books/legacy", () =>
{
    var books = new[] { new { Id = 1, Title = "Clean Code" } };
    return Results.Content(JsonConvert.SerializeObject(books), "application/json");
});

app.Run();

dotnet build çalıştırınca restore aşamasında şunlar çıkıyor (yollar kısaltıldı, tekrar eden satırlar atıldı):

.../Bookstore.Api.csproj : warning NU1902: Package 'Azure.Identity' 1.10.3 has a known moderate severity vulnerability, https://github.com/advisories/GHSA-m5vv-6r4h-3vj9
.../Bookstore.Api.csproj : warning NU1902: Package 'Azure.Identity' 1.10.3 has a known moderate severity vulnerability, https://github.com/advisories/GHSA-wvxc-855f-jvrv
.../Bookstore.Api.csproj : warning NU1902: Package 'Microsoft.Identity.Client' 4.56.0 has a known moderate severity vulnerability, https://github.com/advisories/GHSA-m5vv-6r4h-3vj9
.../Bookstore.Api.csproj : warning NU1901: Package 'Microsoft.Identity.Client' 4.56.0 has a known low severity vulnerability, https://github.com/advisories/GHSA-x674-v45j-fwxw
.../Bookstore.Api.csproj : warning NU1903: Package 'Newtonsoft.Json' 12.0.1 has a known high severity vulnerability, https://github.com/advisories/GHSA-5crp-9r3c-p9vr
...
Build succeeded.
    10 Warning(s)
    0 Error(s)

Açılıştaki sorunun cevabı burada. İki paket ekledik, uyarı ise üç paket için geldi ve bunlardan ikisi (Azure.Identity ve Microsoft.Identity.Client) .csproj'da hiç yok. Onları SqlClient 5.1.5 getiriyor; bunlara transitive bağımlılık diyoruz. Uyarıyı NuGetAudit üretiyor: .NET 8 SDK'dan beri varsayılan olarak açık ve restore sırasında GitHub Advisory Database'e bakıyor. Kodlar şiddeti söyler: NU1901 low, NU1902 moderate, NU1903 high, NU1904 critical.

Transitive paketlerin de taranması .NET 10 ile gelen bir değişiklik. Proje net10.0 ya da daha yenisini hedefliyorsa NuGetAuditMode varsayılanı all, daha eski hedeflerde direct. Aynı projeyi -p:NuGetAuditMode=direct ile restore ettiğimde Azure.Identity uyarıları kayboldu; zafiyet yerinde duruyordu, sadece artık bakılmıyordu.

Komut satırından sormak

dotnet package list --vulnerable
Project `Bookstore.Api` has the following vulnerable packages
   [net10.0]:
   Top-level Package      Requested   Resolved   Severity   Advisory URL
   > Newtonsoft.Json      12.0.1      12.0.1     High       https://github.com/advisories/GHSA-5crp-9r3c-p9vr

Bu komut .NET 10'daki isim-önce yazımı; eski dotnet list package --vulnerable de çalışıyor. Dikkat et, çıktıda transitive paketler yok. Komut satırı aracı, restore'un aksine, onları ancak sen istersen gösteriyor:

dotnet package list --vulnerable --include-transitive
   Top-level Package      Requested   Resolved   Severity   Advisory URL
   > Newtonsoft.Json      12.0.1      12.0.1     High       https://github.com/advisories/GHSA-5crp-9r3c-p9vr

   Transitive Package               Resolved   Severity   Advisory URL
   > Azure.Identity                 1.10.3     Moderate   https://github.com/advisories/GHSA-wvxc-855f-jvrv
                                               Moderate   https://github.com/advisories/GHSA-m5vv-6r4h-3vj9
   > Microsoft.Identity.Client      4.56.0     Low        https://github.com/advisories/GHSA-x674-v45j-fwxw
                                               Moderate   https://github.com/advisories/GHSA-m5vv-6r4h-3vj9

Düzeltme için önce Newtonsoft.Json'ı 13.0.4'e, SqlClient'ı en güncel sürüm olan 7.1.1'e çektim. Burada öğretici bir sürpriz oldu: güncel SqlClient bu kez System.Security.Cryptography.Pkcs 9.0.18 için NU1902 ve NU1903 uyarısı getirdi. En yeni sürüme geçmek her zaman yetmiyor; transitive paketi doğrudan referansla yamalı sürüme sabitlemek gerekebiliyor.

Build'i kırmak ve lock dosyası

Kimsenin okumadığı bir uyarı işe yaramaz. Son hâli şöyle:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <NuGetAuditMode>all</NuGetAuditMode>
    <NuGetAuditLevel>moderate</NuGetAuditLevel>
    <WarningsAsErrors>$(WarningsAsErrors);NU1902;NU1903;NU1904</WarningsAsErrors>
    <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Data.SqlClient" Version="7.1.1" />
    <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
    <PackageReference Include="System.Security.Cryptography.Pkcs" Version="10.0.14" />
  </ItemGroup>

</Project>

NuGetAuditMode varsayılanla aynı ama açıkça yazmak niyeti belgeler. WarningsAsErrors uyarıları hataya çevirir. Eski .csproj ile -p:WarningsAsErrors="NU1903%3BNU1904" verdiğimde build şöyle kırıldı:

.../Bookstore.Api.csproj : error NU1903: Warning As Error: Package 'Newtonsoft.Json' 12.0.1 has a known high severity vulnerability, https://github.com/advisories/GHSA-5crp-9r3c-p9vr
Build FAILED.

Yeni hâliyle ise 0 Warning(s), 0 Error(s). RestorePackagesWithLockFile projeye packages.lock.json üretir; içinde her paketin çözülmüş sürümü ve contentHash değeri durur. CI'da dotnet restore --locked-mode kullanırsan, lock dosyasıyla uyuşmayan bir değişiklik restore'u durdurur. Newtonsoft.Json'ı .csproj'da elle 13.0.3'e çektiğimde:

.../Bookstore.Api.csproj : error NU1004: The package reference Newtonsoft.Json version has changed from [13.0.4, ) to [13.0.3, ).The packages lock file is inconsistent with the project dependencies so restore can't be run in locked mode. Disable the RestoreLockedMode MSBuild property or pass an explicit --force-evaluate option to run restore to update the lock file.

npm Tarafı: audit, ci ve Lock Dosyası

Angular ya da düz JavaScript ön yüzü olan her proje aynı soruyu npm'de de sorar. Lab'da tek bağımlılığı olan bir bookstore-web klasörü açıp eski bir lodash kurdum:

npm install lodash@4.17.20 --save-exact --ignore-scripts
npm audit
# npm audit report

lodash  <=4.17.23
Severity: high
Command Injection in lodash - https://github.com/advisories/GHSA-35jh-r3h4-6jhm
Regular Expression Denial of Service (ReDoS) in lodash - https://github.com/advisories/GHSA-29mw-wpgm-hmr9
lodash vulnerable to Code Injection via `_.template` imports key names - https://github.com/advisories/GHSA-r5fr-rjxr-66jc
Lodash has Prototype Pollution Vulnerability in `_.unset`, `_.omit`, and `_.pullAt` functions - https://github.com/advisories/GHSA-xxjr-mmjv-4gpg
fix available via `npm audit fix --force`
Will install lodash@4.18.1, which is outside the stated dependency range
node_modules/lodash

1 high severity vulnerability

npm audit bu durumda çıkış kodu olarak 1 döndü, yani CI adımı olarak kullanılabilir. Sürümü tam sabitlediğim için (--save-exact) önerilen düzeltme "aralığın dışında" diye işaretlendi; --force yerine sürümü kendim yükselttim ve temiz kurulumu lock dosyasından yaptım:

npm install lodash@4.18.1 --save-exact --ignore-scripts
rm -rf node_modules
npm ci --ignore-scripts
npm audit signatures
added 1 package, and audited 2 packages in 447ms

found 0 vulnerabilities
audited 1 package in 1s

1 package has a verified registry signature

npm ci, npm install'dan farklı olarak package-lock.json'a harfiyen uyar; lock dosyası package.json ile çelişirse hata verir. Lock dosyasındaki integrity alanı her paketin SHA-512 hash'ini taşır. --ignore-scripts, Shai-Hulud 2.0'ın kullandığı türden preinstall/postinstall script'lerinin kurulumda çalışmasını engeller.

CDN Script'leri için SRI

polyfill.io dersini kodla uygulayalım. Subresource Integrity (SRI), tarayıcıya "bu dosyanın hash'i şu olmalı" demenin yolu. Lab'da aynı ASP.NET Core uygulamasını iki porttan çalıştırdım; 5122'yi CDN gibi düşün. CDN cevabına Access-Control-Allow-Origin header'ı ekleyen küçük bir static file ayarı yeterli:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = ctx =>
        ctx.Context.Response.Headers.AccessControlAllowOrigin = "*"
});

app.Run();
openssl dgst -sha384 -binary wwwroot/lib/banner.js | openssl base64 -A
<!doctype html>
<html>
<head>
  <script src="http://localhost:5122/lib/banner.js"
          integrity="sha384-GCEqfNgEwLGIFKMv19TiDwBlWv+l2zxA1qBDKTFMDYVMpGpKF/3oGcqfVN+2Jf2W"
          crossorigin="anonymous"></script>
</head>
<body>Bookstore</body>
</html>

Sayfayı headless Chrome ile açınca script çalıştı ve konsola "banner.js loaded" yazdı. Sonra "CDN'deki" dosyanın sonuna tek satır ekledim, yani CDN ele geçirilmiş gibi davrandım. Aynı sayfa bu kez şunu yazdı ve script hiç çalışmadı:

[...:INFO:CONSOLE:0] "Failed to find a valid digest in the 'integrity' attribute for resource 'http://localhost:5122/lib/banner.js' with computed SHA-384 integrity 'lUw3wA8+QucR+fBeRH3IxuYTMNQe0LUUI66Rb16mWUqwZzletQ9jU+fmEn3V0VYm'. The resource has been blocked.", source: http://localhost:5121/index.html (0)

SRI'nin sınırını da bil: yalnızca içeriği hiç değişmeyen, sürümü sabit dosyalarda işe yarar. polyfill.io gibi istek yapan tarayıcıya göre farklı içerik üreten bir hizmete SRI koyamazsın; orada doğru cevap dosyayı kendi sunucundan sunmak.

SBOM, Provenance ve Dependabot

Log4Shell gününde en çok sorulan soru "biz bunu kullanıyor muyuz?" idi. SBOM (Software Bill of Materials), bu soruya saniyeler içinde cevap verebilmen için uygulamanın içindeki her component'in listesi. OWASP'ın A03 sayfası, doğrudan ve transitive bağımlılıkları kapsayan bir SBOM üretip yönetmeni öneriyor. İki yaygın format var: OWASP'ın CycloneDX'i ve Linux Foundation'ın SPDX'i. .NET'te CycloneDX aracını local tool olarak kurup çalıştırdım:

dotnet new tool-manifest
dotnet tool install CycloneDX
dotnet dotnet-CycloneDX Bookstore.Api.csproj -o sbom -F Json
Found 17 packages

Creating CycloneDX BOM
Writing to: .../Bookstore.Api/sbom/bom.json
{
  "type": "library",
  "bom-ref": "pkg:nuget/Newtonsoft.Json@13.0.4",
  "name": "Newtonsoft.Json",
  "version": "13.0.4",
  "scope": "required",
  "hashes": [
    {
      "alg": "SHA-512",
      "content": "6D1FAFF84FF227A83B195DAE5F0D8EAD44A36187E32E438B0BC243E24092DB79AC2DAA672AD7493C1240EF97F01C7FBE12B21F7DE22ACD82132F102EAF82805C"
    }
  ],
  "purl": "pkg:nuget/Newtonsoft.Json@13.0.4",
  ...
}

Üç paket eklemiştik, SBOM 17 paket buldu. purl (package URL) alanı, component'i araçlar arasında taşınabilir biçimde tanımlar. SBOM'u her sürümle birlikte saklarsan bir sonraki büyük CVE'de cevap hazır olur.

Paket yayımlıyorsan iki kavramı bil. npm provenance, paketin hangi repo'dan ve hangi CI işiyle build edildiğini Sigstore ile imzalı bir kayıtla kanıtlar. Trusted publishing, uzun ömürlü npm token'ı yerine CI'ın o işe özel kısa ömürlü kimliğini kullanır; çalınacak token ortada olmaz. Provenance paketin nerede build edildiğini söyler, kodun zararsız olduğunu söylemez.

Son parça, bunları elle yapmayı bırakmak. GitHub'da Dependabot zafiyetli bağımlılıklar için uyarı gösterir, güvenlik güncellemeleri ve düzenli sürüm güncellemeleri için pull request açabilir. Git & GitHub serisinde repo ayarlarını konuşmuştuk; buraya tek bir dosya ekliyoruz:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "nuget"
    directory: "/"
    schedule:
      interval: "weekly"
  - package-ecosystem: "npm"
    directory: "/bookstore-web"
    schedule:
      interval: "weekly"

Sık Düşülen Tuzaklar

Birincisi, yalnızca doğrudan paketlere bakmak. Lab'da en çok uyarı hiç eklemediğimiz paketlerden geldi ve --include-transitive vermeden komut satırı "zafiyetli paket yok" dedi. net10.0 öncesini hedefleyen projelerde restore da varsayılan olarak yalnız doğrudan paketlere bakar.

İkincisi, uyarıyı susturmak. NoWarn ile NU190x'leri kapatmak ya da npm audit çıktısını görmezden gelmek kolaydır. Gerçekten etkilenmediğin bir advisory varsa NuGetAuditSuppress ile yalnız o advisory URL'ini bastır ve nedenini yorum olarak yaz.

Üçüncüsü, npm audit fix --force'u körlemesine çalıştırmak. Bu komut major sürüm atlayabilir ve uygulamayı kırabilir. Güncellemeyi bir pull request olarak yap, testlerini çalıştır.

Dördüncüsü, cross-origin script'e integrity koyup crossorigin attribute'ünü unutmak. Lab'da bunu da denedim; Chrome dosya doğru olduğu hâlde script'i engelledi ve "integrity cannot be enforced" dedi, çünkü CORS olmadan içeriği doğrulayamıyor.

Kontrol Listesi

NuGet tarafında net10.0 hedefle ya da NuGetAuditMode'u all yap, moderate ve üstünü WarningsAsErrors ile hataya çevir, packages.lock.json üret ve CI'da --locked-mode kullan. Komut satırında dotnet package list --vulnerable --include-transitive çalıştırmayı alışkanlık hâline getir.

npm tarafında lock dosyasını commit'le, CI'da npm ci --ignore-scripts ve npm audit çalıştır, npm audit signatures ile registry imzalarını doğrula. Paket yayımlıyorsan trusted publishing ve provenance kullan.

Tarayıcı tarafında üçüncü taraf script'leri mümkünse kendi sunucundan sun; CDN şartsa sürümü sabitle, integrity ve crossorigin ekle. Her sürümle birlikte bir SBOM üret ve sakla, Dependabot ya da benzeri bir aracı açık tut.

Küçük Bir Deneme

★☆☆ Kendi bir .NET projende dotnet package list --vulnerable ve dotnet package list --vulnerable --include-transitive komutlarını ayrı ayrı çalıştır. İki çıktı arasındaki farkı not et.

★★☆ Lab projesine RestorePackagesWithLockFile ekle, packages.lock.json'ı incele, sonra bir paketin sürümünü elle değiştirip dotnet restore --locked-mode ile NU1004 hatasını kendin gör.

★★★ SRI lab'ını kur: bir script'in SHA-384 hash'ini üret, sayfaya integrity ile ekle, dosyayı değiştir ve headless Chrome konsolunda engellendiğini gör. Ardından crossorigin attribute'ünü kaldırıp ne değiştiğine bak.

Bir sonraki yazıda A04:2025 Cryptographic Failures kategorisine giriyoruz: encoding, hashing ve encryption arasındaki farkı kodla göreceğiz, Random ile RandomNumberGenerator'ı karşılaştıracağız ve HTTPS ile HSTS'in ASP.NET Core'daki varsayılanlarına bakacağız.