API Güvenlik Taraması: OWASP ZAP ile REST API Güvenliğini Test Etme

REST API’lerinizi production’a almadan önce güvenlik taraması yapmak artık bir lüks değil, zorunluluk. Her gün yüzlerce API güvenlik açığı keşfediliyor ve bunların büyük çoğunluğu OWASP Top 10’da yer alan bilinen zaafiyetler. OWASP ZAP (Zed Attack Proxy), bu taramaları hem manuel hem de otomatik olarak yapmanızı sağlayan, üstelik tamamen ücretsiz bir araç. Bu yazıda ZAP’ı sysadmin bakış açısıyla ele alacağız: kurulumdan, CI/CD pipeline entegrasyonuna kadar gerçek dünya senaryolarıyla.

OWASP ZAP Nedir ve Neden Kullanmalıyız?

ZAP, OWASP tarafından geliştirilen ve aktif olarak bakımı yapılan bir web uygulama güvenlik tarayıcısı. Özellikle API güvenliği söz konusu olduğunda ZAP’ın öne çıkan özellikleri şunlar:

  • Active Scanning: Uygulamaya gerçek saldırı vektörleri göndererek zaafiyetleri tespit eder
  • Passive Scanning: Trafiği izleyerek zararsız bir şekilde analiz yapar
  • API Import: OpenAPI/Swagger, GraphQL ve SOAP tanımlarını içe aktarabilir
  • Headless Mode: GUI olmadan komut satırından çalıştırabilirsiniz
  • REST API: ZAP’ın kendisi bir REST API sunuyor, bu da otomasyon için mükemmel

Piyasada Burp Suite gibi alternatifler var ancak Burp Suite’in gelişmiş özellikleri ücretli. Eğer kurumsal lisans bütçeniz yoksa ZAP ciddi anlamda güçlü bir alternatif.

Kurulum

Docker ile Kurulum (Önerilen)

Sysadmin olarak Docker üzerinden çalıştırmayı şiddetle tavsiye ediyorum. Ortam kirliliği yok, versiyon yönetimi kolay.

# ZAP stable imajını çek
docker pull ghcr.io/zaproxy/zaproxy:stable

# Temel çalıştırma testi
docker run --rm ghcr.io/zaproxy/zaproxy:stable zap.sh -version

# Kalıcı veri için volume bağla
mkdir -p /opt/zap/reports /opt/zap/scripts

docker run -d 
  --name zap 
  -p 8080:8080 
  -v /opt/zap/reports:/zap/reports 
  -v /opt/zap/scripts:/zap/scripts 
  ghcr.io/zaproxy/zaproxy:stable 
  zap.sh -daemon 
  -port 8080 
  -host 0.0.0.0 
  -config api.addrs.addr.name=.* 
  -config api.addrs.addr.regex=true 
  -config api.key=supersecretapikey123

Linux’ta Doğrudan Kurulum

# Ubuntu/Debian için
wget https://github.com/zaproxy/zaproxy/releases/download/v2.14.0/ZAP_2_14_0_unix.sh
chmod +x ZAP_2_14_0_unix.sh
sudo ./ZAP_2_14_0_unix.sh -q

# ZAP binary'sini PATH'e ekle
echo 'export PATH=$PATH:/opt/zaproxy' >> ~/.bashrc
source ~/.bashrc

# Daemon modunda başlat
zap.sh -daemon -port 8080 -config api.key=supersecretapikey123

API Tanımı ile Tarama Başlatmak

Gerçek dünya senaryosu: Elinizde bir e-ticaret uygulamasının REST API’si var, OpenAPI/Swagger dosyası mevcut. Bunu ZAP’a besleyerek kapsamlı tarama yapacağız.

OpenAPI Tanımını İçe Aktarma

# Swagger/OpenAPI dosyasını ZAP'a import et
curl "http://localhost:8080/JSON/openapi/action/importUrl/" 
  -G 
  --data-urlencode "apikey=supersecretapikey123" 
  --data-urlencode "url=https://api.example.com/swagger.json" 
  --data-urlencode "hostOverride=api.example.com"

# Veya yerel dosyadan import
curl "http://localhost:8080/JSON/openapi/action/importFile/" 
  -G 
  --data-urlencode "apikey=supersecretapikey123" 
  --data-urlencode "file=/zap/scripts/api-spec.json" 
  --data-urlencode "target=https://api.example.com"

Import işlemi başarılı olursa ZAP size bir importedCount ve errorCount döner. errorCount sıfır değilse Swagger dosyanızda tanım hataları var demektir, bunu önce düzeltin.

Authentication Token Yapılandırması

API’lerin büyük çoğunluğu JWT veya API key gerektiriyor. ZAP’ı authenticate edilmiş bir kullanıcı gibi taraması için yapılandırmanız gerekiyor:

# Context oluştur
CONTEXT_ID=$(curl -s "http://localhost:8080/JSON/context/action/newContext/" 
  -G 
  --data-urlencode "apikey=supersecretapikey123" 
  --data-urlencode "contextName=API-Security-Test" | python3 -c "import sys,json; print(json.load(sys.stdin)['contextId'])")

echo "Context ID: $CONTEXT_ID"

# API'nin scope'unu tanımla
curl "http://localhost:8080/JSON/context/action/includeInContext/" 
  -G 
  --data-urlencode "apikey=supersecretapikey123" 
  --data-urlencode "contextName=API-Security-Test" 
  --data-urlencode "regex=https://api.example.com/v1/.*"

# Header-based authentication için script ekle
# Authorization header'ını her isteğe otomatik ekler
curl "http://localhost:8080/JSON/script/action/load/" 
  -G 
  --data-urlencode "apikey=supersecretapikey123" 
  --data-urlencode "scriptName=jwt-auth" 
  --data-urlencode "scriptType=httpsender" 
  --data-urlencode "scriptEngine=Oracle Nashorn" 
  --data-urlencode "fileName=/zap/scripts/jwt-auth.js"

JWT auth scriptini de oluşturalım:

// /opt/zap/scripts/jwt-auth.js
// ZAP HTTP Sender Script - Her isteğe JWT ekler

var TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0dXNlciJ9.xxxxx";

function sendingRequest(msg, initiator, helper) {
    // Sadece hedef API'ye giden isteklere token ekle
    var uri = msg.getRequestHeader().getURI().toString();
    if (uri.contains("api.example.com")) {
        msg.getRequestHeader().setHeader("Authorization", "Bearer " + TOKEN);
    }
}

function responseReceived(msg, initiator, helper) {
    // Response'ları işlemek için kullanılabilir
}

Aktif Tarama Yürütme

Authentication hazır, context tanımlı. Şimdi aktif taramayı başlatalım:

#!/bin/bash
# zap-api-scan.sh - Tam API güvenlik tarama scripti

ZAP_URL="http://localhost:8080"
API_KEY="supersecretapikey123"
TARGET="https://api.example.com"
REPORT_DIR="/opt/zap/reports"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

echo "[*] Spider taraması başlatılıyor..."
SCAN_ID=$(curl -s "${ZAP_URL}/JSON/spider/action/scan/" 
  -G 
  --data-urlencode "apikey=${API_KEY}" 
  --data-urlencode "url=${TARGET}" 
  --data-urlencode "contextName=API-Security-Test" 
  --data-urlencode "recurse=true" | python3 -c "import sys,json; print(json.load(sys.stdin)['scan'])")

echo "[*] Spider Scan ID: ${SCAN_ID}"

# Spider tamamlanana kadar bekle
while true; do
    PROGRESS=$(curl -s "${ZAP_URL}/JSON/spider/view/status/?apikey=${API_KEY}&scanId=${SCAN_ID}" 
      | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
    echo "    Spider ilerlemesi: ${PROGRESS}%"
    if [ "${PROGRESS}" = "100" ]; then
        break
    fi
    sleep 5
done

echo "[*] Aktif tarama başlatılıyor..."
ASCAN_ID=$(curl -s "${ZAP_URL}/JSON/ascan/action/scan/" 
  -G 
  --data-urlencode "apikey=${API_KEY}" 
  --data-urlencode "url=${TARGET}" 
  --data-urlencode "recurse=true" 
  --data-urlencode "contextId=${CONTEXT_ID}" 
  --data-urlencode "scanPolicyName=API-Policy" | python3 -c "import sys,json; print(json.load(sys.stdin)['scan'])")

echo "[*] Active Scan ID: ${ASCAN_ID}"

# Aktif tarama tamamlanana kadar bekle
while true; do
    PROGRESS=$(curl -s "${ZAP_URL}/JSON/ascan/view/status/?apikey=${API_KEY}&scanId=${ASCAN_ID}" 
      | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
    echo "    Aktif tarama ilerlemesi: ${PROGRESS}%"
    if [ "${PROGRESS}" = "100" ]; then
        break
    fi
    sleep 10
done

echo "[*] Raporlar oluşturuluyor..."

# HTML raporu
curl -s "${ZAP_URL}/OTHER/core/other/htmlreport/?apikey=${API_KEY}" 
  > "${REPORT_DIR}/zap-report-${TIMESTAMP}.html"

# JSON raporu (CI/CD için)
curl -s "${ZAP_URL}/JSON/alert/view/alerts/?apikey=${API_KEY}&baseurl=${TARGET}" 
  > "${REPORT_DIR}/zap-alerts-${TIMESTAMP}.json"

echo "[+] Tarama tamamlandı. Raporlar: ${REPORT_DIR}"

# Yüksek riskli alert sayısını raporla
HIGH_RISK=$(cat "${REPORT_DIR}/zap-alerts-${TIMESTAMP}.json" | 
  python3 -c "import sys,json; alerts=json.load(sys.stdin)['alerts']; print(len([a for a in alerts if a['risk']=='High']))")

echo "[!] Yüksek riskli zafiyet sayısı: ${HIGH_RISK}"

# Eğer yüksek riskli zafiyet varsa exit 1 ile çık (CI/CD için)
if [ "${HIGH_RISK}" -gt "0" ]; then
    echo "[FAIL] Yüksek riskli zafiyetler tespit edildi!"
    exit 1
fi

exit 0

Gerçek Dünya Senaryosu: OWASP API Top 10 Kontrolü

Bir fintech şirketinin API’sini audit ettiğinizi düşünün. Aşağıdaki zaafiyetleri ZAP ile tespit edebilirsiniz:

Broken Object Level Authorization (BOLA) Testi

BOLA, API güvenliğindeki en yaygın zafiyet. Kullanıcı kendi kaydına erişirken başka kullanıcıların kayıtlarına da erişebiliyorsa bu ciddi bir sorun.

# ZAP'ın Fuzzer'ını kullanarak IDOR/BOLA testi
# Önce bir istek proxy'den geçir, sonra fuzzing yap

# Örnek: /api/v1/users/123/profile endpoint'ini fuzz et
curl "http://localhost:8080/JSON/fuzzer/action/startFuzzer/" 
  -G 
  --data-urlencode "apikey=supersecretapikey123" 
  --data-urlencode "url=https://api.example.com/api/v1/users/FUZZ/profile" 
  --data-urlencode "fuzzerName=user-id-fuzzer"

# Sayısal ID'ler için fuzzer payload listesi oluştur
python3 -c "
for i in range(1, 1000):
    print(i)
" > /opt/zap/scripts/user-ids.txt

echo "[*] Farklı user ID'lere erişim denenecek..."
echo "[*] 200 dönen ID'ler BOLA zafiyetine işaret edebilir"

SQL Injection ve Injection Testleri

# ZAP aktif tarama politikasını SQL injection odaklı ayarla
curl "http://localhost:8080/JSON/ascan/action/addScanPolicy/" 
  -G 
  --data-urlencode "apikey=supersecretapikey123" 
  --data-urlencode "scanPolicyName=API-Injection-Policy"

# SQL Injection scan rule'u etkinleştir (ID: 40018)
curl "http://localhost:8080/JSON/ascan/action/setScannerAlertThreshold/" 
  -G 
  --data-urlencode "apikey=supersecretapikey123" 
  --data-urlencode "id=40018" 
  --data-urlencode "alertThreshold=MEDIUM" 
  --data-urlencode "scanPolicyName=API-Injection-Policy"

# XXE Injection (ID: 90023)
curl "http://localhost:8080/JSON/ascan/action/setScannerAttackStrength/" 
  -G 
  --data-urlencode "apikey=supersecretapikey123" 
  --data-urlencode "id=90023" 
  --data-urlencode "attackStrength=HIGH" 
  --data-urlencode "scanPolicyName=API-Injection-Policy"

CI/CD Pipeline Entegrasyonu

Güvenlik taramasının gerçek değeri, geliştirme sürecine entegre edildiğinde ortaya çıkıyor. Her pull request ya da deployment öncesi otomatik tarama yapmak için aşağıdaki yaklaşımı kullanabilirsiniz.

GitLab CI/CD Örneği

# .gitlab-ci.yml içine eklenecek security stage

stages:
  - build
  - test
  - security-scan
  - deploy

variables:
  ZAP_IMAGE: "ghcr.io/zaproxy/zaproxy:stable"
  TARGET_URL: "https://staging-api.example.com"
  ZAP_API_KEY: "${ZAP_SECRET_KEY}"  # GitLab secret variable

api-security-scan:
  stage: security-scan
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker pull ${ZAP_IMAGE}
    - mkdir -p reports
  script:
    - |
      docker run --rm 
        -v $(pwd)/reports:/zap/reports:rw 
        -v $(pwd)/api-spec.json:/zap/api-spec.json:ro 
        ${ZAP_IMAGE} 
        zap-api-scan.py 
        -t /zap/api-spec.json 
        -f openapi 
        -r /zap/reports/api-scan-report.html 
        -J /zap/reports/api-scan-report.json 
        -x /zap/reports/api-scan-report.xml 
        -z "-config api.key=${ZAP_API_KEY}" 
        --target ${TARGET_URL} 
        -l WARN
    - |
      # Yüksek riskli bulgu varsa pipeline'ı durdur
      HIGH_COUNT=$(python3 -c "
      import json
      with open('reports/api-scan-report.json') as f:
          data = json.load(f)
      highs = [s for s in data.get('site', [{}])[0].get('alerts', []) 
               if s.get('riskcode', '0') == '3']
      print(len(highs))
      ")
      echo "Yüksek riskli zafiyet sayısı: ${HIGH_COUNT}"
      if [ "${HIGH_COUNT}" -gt "0" ]; then
        echo "FAILED: Yüksek riskli zafiyetler tespit edildi!"
        exit 1
      fi
  artifacts:
    when: always
    paths:
      - reports/
    expire_in: 30 days
  only:
    - main
    - staging
    - merge_requests

Tarama Sonuçlarını Parse Etme ve Alarm Gönderme

#!/usr/bin/env python3
# parse-zap-results.py - ZAP sonuçlarını analiz et ve Slack'e bildir

import json
import sys
import requests
from datetime import datetime

def parse_zap_report(report_file):
    """ZAP JSON raporunu parse eder ve özet çıkarır"""
    with open(report_file, 'r') as f:
        data = json.load(f)
    
    alerts = data.get('alerts', [])
    
    summary = {
        'High': [],
        'Medium': [],
        'Low': [],
        'Informational': []
    }
    
    for alert in alerts:
        risk = alert.get('risk', 'Informational')
        summary[risk].append({
            'name': alert.get('name'),
            'description': alert.get('description', '')[:200],
            'solution': alert.get('solution', '')[:200],
            'url': alert.get('url'),
            'cweid': alert.get('cweid')
        })
    
    return summary

def send_slack_notification(summary, webhook_url, env="staging"):
    """Slack'e güvenlik tarama özeti gönder"""
    
    total_high = len(summary['High'])
    total_medium = len(summary['Medium'])
    total_low = len(summary['Low'])
    
    color = "danger" if total_high > 0 else "warning" if total_medium > 0 else "good"
    
    message = {
        "attachments": [{
            "color": color,
            "title": f"API Güvenlik Tarama Raporu - {env.upper()}",
            "text": f"Tarama Tarihi: {datetime.now().strftime('%d/%m/%Y %H:%M')}",
            "fields": [
                {"title": "Yüksek Risk", "value": str(total_high), "short": True},
                {"title": "Orta Risk", "value": str(total_medium), "short": True},
                {"title": "Düşük Risk", "value": str(total_low), "short": True}
            ]
        }]
    }
    
    # Yüksek riskli bulguları detaylı listele
    if summary['High']:
        high_details = "n".join([f"* {a['name']} - CWE-{a['cweid']}" 
                                   for a in summary['High'][:5]])
        message["attachments"][0]["fields"].append({
            "title": "Kritik Bulgular",
            "value": high_details,
            "short": False
        })
    
    response = requests.post(webhook_url, json=message)
    return response.status_code == 200

if __name__ == "__main__":
    report_file = sys.argv[1] if len(sys.argv) > 1 else "zap-alerts.json"
    slack_webhook = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
    
    summary = parse_zap_report(report_file)
    
    print(f"Yüksek Risk: {len(summary['High'])}")
    print(f"Orta Risk: {len(summary['Medium'])}")
    print(f"Düşük Risk: {len(summary['Low'])}")
    
    for alert in summary['High']:
        print(f"n[HIGH] {alert['name']}")
        print(f"  URL: {alert['url']}")
        print(f"  CWE: {alert['cweid']}")
    
    send_slack_notification(summary, slack_webhook)
    
    # CI pipeline için exit code
    sys.exit(1 if len(summary['High']) > 0 else 0)

ZAP Kullanırken Sık Yapılan Hatalar

Birkaç projede ZAP kullanırken kafayı yiyen şeyleri paylaşayım:

  • Rate limiting görmezden gelmek: API’niz rate limiting uyguluyorsa ZAP agresif taramada 429 hataları alır ve sonuçlar yanıltıcı olur. ascan.delayInMs parametresini ayarlayın
  • Sadece authenticated endpoint’leri taramak: Anonymous erişimle ulaşılabilen endpoint’leri de taramayı unutmayın, bazen asıl açıklar orada olur
  • Production’da aktif tarama: Aktif scanning gerçek istekler gönderir, production’da veri bozulabilir. Her zaman staging ortamında çalıştırın
  • False positive’leri filtrelememek: ZAP çok sayıda false positive üretir. Bir alert-filters.conf dosyası oluşturup bilinen false positive’leri filtrelemeyi alışkanlık haline getirin
  • Swagger dosyası olmadan taramak: ZAP’a OpenAPI tanımı beslemeden sadece spider ile taramak, API endpoint’lerinin büyük bölümünü kaçırmanıza neden olur

False Positive Yönetimi

# ZAP alert filter konfigürasyonu
# Bilinen false positive'leri session'dan çıkar

curl "http://localhost:8080/JSON/alertFilter/action/addAlertFilter/" 
  -G 
  --data-urlencode "apikey=supersecretapikey123" 
  --data-urlencode "contextId=${CONTEXT_ID}" 
  --data-urlencode "ruleId=10038" 
  --data-urlencode "newLevel=-1" 
  --data-urlencode "url=https://api.example.com/health" 
  --data-urlencode "urlIsRegex=false"

# Content-Security-Policy eksikliği uyarısını API endpoint'leri için kapat
# API'lerde CSP header gerekmez, bu false positive'dir
curl "http://localhost:8080/JSON/alertFilter/action/addAlertFilter/" 
  -G 
  --data-urlencode "apikey=supersecretapikey123" 
  --data-urlencode "contextId=${CONTEXT_ID}" 
  --data-urlencode "ruleId=10038" 
  --data-urlencode "newLevel=-1" 
  --data-urlencode "url=https://api.example.com/v1/.*" 
  --data-urlencode "urlIsRegex=true"

Periyodik Tarama ve Raporlama

Production öncesi tek seferlik tarama yetmez. Haftalık düzenli taramalar için cron job kurun:

# /etc/cron.d/zap-api-scan
# Her Pazar gece 02:00'de çalışır
0 2 * * 0 root /opt/scripts/zap-weekly-scan.sh >> /var/log/zap-scan.log 2>&1

# /opt/scripts/zap-weekly-scan.sh içeriği
#!/bin/bash
set -euo pipefail

WEEK=$(date +%Y-W%V)
REPORT_DIR="/opt/zap/reports/${WEEK}"
mkdir -p "${REPORT_DIR}"

# ZAP container'ı başlat
docker run -d --name zap-weekly 
  -p 8081:8080 
  -v "${REPORT_DIR}:/zap/reports" 
  ghcr.io/zaproxy/zaproxy:stable 
  zap.sh -daemon -port 8080 
  -config api.key=weeklyscankey 
  -config api.addrs.addr.name=.* 
  -config api.addrs.addr.regex=true

sleep 15  # ZAP başlaması için bekle

# Taramayı çalıştır
/opt/scripts/zap-api-scan.sh

# Sonuçları e-posta ile gönder
python3 /opt/scripts/parse-zap-results.py "${REPORT_DIR}/zap-alerts-latest.json"

# Container'ı temizle
docker stop zap-weekly && docker rm zap-weekly

Sonuç

OWASP ZAP, doğru yapılandırıldığında API güvenlik süreçlerinizi ciddi ölçüde güçlendirecek bir araç. Özellikle şu üç nokta hayat kurtarıyor:

İlk olarak, OpenAPI entegrasyonu. Swagger dosyanız varsa ZAP’a besleyin ve coverage’ı önemli ölçüde artırın. Dosya yoksa önce bunu çıkarın, hem güvenlik hem de dokümantasyon için değerli.

İkinci olarak, CI/CD entegrasyonu. Her deploy öncesi otomatik tarama yapmak, güvenlik açıklarını production’a geçmeden yakalamanızı sağlıyor. Pipeline’ı yüksek riskli bulgular için fail edecek şekilde ayarlayın.

Üçüncü olarak, düzenli periyodik taramalar. Uygulamanız değişmese bile ZAP’ın tarama kuralları güncelleniyor ve daha önce gözden kaçan zafiyetler yakalanabiliyor. Haftalık tarama rutini oluşturun.

ZAP tek başına yeterli bir güvenlik çözümü değil, bu konuda net olalım. Manuel penetrasyon testi, kod review ve tehdit modellemesi ile birlikte kullanıldığında anlamlı sonuçlar veriyor. Ama sıfır maliyet ve güçlü otomasyon özellikleriyle her API projesinin güvenlik toolchain’ine dahil edilmesi gereken bir araç. Başlangıç için zap-api-scan.py scriptini Docker üzerinden çalıştırın ve ilk raporunuza bakın; büyük ihtimalle sizi şaşırtacak bulgular göreceksiniz.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir