Bu rehber neyi kapsıyor?
ZAP, web uygulamalarını çalışırken dışarıdan test eden açık kaynaklı bir dinamik uygulama güvenliği testi (DAST) aracıdır. Uzun yıllar OWASP'ın amiral gemisi projelerinden biri olduğu için hâlâ en çok "OWASP ZAP" adıyla aranıyor. Proje Ağustos 2023'te OWASP'tan ayrılıp Linux Foundation bünyesindeki Software Security Project'e katıldı; Eylül 2024'ten bu yana "ZAP by Checkmarx" adıyla, yine Apache 2.0 lisanslı açık kaynak olarak geliştiriliyor.OWASP Top 10:2025 kategorilerini ve CI'da pasif baseline taramasını pratik web güvenliği rehberimizde anlattık. Bu yazı oradan devam ediyor: saldırı yükü gönderen aktif tarama (active scan), giriş yapmış kullanıcıyla kimlik doğrulamalı tarama (authenticated scan), OpenAPI ve GraphQL taraması, Automation Framework ile tekrarlanabilir bir tarama planı ve sonuçların ayıklanması. Komutlar ZAP 2.17.0 (Aralık 2025) ve resmî Docker imajlarının belgeleriyle karşılaştırıldı; seçenekler sürümden sürüme değişebildiği için kendi sürümünüzde -h çıktısını kontrol edin.
Taramadan önce: izin, kapsam ve ortam
Aktif tarama gerçek saldırı denemeleri gönderir: form alanlarına SQL enjeksiyonu yükleri yazar, parametrelerde betik enjeksiyonu dener, kısa sürede binlerce istek üretir. Bu yüzden şu üç kuraldan taviz verilmez:- Yazılı izin. Size ait olmayan ya da test için açıkça yetki verilmemiş bir sistemde aktif tarama çalıştırmak suç oluşturabilir. Türkiye'de bu tür eylemler TCK 243 (bilişim sistemine girme) ve 244 (sistemi engelleme, bozma, verileri yok etme veya değiştirme) kapsamında değerlendirilebilir. Barındırma, CDN ve WAF sağlayıcınızın kullanım koşullarına da bakın.
- Canlı değil, test ortamı. Aktif tarama yorum, sipariş ve destek talebi gibi kayıtlar oluşturabilir, e-posta tetikleyebilir, hatta veri silebilir. Canlıya benzeyen ama anonimleştirilmiş veriyle çalışan bir staging ortamı kullanın ve taramadan önce yedek alın.
- Haberdar bir ekip. Operasyon ekibine tarama penceresini ve kaynak IP adresini bildirin. WAF'ı tümden kapatmak yerine tarama yapan makinenin IP adresini izin listesine alın; aksi hâlde WAF'ın engellediği istekleri temiz sonuç sanırsınız.
Üç hazır betik: baseline, full ve API
ZAP'ın Docker imajları (ghcr.io/zaproxy/zaproxy:stable ya da Docker Hub'daki zaproxy/zap-stable) üç hazır tarama betiğiyle gelir; CI için tasarlanan küçük "bare" imajında bu betikler yoktur.| Betik | Ne yapar | Saldırı gönderir mi? | Nerede kullanılır |
|---|---|---|---|
| zap-baseline.py | Varsayılan olarak 1 dakika örümcek (spider), ardından yalnızca pasif kurallar | Hayır | Her PR, canlı ortamın hafif kontrolü |
| zap-full-scan.py | Süre sınırı olmadan örümcek, isteğe bağlı AJAX ya da istemci örümceği, ardından tam aktif tarama | Evet | Gece çalışan staging taraması |
| zap-api-scan.py | OpenAPI, SOAP veya GraphQL tanımını içe aktarır, API'lere göre ayarlanmış aktif tarama yapar | Evet (-S ile yalnızca baseline) | Sürüm öncesi API kontrolü |
Bash:
# İlk çalıştırma: tüm kuralların WARN olduğu şablonu üret (-g)
docker run --rm -v "$(pwd):/zap/wrk/:rw" -t ghcr.io/zaproxy/zaproxy:stable \
zap-full-scan.py -t https://staging.example.com -m 10 -g full-rules.conf -r full-report.html
# Sonraki çalıştırmalar: düzenlenmiş kural dosyası, AJAX örümceği, HTML ve JSON rapor
docker run --rm -v "$(pwd):/zap/wrk/:rw" -t ghcr.io/zaproxy/zaproxy:stable \
zap-full-scan.py -t https://staging.example.com -m 10 -j -T 30 \
-c full-rules.conf -r full-report.html -J full-report.json
Automation Framework ile tek dosyada tarama planı
Hazır betikler hızlı başlangıç için iyidir. Kimlik doğrulama, birden fazla örümcek, tarama politikası ve farklı rapor biçimleri devreye girince ZAP ekibinin önerdiği yol Automation Framework'tür: tüm akış tek bir YAML planında durur, plan Git'te sürümlenir ve herkes aynı taramayı çalıştırır. Plan iki bölümden oluşur: hedefi, kimlik doğrulamayı ve kullanıcıları tanımlayan env ile sırayla çalışan jobs.
YAML:
env:
contexts:
- name: staging
urls:
- https://staging.example.com
includePaths:
- "https://staging.example.com.*"
excludePaths:
- ".*/logout.*"
- ".*/account/delete.*"
- ".*/checkout/pay.*"
authentication:
method: browser
parameters:
loginPageUrl: https://staging.example.com/login
verification:
method: autodetect
sessionManagement:
method: autodetect
users:
- name: tester
credentials:
username: ${ZAP_USER}
password: ${ZAP_PASS}
parameters:
failOnError: true
failOnWarning: false
progressToStdout: true
jobs:
- type: alertFilter
alertFilters:
- ruleId: 10096
newRisk: "False Positive"
url: "https://staging.example.com/assets/.*"
urlRegex: true
- type: spider
parameters:
context: staging
user: tester
maxDuration: 10
- type: spiderAjax
parameters:
context: staging
user: tester
maxDuration: 10
- type: passiveScan-wait
parameters:
maxDuration: 10
- type: activeScan
parameters:
context: staging
user: tester
maxScanDurationInMins: 90
maxRuleDurationInMins: 10
delayInMs: 50
threadPerHost: 4
policyDefinition:
defaultStrength: Medium
defaultThreshold: Medium
- type: report
parameters:
template: traditional-html
reportDir: /zap/wrk/reports
reportFile: zap-nightly.html
- type: report
parameters:
template: sarif-json
reportDir: /zap/wrk/reports
reportFile: zap-nightly.sarif.json
- type: exitStatus
parameters:
errorLevel: High
warnLevel: Medium
Bash:
mkdir -p reports && chmod a+w reports
docker run --rm -v "$(pwd):/zap/wrk/:rw" -e ZAP_USER -e ZAP_PASS \
-t ghcr.io/zaproxy/zaproxy:stable zap.sh -cmd -autorun /zap/wrk/zap.yaml
- excludePaths: Çıkış, hesap silme ve ödeme yolları dışarıda. Örümcek "Çıkış" bağlantısına tıklarsa taramanın geri kalanı oturumsuz geçebilir.
- browser + autodetect: ZAP arayüzsüz (headless) bir tarayıcıyla giriş sayfasını açar, kullanıcı adı ve parolayı doldurur; oturum belirtecini (cookie ya da Authorization başlığı) ve "giriş yapılmış mı" kontrolünü kendisi bulmaya çalışır.
- ${ZAP_USER} ve ${ZAP_PASS}: Değişkenler sistem ortamından okunur. Parolayı plana yazmayın; CI'ın gizli değişkenlerinden (secrets) verin.
- activeScan: maxScanDurationInMins taramaya, maxRuleDurationInMins tek bir kurala üst sınır koyar; delayInMs ve threadPerHost staging sunucusunu yormamak içindir. Saldırı gücü (strength) Low, Medium, High veya Insane; uyarı eşiği (threshold) Off, Low, Medium veya High olabilir.
- alertFilter: Uyarı filtreleri varsayılan olarak yalnızca yeni oluşan uyarılara uygulanır; bu yüzden bu iş taramalardan önce gelir. Ayrıntısı aşağıda.
- exitStatus: Planın son işi. High riskli bir uyarı çıkarsa hata (1), Medium çıkarsa uyarı (2) koduyla biter; CI adımının başarısız sayılıp sayılmayacağını bu koda göre belirlersiniz.
Kimlik doğrulamalı tarama: en sık bozulan kısım
Bir uygulamanın asıl saldırı yüzeyi çoğunlukla girişten sonra başlar: profil, sipariş, yönetim ekranları, API'ler. Kimlik doğrulamasız tarama bunların hiçbirini görmez. Daha kötüsü, oturum yarıda düşer ve ZAP bunu fark etmezse isteklerin çoğu giriş sayfasına yönlenir, rapor da yanıltıcı biçimde temiz görünür. Doğrulama (verification) ayarı çalıştığında ZAP oturumun düştüğünü anlar ve yeniden giriş yapar; bu yüzden bu ayarı boş bırakmayın. Yöntem seçimi:| Yöntem | Ne zaman uygun |
|---|---|
| browser (tarayıcı tabanlı) | Klasik giriş formları ve JavaScript ile çalışan giriş sayfaları; kullanıcıya TOTP gizli anahtarı (secret) tanımlanarak iki adımlı doğrulama da geçilebilir |
| form / json | Giriş isteği tek bir POST (form ya da JSON gövdesi) olduğunda |
| http | HTTP Basic, Digest veya NTLM |
| script / client | Çok adımlı ya da standart dışı akışlar; client yöntemi tarayıcıda kaydedilmiş bir giriş betiğini oynatır |
| Başlık ortam değişkenleri | Elinizde hazır bir API belirteci (token) varsa en kısa yol |
Bash:
docker run --rm -v "$(pwd):/zap/wrk/:rw" \
-e ZAP_AUTH_HEADER_VALUE="Bearer $API_TOKEN" \
-e ZAP_AUTH_HEADER_SITE="api.staging.example.com" \
-t ghcr.io/zaproxy/zaproxy:stable zap-api-scan.py \
-t https://api.staging.example.com/openapi.json -f openapi -r api-report.html
Kimlik doğrulamanın gerçekten çalıştığını kontrol edin: rapordaki URL'ler arasında yalnızca giriş yapmış kullanıcının görebileceği sayfalar var mı, bakın. ZAP masaüstündeki Authentication Tester penceresi aynı ayarları taramadan önce denemenizi sağlar. Her rol (müşteri, bayi, yönetici) için ayrı test kullanıcısı tanımlamak, ileride yetki hatalarını aramanın da ön koşuludur.
API taraması: OpenAPI ve GraphQL
Tek sayfa uygulamalarda (SPA) ve mobil arka uçlarda asıl hedef API'dir ve örümcek, API uç noktalarının çoğunu bulamaz. zap-api-scan.py, tanımı (OpenAPI, SOAP veya GraphQL; yerel dosya ya da URL) içe aktarır ve bulunan URL'lere aktif tarama uygular. Bu tarama API'lere göre ayarlıdır: XSS gibi tarayıcıya özgü kontrollerle vakit kaybetmez; 5xx sunucu hatası dönen ve API'lerde beklenmeyen içerik türleri döndüren URL'ler için ayrıca uyarı üretir.Tanımdaki sunucu adresi staging'i göstermiyorsa -O ile ana makine adını değiştirin; Automation Framework'te aynı işi openapi işinin targetUrl alanı görür. GraphQL'de -f graphql verin. Şema verilmezse ZAP onu uç noktadan introspection ile okur; isterseniz --schema ile bir dosya ya da URL gösterebilirsiniz. -S (güvenli mod) aktif taramayı atlayıp yalnızca baseline yapar; canlı bir API'ye ilk bakış için kullanışlıdır.
Tanımın kalitesi taramanın kalitesini belirler. Örnek değerleri olmayan, zorunlu alanları eksik bir OpenAPI dosyası isteklerin çoğunun doğrulama hatasıyla (400) dönmesine ve taranan yüzeyin daralmasına yol açabilir. Taramadan önce tanımı bir şema doğrulayıcıdan geçirin, taramadan sonra da sunucu loglarında isteklerin ne kadarının 400 ile döndüğüne bakın.
Bulguları ayıklamak: risk, güven ve yanlış pozitifler
ZAP her uyarıya bir risk (High, Medium, Low, Informational) ve bir güven düzeyi (confidence) verir. Öncelik ikisinin birleşimidir: riski High ama güven düzeyi Low olan bir SQL enjeksiyonu uyarısı, elle doğrulanana kadar yalnızca bir şüphedir. Sık görülen uyarılar ve ilk adım:| Kural (ID) | Ne anlatır | İlk adım |
|---|---|---|
| 40018-40022, 40024, 40027 SQL Injection | Parametre SQL sorgusuna işleniyor olabilir; zamana dayalı (time based) türler yük altındaki sunucuda yanlış alarm verebilir | İsteği tekrar gönderip yanıtları karşılaştırın, kodda parametreli sorgu kullanıldığını kontrol edin |
| 40012, 40014, 40026 XSS | Yansıyan, kalıcı ve DOM tabanlı betik enjeksiyonu | Yükü tarayıcıda deneyin, çıktı kodlamasını (output encoding) düzeltin |
| 90022 Application Error Disclosure | Hata sayfası yığın izi (stack trace) ya da sürüm bilgisi sızdırıyor | Canlı hata sayfalarını genel bir mesaja çevirin, ayrıntıyı loga yazın |
| 10038, 10055 CSP | Content-Security-Policy yok ya da zayıf | Politikayı önce report-only modda yazıp izleyin |
| 10202 Absence of Anti-CSRF Tokens | Formda CSRF belirteci bulunamadı | GET ile çalışan arama formlarında çoğu zaman yanlış pozitif; durum değiştiren formlarda gerçek sorun |
| 10096 Timestamp Disclosure - Unix | Yanıtta Unix zaman damgasına benzeyen bir sayı var | Çoğunlukla zararsız; bağlamı kontrol edip dar bir filtreyle kapatın |
Gerçek bulgularda düzeltmeyi de ZAP ile doğrulayın: aynı isteği ZAP masaüstündeki Manual Request Editor penceresiyle yeniden gönderip yanıtı karşılaştırın, düzeltme yayına çıkınca planı tekrar çalıştırın.
CI'a yerleştirmek: hangi tarama ne zaman?
Her PR'da tam aktif tarama çalıştırmak hem yavaştır hem de staging'i sürekli kirletir. Pratik bir dağılım şöyle:- Her PR: baseline (pasif, birkaç dakika). Kurulumu yukarıda andığımız OWASP Top 10 yazısında var.
- Her gece: staging'e Automation Framework planıyla kimlik doğrulamalı aktif tarama; raporlar artefakt olarak saklanır, High bulgu bildirim üretir.
- Sürüm öncesi: API taraması ve kritik akışların elle kontrolü.
YAML:
name: zap-nightly
on:
schedule:
- cron: "30 1 * * *"
workflow_dispatch:
jobs:
zap:
runs-on: ubuntu-latest
timeout-minutes: 150
steps:
- uses: actions/checkout@v7
- name: ZAP Automation Framework
env:
ZAP_USER: ${{ secrets.ZAP_USER }}
ZAP_PASS: ${{ secrets.ZAP_PASS }}
run: |
mkdir -p reports && chmod a+w reports
docker run --rm -v "$PWD:/zap/wrk/:rw" -e ZAP_USER -e ZAP_PASS \
ghcr.io/zaproxy/zaproxy:stable zap.sh -cmd -autorun /zap/wrk/zap.yaml || [ $? -eq 2 ]
- uses: actions/upload-artifact@v7
if: always()
with:
name: zap-reports
path: reports/
ZAP'ın kendi GitHub Actions eylemleri de var: hazır betikleri saran zaproxy/action-baseline, action-full-scan ve action-api-scan, isterseniz bulgular için bir GitHub issue'su açar; Automation Framework planını çalıştıran action-af ise bu seçeneği sunmaz. Docker çalıştırabilen GitLab CI ve Jenkins ajanlarında (agent) aynı komut değişmeden çalışır; pipeline tasarımı için CI/CD yazımıza bakabilirsiniz. İmajı da bilinçli seçin: weekly her hafta değişir; stable ise her tam sürümde ve arada düzenli olarak (belgelere göre en az ayda bir) yeniden üretilir. Bu yeniden üretimler ZAP çekirdeğine dokunmaz ama tarama kuralları dâhil güncel eklentileri getirir; 2.17.0 gibi sürüm etiketleri de onlarla birlikte güncellenir. Sonuç farklarını açıklayabilmek için CI'da imajı özetine (digest) göre sabitleyin (ghcr.io/zaproxy/zaproxy@sha256:...) ve yükseltmeyi ayrı bir değişiklik olarak yapın.
ZAP'ın bulamadıkları
DAST aracı uygulamayı dışarıdan, kurallara göre dener. İş mantığı hatalarını (indirim kodunun iki kez uygulanması, sipariş adımlarının atlanması), başka bir kullanıcının kaydına erişimi (IDOR), rol bazlı yetki hatalarının çoğunu, yarış koşullarını ve tasarım zayıflıklarını tek başına bulamaz. Access Control Testing eklentisi, hangi rolün hangi URL'ye erişmesi gerektiğini tanımlarsanız karşılaştırma yapabilir; kuralları yine sizin yazmanız gerekir. Kaynak kod analizi (SAST), bağımlılık taraması (SCA) ve dönemsel elle sızma testi bu boşlukları kapatmaya yardım eder. "ZAP sıfır High buldu" cümlesi "uygulama güvenli" demek değildir; "otomatik araçla bulunabilen bariz sorunları kapattık" demektir.Sık yapılan hatalar
- Canlı ortamda izinsiz ya da yedeksiz aktif tarama.
- Çıkış (logout) yolunu dışlamamak: taramanın büyük kısmı oturumsuz geçebilir.
- Yönetici hesabıyla taramak: ZAP formları doldururken gerçek kayıtları değiştirebilir ya da silebilir. Silme yetkisi olmayan bir test rolüyle başlayın.
- Tüm Low ve Informational uyarıları geliştiricilere iş kaydı olarak açmak: ekip raporu okumayı bırakır. Önce High ve Medium.
- WAF'ın engellediği istekleri temiz sonuç sanmak.
- İmajı özetiyle (digest) sabitlemeden çalışıp sonuç farklarını açıklayamamak.
Sık sorulan sorular
ZAP ücretsiz mi?Evet. ZAP, Apache 2.0 lisanslı açık kaynak bir araçtır ve Checkmarx desteğine geçtikten sonra da açık kaynak kaldı. Ticari DAST ürünleri merkezi yönetim, raporlama ve destek ekler; tarama motoru olarak ZAP tek başına sağlam bir başlangıçtır.
OWASP ZAP ile ZAP aynı araç mı?
Evet. Proje 2023'te OWASP'tan ayrıldı ve adı ZAP olarak kaldı; komutlar, eklentiler ve belgeler aynı çizgide sürüyor. Eski blog yazılarında geçen owasp/ ön ekli Docker imajları yerine resmî belgelerdeki ghcr.io/zaproxy/zaproxy ya da zaproxy/zap-stable imajlarını kullanın.
Aktif tarama ne kadar sürer?
URL ve parametre sayısına, kural setine ve sunucunun hızına bağlıdır; küçük bir sitede dakikalar, büyük bir uygulamada saatler sürebilir. Bu yüzden maxScanDurationInMins ve örümcek süre sınırlarını baştan koyun.
Canlı ortamı hiç tarayamaz mıyım?
Pasif baseline saldırı yükü göndermediği için canlıda genellikle güvenlidir. Aktif taramayı canlıda ancak yazılı izin, yedek, düşük istek hızı ve dar kapsamla, bir bakım penceresinde düşünün.
ZAP mı, Burp Suite mi?
İkisi de araya giren vekil sunucu (intercepting proxy) olarak çalışır. ZAP'ta otomatik zafiyet tarayıcısı (scanner) dâhil her şey ücretsizdir; Docker, YAML planı ve CI ile kolayca otomatikleştirilir. Burp Suite'in ücretsiz Community sürümü elle test araçlarını içerir; web zafiyet tarayıcısı ise ücretli Professional sürümündedir. Birçok ekip CI'da ZAP, elle testte Burp kullanır.
Sonuç ve sonraki adımlar
- Staging'de silme yetkisi olmayan bir test kullanıcısı ve yazılı bir kapsam hazırlayın.
- Yukarıdaki planı kendi URL'lerinizle çalıştırın, raporda giriş sonrası sayfaların bulunduğunu doğrulayın.
- İlk raporu ayıklayın: yanlış pozitifleri gerekçesiyle alertFilter'a, gerçek bulguları iş takip sisteminize taşıyın.
- Planı gece çalışan CI işine bağlayın; başlangıçta işi yalnızca High bulgularda durdurun, ekip alıştıkça Medium bulguları da ekleyin.
What this guide covers
ZAP is an open-source dynamic application security testing (DAST) tool: it tests a web application from the outside while the application is running. For many years it was one of OWASP's flagship projects, which is why most people still search for it as "OWASP ZAP". In August 2023 the project left OWASP to join the Software Security Project under the Linux Foundation, and since September 2024 it has been developed as "ZAP by Checkmarx", still open source under the Apache 2.0 licence.Our practical web security guide covers the OWASP Top 10:2025 categories and a passive baseline scan in CI. This article picks up from there: active scanning that sends attack payloads, authenticated scanning as a logged-in user, OpenAPI and GraphQL scanning, a repeatable scan plan with the Automation Framework, and triaging what comes back. The commands were checked against the documentation for ZAP 2.17.0 (December 2025) and the official Docker images; options change between releases, so check the -h output of your own version.
Before you scan: permission, scope and environment
An active scan sends real attacks: it writes SQL injection payloads into form fields, tries script injection on parameters and produces thousands of requests in a short time. Three rules are therefore not negotiable:- Written permission. Actively scanning a system you do not own, or have not been explicitly authorised to test, can be a criminal offence. In Türkiye such acts can fall under Articles 243 (accessing an information system) and 244 (hindering or disrupting a system, deleting or altering data) of the Turkish Penal Code, and many other countries have similar computer misuse laws. Check the terms of your hosting, CDN and WAF providers too.
- Staging, not production. An active scan can create records such as comments, orders and support tickets, trigger e-mails and even delete data. Use a staging environment that resembles production but holds anonymised data, and take a backup before the scan.
- A team that knows. Tell operations the scan window and the source IP address. Rather than switching the WAF off entirely, allow-list the scanner's IP; otherwise you will read requests the WAF blocked as clean results.
The three packaged scans: baseline, full and API
ZAP's Docker images (ghcr.io/zaproxy/zaproxy:stable, or zaproxy/zap-stable on Docker Hub) ship with three ready-made scan scripts; the small "bare" image built for CI does not include them.| Script | What it does | Sends attacks? | Where to use it |
|---|---|---|---|
| zap-baseline.py | Spiders for 1 minute by default, then runs passive rules only | No | Every PR, a light check of production |
| zap-full-scan.py | Spiders with no time limit, optionally runs the AJAX or client spider, then a full active scan | Yes | Nightly scan of staging |
| zap-api-scan.py | Imports an OpenAPI, SOAP or GraphQL definition and runs an active scan tuned for APIs | Yes (baseline only with -S) | API check before a release |
Bash:
# First run: generate the rules template with every rule set to WARN (-g)
docker run --rm -v "$(pwd):/zap/wrk/:rw" -t ghcr.io/zaproxy/zaproxy:stable \
zap-full-scan.py -t https://staging.example.com -m 10 -g full-rules.conf -r full-report.html
# Later runs: the edited rules file, the AJAX spider, HTML and JSON reports
docker run --rm -v "$(pwd):/zap/wrk/:rw" -t ghcr.io/zaproxy/zaproxy:stable \
zap-full-scan.py -t https://staging.example.com -m 10 -j -T 30 \
-c full-rules.conf -r full-report.html -J full-report.json
The Automation Framework: the whole scan in one file
The packaged scripts are a good start. Once authentication, more than one spider, a scan policy and several report formats come into play, the ZAP team's recommended route is the Automation Framework: the whole flow lives in one YAML plan, the plan is versioned in Git, and everyone runs the same scan. A plan has two parts: env, which defines the target, authentication and users, and jobs, which run in order.
YAML:
env:
contexts:
- name: staging
urls:
- https://staging.example.com
includePaths:
- "https://staging.example.com.*"
excludePaths:
- ".*/logout.*"
- ".*/account/delete.*"
- ".*/checkout/pay.*"
authentication:
method: browser
parameters:
loginPageUrl: https://staging.example.com/login
verification:
method: autodetect
sessionManagement:
method: autodetect
users:
- name: tester
credentials:
username: ${ZAP_USER}
password: ${ZAP_PASS}
parameters:
failOnError: true
failOnWarning: false
progressToStdout: true
jobs:
- type: alertFilter
alertFilters:
- ruleId: 10096
newRisk: "False Positive"
url: "https://staging.example.com/assets/.*"
urlRegex: true
- type: spider
parameters:
context: staging
user: tester
maxDuration: 10
- type: spiderAjax
parameters:
context: staging
user: tester
maxDuration: 10
- type: passiveScan-wait
parameters:
maxDuration: 10
- type: activeScan
parameters:
context: staging
user: tester
maxScanDurationInMins: 90
maxRuleDurationInMins: 10
delayInMs: 50
threadPerHost: 4
policyDefinition:
defaultStrength: Medium
defaultThreshold: Medium
- type: report
parameters:
template: traditional-html
reportDir: /zap/wrk/reports
reportFile: zap-nightly.html
- type: report
parameters:
template: sarif-json
reportDir: /zap/wrk/reports
reportFile: zap-nightly.sarif.json
- type: exitStatus
parameters:
errorLevel: High
warnLevel: Medium
Bash:
mkdir -p reports && chmod a+w reports
docker run --rm -v "$(pwd):/zap/wrk/:rw" -e ZAP_USER -e ZAP_PASS \
-t ghcr.io/zaproxy/zaproxy:stable zap.sh -cmd -autorun /zap/wrk/zap.yaml
- excludePaths: logout, account deletion and payment stay out. If the spider follows a "Log out" link, the rest of the scan can run without a session.
- browser + autodetect: ZAP opens the login page in a headless browser, fills in the username and password, and tries to work out by itself which token carries the session (a cookie or an Authorization header) and how to tell whether it is still logged in.
- ${ZAP_USER} and ${ZAP_PASS}: the variables are read from the system environment. Keep passwords out of the plan and pass them from your CI secrets.
- activeScan: maxScanDurationInMins caps the scan and maxRuleDurationInMins caps any single rule; delayInMs and threadPerHost keep the load on staging reasonable. Attack strength is Low, Medium, High or Insane; the alert threshold is Off, Low, Medium or High.
- alertFilter: by default, alert filters only apply to alerts raised after they are defined, so this job comes before the scans; more on this below.
- exitStatus: the last job. The plan exits with an error (1) if any High risk alert is raised and with a warning (2) for Medium, so your CI step can fail on it.
Authenticated scanning: the part that breaks most often
Most of an application's attack surface sits behind the login: profiles, orders, admin screens, APIs. An unauthenticated scan sees none of it. Worse, if the session drops halfway through and ZAP does not notice, most requests end up on the login page and the report looks misleadingly clean. With working verification, ZAP detects the lost session and logs in again, so do not leave the verification settings empty. Choosing a method:| Method | When it fits |
|---|---|
| browser (browser-based) | Classic login forms and JavaScript-driven login pages; with a TOTP secret defined for the user it can pass two-factor authentication too |
| form / json | The login is a single POST with a form or JSON body |
| http | HTTP Basic, Digest or NTLM |
| script / client | Multi-step or non-standard flows; the client method replays a login script recorded in the browser |
| Header environment variables | The shortest route when you already have an API token |
Bash:
docker run --rm -v "$(pwd):/zap/wrk/:rw" \
-e ZAP_AUTH_HEADER_VALUE="Bearer $API_TOKEN" \
-e ZAP_AUTH_HEADER_SITE="api.staging.example.com" \
-t ghcr.io/zaproxy/zaproxy:stable zap-api-scan.py \
-t https://api.staging.example.com/openapi.json -f openapi -r api-report.html
Confirm that authentication actually worked: the report should list URLs that only a logged-in user can reach. The Authentication Tester dialog in the ZAP desktop lets you try the same settings before a scan. Defining a separate test user for each role (customer, reseller, admin) is also the precondition for hunting authorisation bugs later.
API scanning: OpenAPI and GraphQL
In single-page applications and mobile back ends the API is the real target, and a spider finds few API endpoints. zap-api-scan.py imports the definition (OpenAPI, SOAP or GraphQL, from a local file or a URL) and runs an active scan against the URLs it finds. The scan is tuned for APIs: it does not waste time on browser-specific checks such as XSS, and it raises extra alerts for URLs that return 5xx server errors or content types you would not expect from an API.If the server address in the definition does not point at staging, override the hostname with -O; in the Automation Framework the openapi job's targetUrl does the same. For GraphQL pass -f graphql; without a schema ZAP reads it from the endpoint by introspection, or you can point --schema at a file or URL. -S (safe mode) skips the active scan and runs a baseline only, which is handy for a first look at a live API.
The definition sets the quality of the scan. An OpenAPI file without example values and with missing required fields can make most requests fail validation with a 400, shrinking the surface that actually gets tested. Run the definition through a schema validator before the scan, and afterwards check the server logs for how many of the scan's requests came back 400.
Triage: risk, confidence and false positives
ZAP gives every alert a risk (High, Medium, Low, Informational) and a confidence level. Priority comes from both: a High risk SQL injection with Low confidence is a suspicion until someone confirms it by hand. Common alerts and the first step for each:| Rule (ID) | What it means | First step |
|---|---|---|
| 40018-40022, 40024, 40027 SQL Injection | A parameter may reach an SQL query; the time-based variants can raise false alarms on a server under load | Resend the request and compare responses; check in the code that the query is parameterised |
| 40012, 40014, 40026 XSS | Reflected, persistent and DOM-based script injection | Try the payload in a browser; fix the output encoding |
| 90022 Application Error Disclosure | An error page leaks a stack trace or version details | Serve a generic error page in production and log the detail |
| 10038, 10055 CSP | Content-Security-Policy missing or weak | Write the policy in report-only mode first and watch the reports |
| 10202 Absence of Anti-CSRF Tokens | No CSRF token found in a form | Often a false positive on GET search forms; a real issue on state-changing forms |
| 10096 Timestamp Disclosure - Unix | A number in the response looks like a Unix timestamp | Usually harmless; check the context and close it with a narrow filter |
Use ZAP to confirm real fixes too: resend the same request with the Manual Request Editor in the ZAP desktop and compare the response, then rerun the plan once the fix is deployed.
Fitting it into CI: which scan, when?
A full active scan on every PR is slow and keeps polluting staging. A practical split:- Every PR: the baseline (passive, a few minutes); the setup is in the OWASP Top 10 guide mentioned above.
- Every night: an authenticated active scan of staging with the Automation Framework plan; reports are kept as artifacts and High findings trigger a notification.
- Before a release: the API scan plus a manual check of critical flows.
YAML:
name: zap-nightly
on:
schedule:
- cron: "30 1 * * *"
workflow_dispatch:
jobs:
zap:
runs-on: ubuntu-latest
timeout-minutes: 150
steps:
- uses: actions/checkout@v7
- name: ZAP Automation Framework
env:
ZAP_USER: ${{ secrets.ZAP_USER }}
ZAP_PASS: ${{ secrets.ZAP_PASS }}
run: |
mkdir -p reports && chmod a+w reports
docker run --rm -v "$PWD:/zap/wrk/:rw" -e ZAP_USER -e ZAP_PASS \
ghcr.io/zaproxy/zaproxy:stable zap.sh -cmd -autorun /zap/wrk/zap.yaml || [ $? -eq 2 ]
- uses: actions/upload-artifact@v7
if: always()
with:
name: zap-reports
path: reports/
ZAP also publishes ready-made GitHub Actions: zaproxy/action-baseline, action-full-scan and action-api-scan wrap the packaged scans and can open a GitHub issue with the findings if you want, while action-af runs an Automation Framework plan and has no such option. On GitLab CI and Jenkins agents that can run Docker the same command works unchanged; for pipeline design see our CI/CD article. Choose the image on purpose as well: weekly changes every week, while stable is rebuilt for each full release and regularly in between (at least monthly, according to the docs). Those rebuilds leave the ZAP core alone but bring in updated add-ons, scan rules included, and version tags such as 2.17.0 move with them. To be able to explain changes in results, pin the image by digest in CI (ghcr.io/zaproxy/zaproxy@sha256:...) and upgrade in a separate change.
What ZAP will not find
A DAST tool probes the application from the outside, following rules. On its own it will not find business logic flaws (a discount code applied twice, a skipped checkout step), access to another user's records (IDOR), most role-based authorisation bugs, race conditions or design weaknesses. The Access Control Testing add-on can compare roles if you define which role should reach which URL, but you still write those rules. Static analysis (SAST), dependency scanning (SCA) and a periodic manual penetration test help close these gaps. "ZAP found zero Highs" does not mean "the application is secure"; it means "we fixed the obvious issues an automated tool can find".Common mistakes
- Active scanning production without permission or a backup.
- Not excluding logout: much of the scan can run without a session.
- Scanning with an admin account: while filling in forms, ZAP can modify or delete real records. Start with a test role that cannot delete anything.
- Opening every Low and Informational alert as a ticket for developers: the team stops reading the report. High and Medium first.
- Reading requests blocked by the WAF as clean results.
- Running an image that is not pinned by digest and being unable to explain why the results changed.
Frequently asked questions
Is ZAP free?Yes. ZAP is open source under the Apache 2.0 licence and stayed open source after moving to Checkmarx's support. Commercial DAST products add central management, reporting and support; as a scanning engine, ZAP on its own is a solid start.
Are OWASP ZAP and ZAP the same tool?
Yes. The project left OWASP in 2023 and kept the name ZAP; commands, add-ons and documentation carried on along the same line. Instead of the owasp/-prefixed Docker images you may see in older blog posts, use ghcr.io/zaproxy/zaproxy or zaproxy/zap-stable from the official documentation.
How long does an active scan take?
It depends on the number of URLs and parameters, the rule set and the speed of the server: minutes for a small site, hours for a large application. That is why you should set maxScanDurationInMins and spider time limits from the start.
Can I scan production at all?
A passive baseline is generally safe in production because it sends no attack payloads. Consider an active scan of production only with written permission, a backup, a low request rate and a narrow scope, inside a maintenance window.
ZAP or Burp Suite?
Both work as intercepting proxies. In ZAP everything, the automated scanner included, is free and easy to automate with Docker, a YAML plan and CI. Burp Suite Community Edition contains the manual testing tools, while the web vulnerability scanner is part of the paid Professional edition. Many teams use ZAP in CI and Burp for manual testing.
Conclusion and next steps
- Prepare a test user on staging that cannot delete anything, and a written scope.
- Run the plan above with your own URLs and confirm that the report contains pages behind the login.
- Triage the first report: move false positives into alertFilter with a reason, real findings into your issue tracker.
- Wire the plan into a nightly CI job; at first let only High findings fail it, and add Medium once the team is used to it.
Son düzenleyen: