Web Geliştirme

API Güvenliği: OWASP 2023 Listesini Laravel Tarafında Gerçekten Kapatmak

20 Nov 2025
12 dakika okuma
Ininia Teknoloji
1,186

API güvenliğinde vaktinizin çoğunu tek bir açık sınıfı yiyor: yetkilendirme. OWASP'ın 2023 API Security Top 10 listesinde on maddenin dördü (API1, API3, API5, API6) doğrudan yetkilendirme kusuru; şifreleme, WAF veya token algoritması değil (bkz. OWASP API Security Top 10 – 2023). Bunun pratik sonucu şu: TLS'i doğru kurup JWT imzasını doğrulayan bir API hâlâ tüm müşteri verisini sızdırabilir, çünkü /api/orders/1042 isteğinde 1042 numaralı siparişin kime ait olduğu sorulmamıştır. Bu yazı, o dört maddeyi Laravel/PHP tarafında nasıl yapısal olarak kapatacağınızı ve hangi testle kanıtlayacağınızı anlatıyor.

OWASP 2023 listesi 2019'dan farklı; eski kontrol listenizle çalışıyorsanız üç madde eksik

2023 sürümünde kategori adları değişti ve iş mantığına dokunan iki yeni madde geldi. Türkçe kaynakların çoğu hâlâ 2019 listesini çeviriyor (bkz. OWASP API Security Top 10 – 2023).

Kod Kategori Sahada nasıl görünür
API1:2023Broken Object Level AuthorizationID'yi değiştirip başkasının kaydını okumak
API2:2023Broken AuthenticationRefresh token rotasyonu yok, OTP'de rate limit yok
API3:2023Broken Object Property Level AuthorizationYanıtta fazla alan dönmek veya istekte role alanını kabul etmek
API4:2023Unrestricted Resource Consumptionper_page=100000, sınırsız SMS/e-posta tetikleme, sınırsız LLM çağrısı
API5:2023Broken Function Level AuthorizationAdmin endpoint'i sadece arayüzde gizli
API6:2023Unrestricted Access to Sensitive Business FlowsBotun tüm kampanya stoğunu sepete kilitlemesi
API7:2023Server Side Request ForgeryKullanıcının verdiği URL'den görsel çekmek
API8:2023Security MisconfigurationÜretimde APP_DEBUG=true, açık CORS
API9:2023Improper Inventory ManagementKapatıldığı sanılan /api/v1 hâlâ ayakta
API10:2023Unsafe Consumption of APIsEntegre olduğunuz kargo/banka API'sinin yanıtını doğrulamadan kullanmak

API6 ve API10 listede yeni. İkisinin ortak noktası, klasik "açık tarayıcı" araçlarının bunları bulamaması: ikisi de teknik bir zafiyet değil, iş mantığı varsayımı.

BOLA'yı controller'da kontrol ederek çözmeye çalışmayın, sorguda çözün

En yaygın hata, yetki kontrolünü kaydı çektikten sonra yapmak. Her yeni endpoint bu kontrolü tekrar yazmayı gerektirdiği için, eninde sonunda biri unutulur.

Kırılgan hâli (Laravel 12, PHP 8.4):

// KIRILGAN: kayıt önce çekiliyor, sahiplik sonra kontrol ediliyor
public function show(string $id)
{
    $order = Order::findOrFail($id);

    if ($order->company_id !== auth()->user()->company_id) {
        abort(403);
    }

    return new OrderResource($order);
}

Dayanıklı hâli, kiracı sınırını sorgunun kendisine gömüyor. Model üzerinde bir global scope tanımlarsanız, o modele giden her sorgu otomatik filtrelenir ve kontrolü unutmak mümkün olmaz:

// app/Models/Concerns/BelongsToCompany.php
namespace App\Models\Concerns;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;

trait BelongsToCompany
{
    public static function bootBelongsToCompany(): void
    {
        static::addGlobalScope(new class implements Scope {
            public function apply(Builder $builder, Model $model): void
            {
                $user = auth()->user();

                if ($user === null) {
                    // Kimliksiz istekte hiçbir satır dönmesin.
                    $builder->whereRaw('1 = 0');
                    return;
                }

                $builder->where($model->qualifyColumn('company_id'), $user->company_id);
            }
        });

        static::creating(function (Model $model): void {
            $model->company_id ??= auth()->user()?->company_id;
        });
    }
}

Artık Order::findOrFail($id) başka şirketin kaydı için 404 döner. 403 yerine 404 dönmesi kasıtlı: 403, kaydın var olduğunu doğrular ve numaralandırma saldırısına yardım eder.

Global scope'un iki bilinen kırılma noktası var. Birincisi kuyruk işçileri ve zamanlanmış görevler: orada auth()->user() null olduğu için sorgu boş döner. Bu kod o durumda sessizce boş liste değil, sıfır satır döndürür; arka plan işlerinde scope'u bilinçli olarak withoutGlobalScope() ile kaldırıp kiracıyı iş yükünden (job payload) almanız gerekir. İkincisi ham DB::table() sorguları: global scope Eloquent'e bağlıdır, query builder'ı kapsamaz.

API3 iki yönlü: fazla alan dönmek de, fazla alan kabul etmek de aynı madde

Türkçe kaynaklarda bu madde genellikle "gereğinden fazla veri dönmeyin" diye özetleniyor. Yarısı eksik. Aynı kategori, istekte kabul ettiğiniz alanları da kapsıyor: modeli doğrudan $request->all() ile doldurmak, saldırganın is_admin veya balance alanını göndermesine kapı açar.

Çıkış tarafı için kaynak sınıfı kullanın ve alanları tek tek sayın; $this->resource->toArray() yazmayın:

class OrderResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id'          => $this->id,
            'code'        => $this->code,
            'total'       => $this->total,
            'status'      => $this->status,
            // Satış temsilcisinin iç notu ve maliyet yalnızca yetkiliye:
            'cost'        => $this->when($request->user()->can('viewCost', $this->resource), $this->cost),
        ];
    }
}

Giriş tarafı için FormRequest ile validated() kullanın ve modele yalnızca doğrulanmış diziyi verin. Modeldeki $fillable tek başına yeterli sayılmamalı; ikinci bir savunma katmanı olarak kalsın (bkz. Laravel 12, Validation).

KVKK açısından bu maddenin ayrı bir ağırlığı var: 6698 sayılı Kanun'un 4. maddesi kişisel verilerin işlendikleri amaçla bağlantılı, sınırlı ve ölçülü olmasını istiyor. Mobil uygulamaya TC kimlik numarası, adres ve doğum tarihi dönen bir sipariş listesi endpoint'i, henüz sızmadan da bu ilkeye aykırı.

Rate limit'i istek sayısıyla değil, maliyetle kurun

"Dakikada 60 istek" tek başına API4'ü kapatmıyor, çünkü isteklerin maliyeti eşit değil. Rapor çeken bir endpoint ile profil okuyan bir endpoint aynı kotadan düşmemeli. Laravel'de maliyet bazlı sınırlama şöyle kurulur:

// bootstrap/app.php veya bir ServiceProvider içinde
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

RateLimiter::for('api', function ($request) {
    $user = $request->user();

    return [
        // Genel kota
        Limit::perMinute(120)->by($user?->id ?: $request->ip()),
        // Pahalı uçlar için ikinci, daha dar kota
        Limit::perMinute(5)->by('report:' . ($user?->id ?: $request->ip())),
    ];
});

Üç sınır daha koyun: sayfa boyutu için sabit bir tavan (per_page ne gelirse gelsin en çok 100), istek gövdesi için boyut sınırı, ve para/mesaj harcatan akışlar için ayrı kota. Üçüncüsü en çok atlananı: SMS OTP gönderen, e-posta atan veya bir LLM sağlayıcısına istek yapan uçlar. Bunlar saldırganın size doğrudan fatura çıkarabildiği yerler.

API9 bir güvenlik açığı değil, envanter sorunu; ama en çok veri buradan gidiyor

Kapatıldığı sanılan eski sürüm, unutulan staging alan adı, dokümante edilmemiş iç servis. Bunlar pentest raporlarında değil, kaza eseri bulunuyor. İki somut iş, envanteri bir günde ayağa kaldırıyor:

  1. Rota listesini otomatik dışa aktarın. Laravel'de php artisan route:list --json --path=api çıktısını CI'da bir dosyaya yazıp depoya işleyin. Yeni bir uç yetkilendirme middleware'i olmadan eklendiğinde diff'te görünür.
  2. Sertifika şeffaflık kayıtlarından alt alan adı taraması yapın. Herkese açık CT log'ları, sizin unuttuğunuz api-test.sirket.com.tr kaydını da içeriyor. Saldırgan bunu zaten yapıyor.

Rota listesini kontrol altına aldıktan sonra her uç için tek bir soru: kimlik doğrulama middleware'i var mı, ve yetkilendirme kontrolü nerede? İkincisi olmayan uçlar API5 adayınız.

API10: entegre olduğunuz kurumun yanıtına güvenmeyin

Türkiye'deki entegrasyon işlerinde bu madde beklenenden çok iş çıkarıyor. Kargo, banka, e-fatura ve kamu servislerinin yanıtları çoğu zaman şema doğrulamasından geçirilmeden doğrudan veritabanına yazılıyor. Üç somut önlem:

  • Dış yanıtı şemaya karşı doğrulayın. Beklenmeyen alan geldiğinde kaydı reddedin, sessizce kabul etmeyin. SOAP servislerinde WSDL değişikliği haber verilmeden yapılıyor.
  • Yönlendirmeleri izlemeyin. Dış servise yapılan HTTP çağrısında allow_redirects kapalı olsun; aksi hâlde SSRF'in (API7) uzantısı hâline gelir.
  • Zaman aşımı ve devre kesici koyun. Yanıt vermeyen bir kurum servisi, sizin API'nizin tüm PHP-FPM işçilerini tüketerek dolaylı bir DoS üretir.

Ham istek ve yanıtı saklamak bu maddede ayrıca değerli: kurumun davranışı değiştiğinde elinizde kanıt olur. Bu yaklaşımı kargo entegrasyonu ve banka entegrasyonu rehberlerinde ayrıntılandırdık.

Bir saatte çalıştırabileceğiniz BOLA testi

Otomatik tarayıcılar BOLA'yı bulamıyor, çünkü hangi kaydın kime ait olduğunu bilmiyorlar. Bunu bulan test elle kurulur ve bir kez kurulduğunda CI'da kalır:

  1. İki ayrı kiracıda ikişer kullanıcı açın: A1, A2 (X şirketi), B1 (Y şirketi).
  2. A1 ile bir kayıt oluşturun, dönen id'yi saklayın.
  3. Aynı id için B1'in token'ıyla GET, PUT, PATCH, DELETE deneyin. Beklenen sonuç: dördü de 404.
  4. Aynı testi listeleme uçlarında filtre parametreleriyle tekrarlayın: ?company_id=X gönderildiğinde sunucu bunu dikkate alıyor mu?
  5. Alt kaynaklarda tekrarlayın: /orders/{A_kaydi}/items gibi iç içe yollar, üst kaydın sahipliğini çoğu zaman kontrol etmiyor.

Beşinci adım en çok bulgu veren yer. Üst kaynağın sahipliği kontrol edilse bile, alt kaynak sorgusu genellikle doğrudan OrderItem::where('order_id', $id) ile yazılıyor.

Sırada ne var

Bu yazıyı kapattığınızda yapılacak ilk iş şu: php artisan route:list --path=api çıktısını alın, yetkilendirme kontrolü olmayan uçları işaretleyin, ve o listeyi yukarıdaki beş adımlı BOLA testinden geçirin. Bulgu çıkarsa sorunu controller'da değil, model katmanında kapatın.

Elinizde bir pentest raporu varsa bulguları önceliklendirmek için pentest raporundaki açıklar nasıl kapatılır yazısına bakabilirsiniz. API tasarımı ve entegrasyon tarafındaki yaklaşımımız API geliştirme sayfasında; kurumsal güvenlik başlıklarını güvenlik merkezi altında topladık. Mevcut bir API'nin gözden geçirilmesi için kapsam çıkarmak isterseniz iletişim sayfası uygun yer.

Kategori kodları ve adları 25 Eylül 2026 tarihinde OWASP API Security Top 10 – 2023 sürümünden doğrulanmıştır. Kod örnekleri Laravel 12 / PHP 8.4 içindir ve üretime alınmadan önce kendi yetkilendirme modelinize uyarlanmalıdır. KVKK atfı 6698 sayılı Kanun'un ilgili maddesinedir ve hukuki görüş yerine geçmez.

Bu konuda bir yazılım projesi mi planlıyorsunuz?

Projenizi birlikte analiz edip teknik yol haritasını çıkarabiliriz. Ücretsiz keşif görüşmesi için hemen yazın.

İlgili Hizmetlerimiz

Ininia Teknoloji

İstanbul Teknik Üniversitesi ARI Teknokent'te kurulu Ininia Teknoloji, 12+ yıllık deneyimle AR/VR, yapay zeka ve mobil uygulama alanlarında yenilikçi çözümler sunmaktadır.

Projeniz için profesyonel destek mi arıyorsunuz?

12+ yıllık deneyimimizle dijital dönüşümünüzü hızlandıralım.

Ücretsiz Görüşme Talep Et