OWASP Top 10: 2021'den 2025'e Ne Değişti ve Eski Dokümanları Güncellemek
SSRF'in A01'e katılması, bileşen sorununun Software Supply Chain Failures'a büyümesi, Security Misconfiguration'ın ikinciliğe çıkışı, yeni A10 Mishandling of Exceptional Conditions ve üç isim değişikliği. 2025 metodolojisini rakamlarıyla okuyor, eski ve yılsız kategori numaralarını bulup 2025 karşılığını öneren küçük bir .NET aracı yazıyoruz.
Sürüm notu: Bu yazı OWASP Top 10:2021 ile OWASP Top 10:2025'i karşılaştırır; kod .NET 10 ile denenmiştir.
Önce Tahmin Et
Ekibin threat model dokümanında şu satır var: "Kapak resmi proxy'si: A10 riski, host'ları allowlist'e al." Dokümanı 2025 listesiyle okuyan yeni bir ekip arkadaşı A10'a baktığında ne görür? Satır hâlâ doğru mu?
Görür ki A10:2025 Mishandling of Exceptional Conditions, yani hata ve istisna durumlarının yanlış ele alınması. Dokümanı yazan kişi A10:2021 SSRF'i kastediyordu ve SSRF 2025'te A01 Broken Access Control'ün içine taşındı. Satır yanlış değil, ama yılı yazılmadığı için artık yanlış okunuyor. Bu yazıda 2021'den 2025'e neyin değiştiğini görüyor, sonunda da eski numaralarla yazılmış dokümanları bulan küçük bir araç yazıyoruz. Seri boyunca olduğu gibi bu teknikleri yalnız sahibi olduğun ya da yazılı izin aldığın sistemlerde dene.
2025 Listesi Bir Bakışta
OWASP Top 10:2025, listenin sekizinci sürümü. Önce release candidate olarak paylaşıldı, final sürüm Aralık 2025'in sonunda yayımlandı. 2017'nin aksine resmî bir PDF'i yok; yalnız web belgesi olarak var. Listeyi 2021'deki yerleriyle birlikte okuyalım.
A01:2025 Broken Access Control yine bir numara ve SSRF artık onun içinde. A02:2025 Security Misconfiguration beşinci sıradan ikinciye çıktı. A03:2025 Software Supply Chain Failures, 2021'in A06 Vulnerable and Outdated Components kategorisini genişleten yeni bir ad. A04:2025 Cryptographic Failures ikinci sıradan dördüncüye, A05:2025 Injection üçüncüden beşinciye, A06:2025 Insecure Design dördüncüden altıncıya indi.
A07:2025 Authentication Failures, adındaki "Identification"ı bıraktı. A08:2025 Software or Data Integrity Failures yerini korudu; adında "and" yerine "or" var. A09:2025 Security Logging and Alerting Failures, "Monitoring" yerine "Alerting" diyor. A10:2025 Mishandling of Exceptional Conditions ise tamamen yeni.
Yer Değiştirenler: SSRF ve Misconfiguration
SSRF'in (CWE-918) A01'e katılması, 2021'de başlayan kök neden bakışının doğal devamı. SSRF'te sunucu, kullanıcının adına normalde ulaşamayacağı bir kaynağa istek atıyor; yani sorun özünde bir erişim kontrolü sorunu. Tek CWE'lik bir kategoriyi ayrı tutmak yerine onu ait olduğu aileye koymak listeyi daha tutarlı yapıyor. Savunma değişmedi: allowlist, adresin çözüldükten sonra kontrol edilmesi ve yönlendirmelerin kapatılması. Bunu 4. yazıda HttpClient ile gördük.
Security Misconfiguration'ın ikinciliğe çıkması da sürpriz değil. Uygulamalar artık kod kadar yapılandırmadan oluşuyor: container ayarları, bulut kaynakları, CORS ve header'lar, ortam bazlı ayar dosyaları. XXE (CWE-611) hâlâ bu kategorinin içinde. 5. yazıda Production'da açık kalan developer exception page'i ve eksik security header'ları bu yüzden ilk sıralarda ele aldık.
Bileşenden Tedarik Zincirine
2021'deki "Vulnerable and Outdated Components" yalnız uygulamanın kullandığı bileşenlere bakıyordu: bilinen açığı olan bir paket var mı? 2025'teki "Software Supply Chain Failures" soruyu genişletiyor: paketin kendisi kötü niyetli mi, build sistemi güvenilir mi, yayımlanan artifact gerçekten senin kaynağından mı çıktı? Arada geçen dört yılda npm'deki hesap ele geçirmeleri, kendini yayan worm'lar ve bakımcı kimliğiyle yıllarca süren sızma girişimleri bu genişlemenin gerekçesi oldu; 6. yazıda bu olaylara ayrıntılı baktık.
Bu kategori verilerden değil topluluk anketinden geldi ve yalnız 6 CWE'ye eşleniyor. Az CWE, az önem demek değil: bu tür olaylar klasik test araçlarının bulduğu türden değil, o yüzden veride az görünüyor. .NET tarafındaki karşılığı NuGetAudit, lock dosyaları, SBOM ve yayın tarafında provenance.
Yeni A10 ve İsim Değişiklikleri
A10:2025 Mishandling of Exceptional Conditions, 24 CWE'yi tek çatı altında topluyor. Örnekler: istemciye hassas bilgi taşıyan hata mesajı (CWE-209), hata durumunda açık kalan kontrol, yani fail open (CWE-636) ve NULL pointer dereference (CWE-476). OWASP'ın önerileri tanıdık: hatayı oluştuğu yerde yakala, global bir exception handler'ı güvenlik ağı olarak kullan, yarım kalan işlemleri geri al, tekrarlayan hataları izle. 16. yazıdaki IExceptionHandler ve ProblemDetails örnekleri bu kategorinin ASP.NET Core karşılığıydı.
İsim değişikliklerinin üçü de kapsamı netleştiriyor. "Identification" düştü, çünkü kategori kimlik doğrulamanın kendisini anlatıyor. "and" yerine "or" geldi, çünkü yazılımın ya da verinin bütünlüğünden birinin bozulması yetiyor. "Monitoring" yerine "Alerting" geldi, çünkü log toplayıp kimsenin bakmadığı bir pano kurmak yetmiyor; doğru olayda birinin haberdar edilmesi gerekiyor.
Metodoloji: Veri ve Anket
OWASP'ın 2025 giriş sayfasına göre veri 2,8 milyondan fazla uygulamayı kapsıyor. Yaklaşık 220 bin CVE çıkarıldı, bunların yaklaşık 175 bini NVD'de bir CWE'ye eşlenmiş. On kategori toplam 248 CWE içeriyor. Yöntem 2021 ile aynı mantıkta: sekiz kategori veriden, ikisi topluluk anketinden. Bu kez anketten gelenler A03 Software Supply Chain Failures ve A09 Security Logging and Alerting Failures. Kategoriler yine semptoma değil kök nedene göre kurulmuş.
Rakamları okurken küçük bir not: giriş sayfası analiz edilen CWE sayısı için iki farklı değer veriyor ve kategori sayfalarındaki CWE sayılarını topladığında 249 çıkıyor, 248 değil. Bu farklar listenin değerini değiştirmiyor; ama bir sunumda rakam kullanacaksan kaynağını göster.
Eski Numaralı Dokümanları Güncellemek
Numaraların kaydığını gördük; şimdi pratik soru: senin repo'nda kaç yerde eski numara var? Örnek bir klasör:
$ cat docs/threat-model.md
# Bookstore API threat model
Cover image proxy: A10:2021 SSRF, allow-list the hosts.
Password storage: see A02:2021 Cryptographic Failures.
Order endpoints: A01:2021, check ownership on every request.
$ cat docs/pr-checklist.md
- [ ] A03:2021 Injection: no string-built SQL
- [ ] A06 dependencies checked
- [ ] A05:2025 Injection reviewed
$ cat src/OrderEndpoints.cs
// OWASP A01:2021: sipariş mevcut kullanıcıya ait olmalı
// OWASP A09:2021: reddedilen erişimi log'la
Bu dosyaları tarayan küçük bir konsol uygulaması:
using System.Text.RegularExpressions;
// 2021 id'si -> 2025 kategorisi
Dictionary<string, string> from2021 = new()
{
["A01"] = "A01:2025 Broken Access Control",
["A02"] = "A04:2025 Cryptographic Failures",
["A03"] = "A05:2025 Injection",
["A04"] = "A06:2025 Insecure Design",
["A05"] = "A02:2025 Security Misconfiguration",
["A06"] = "A03:2025 Software Supply Chain Failures",
["A07"] = "A07:2025 Authentication Failures",
["A08"] = "A08:2025 Software or Data Integrity Failures",
["A09"] = "A09:2025 Security Logging and Alerting Failures",
["A10"] = "A01:2025 Broken Access Control (SSRF moved here)",
};
Regex categoryId = new(@"\bA(0[1-9]|10)(?::(\d{4}))?\b");
string root = args.Length > 0 ? args[0] : ".";
string[] extensions = [".md", ".cs"];
foreach (string file in Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories)
.Where(f => extensions.Contains(Path.GetExtension(f)))
.Order())
{
string[] lines = File.ReadAllLines(file);
for (int i = 0; i < lines.Length; i++)
{
foreach (Match match in categoryId.Matches(lines[i]))
{
string id = $"A{match.Groups[1].Value}";
string year = match.Groups[2].Value;
string? hint = year switch
{
"2021" => $"-> {from2021[id]}",
// yılı yazılmamış bir id, her sürümde başka bir şey demek
"" => "ambiguous: add the edition, e.g. A06:2025",
_ => null,
};
if (hint is not null)
{
Console.WriteLine($"{Path.GetRelativePath(root, file)}:{i + 1} {match.Value} {hint}");
}
}
}
}
$ dotnet run -- ../sample
docs/pr-checklist.md:1 A03:2021 -> A05:2025 Injection
docs/pr-checklist.md:2 A06 ambiguous: add the edition, e.g. A06:2025
docs/threat-model.md:3 A10:2021 -> A01:2025 Broken Access Control (SSRF moved here)
docs/threat-model.md:4 A02:2021 -> A04:2025 Cryptographic Failures
docs/threat-model.md:5 A01:2021 -> A01:2025 Broken Access Control
src/OrderEndpoints.cs:1 A01:2021 -> A01:2025 Broken Access Control
src/OrderEndpoints.cs:2 A09:2021 -> A09:2025 Security Logging and Alerting Failures
Araç zaten 2025'e göre yazılmış satırı (A05:2025 Injection reviewed) atladı, yılsız A06'yı ise belirsiz diye işaretledi. Bu ikinci çıktı en değerlisi: 2021'de A06 bileşenler demekti, 2025'te Insecure Design. Araç dosyaları değiştirmiyor, yalnız öneri veriyor; çünkü bazı eşlemeler insan kararı istiyor. Örneğin A10:2021 satırını A01:2025'e taşırken metne "SSRF" kelimesini de eklemek, okuyanın işini kolaylaştırır. 2017 tarzı tek haneli numaralar (A4) bu regex'e takılmıyor; onları yakalamak denemelerden biri.
Sık Düşülen Tuzaklar
Birincisi, yılsız numara yazmak. Gördüğün gibi aynı "A06" iki sürümde iki farklı şey. Kural basit: dokümanda, PR şablonunda, issue etiketinde ve kod yorumunda her zaman A06:2025 biçiminde yaz.
İkincisi, kategori kaydığında içeriğin de kaybolduğunu sanmak. SSRF A01'e taşındı ama savunması aynı; ayrı bir maddesi olmaması, kontrol listesinden çıkacağı anlamına gelmez.
Üçüncüsü, eşlemeyi tek yönlü sanmak. A10:2025 için 2021'de bir karşılık yok. Eski dokümanın "tam" olduğunu düşünüp yeni kategoriyi eklemeyi unutursan istisna yönetimi hiç konuşulmamış olur.
Dördüncüsü, eski numarayı araç ve rapor ayarlarında unutmak. Statik analiz kuralları, pentest raporu şablonları ve risk kayıtları da Top 10 numarası taşıyabilir; yalnız Markdown'a bakmak yetmez.
Kontrol Listesi
Repo'daki dokümanları ve kod yorumlarını tara; 2021 numaralarını 2025 karşılıklarına çevir, yılsız numaralara yıl ekle. SSRF geçen satırları A01:2025 altına taşırken "SSRF" kelimesini koru. Kontrol listene A10:2025 için bir bölüm ekle: global exception handler, fail closed, yarım işlemlerin geri alınması. A03 başlığını yalnız "güncel paket" olarak değil, build ve yayın zincirini kapsayacak şekilde genişlet. A09'da log toplamanın yanında alarm üretmeyi de kontrol et. Rakam aktarıyorsan kaynağını göster.
Küçük Bir Deneme
★☆☆ Aracı kendi bir projende çalıştır ve kaç satır bulduğunu not et. Uzantı listesine .yml ve .json ekleyip yeniden dene.
★★☆ Regex'i 2017 tarzı numaraları (A4:2017, A7) da yakalayacak şekilde genişlet ve 2017 numaraları için ayrı bir eşleme sözlüğü yaz. Bu yazıyla 18. ve 19. yazıdaki eşlemeleri kaynak olarak kullan.
★★★ Araca --check seçeneği ekle: herhangi bir eski ya da yılsız numara bulursa sıfırdan farklı bir çıkış kodu dönsün. Sonra bunu CI'da çalışan bir adım olarak düşün; yeni bir dokümanın eski numarayla merge edilmesini engelleyebilir misin?
Bir sonraki ve serinin son yazısında Top 10'un dışına çıkıyoruz: API Security Top 10, Top 10 for LLM Applications, ASVS ve Cheat Sheet Series ile OWASP'ın diğer listelerine bakıp serinin bundan sonra nasıl süreceğini konuşacağız.