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.delayInMsparametresini 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.confdosyası 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.
