OWASP Dependency Check ile Bağımlılık Güvenlik Açığı Taraması

Bağımlılık yönetimi meselesine çoğu ekip gerektiği kadar ciddiyetle yaklaşmıyor. “Kod çalışıyor, testler geçiyor, deploy edelim” mantığıyla ilerlerken, kullandığınız açık kaynak kütüphanelerin içinde onlarca kritik güvenlik açığı yatıyor olabilir. Log4Shell olayını hatırlıyor musunuz? Milyonlarca uygulamada kullanılan Log4j kütüphanesindeki o tek açık, neredeyse tüm sektörü birkaç gün felç etti. OWASP Dependency Check tam da bu tür senaryolara karşı geliştirilmiş, olgun ve güvenilir bir araç.

OWASP Dependency Check Nedir, Ne Yapar?

OWASP Dependency Check, projelerinizin kullandığı üçüncü taraf kütüphaneleri NVD (National Vulnerability Database) ve diğer açık güvenlik veritabanlarıyla karşılaştırarak bilinen CVE’leri tespit eden bir statik analiz aracıdır. Java, .NET, JavaScript, Python, Ruby ve daha birçok ekosistemdeki bağımlılıkları destekler.

Araç, bağımlılıklarınızı taradıktan sonra size CVSS skoru, CVE numarası, etkilenen sürüm aralığı ve varsa önerilen düzeltme sürümüyle birlikte detaylı bir rapor üretir. Bu raporu HTML, XML, JSON veya JUnit formatında alabilirsiniz; ikincisi özellikle CI/CD pipeline’larına entegrasyon için çok işe yarıyor.

SonarQube ile karşılaştırmak gerekirse, SonarQube ağırlıklı olarak kendi yazdığınız kodu analiz eder. Dependency Check ise dışarıdan çektiğiniz paketlere odaklanır. İkisi birbirinin rakibi değil, tamamlayıcısı. İyi kurulmuş bir CI/CD pipeline’ında ikisi de beraber çalışmalı.

Kurulum ve Temel Kullanım

Dependency Check’i birkaç farklı şekilde kullanabilirsiniz: standalone CLI aracı olarak, Maven/Gradle plugin olarak, ya da Docker container üzerinden. Hepsini sırayla ele alalım.

CLI Kurulumu

# En güncel sürümü GitHub releases sayfasından çekin
wget https://github.com/jeremylong/DependencyCheck/releases/download/v9.0.9/dependency-check-9.0.9-release.zip

unzip dependency-check-9.0.9-release.zip
cd dependency-check/bin

# Basit bir tarama başlatmak için
./dependency-check.sh 
  --project "MyApp" 
  --scan /path/to/your/project 
  --format HTML 
  --out /tmp/dc-reports

İlk çalıştırmada araç NVD veritabanını indireceği için biraz zaman alır. Bağlantı hızınıza göre bu 10-20 dakika sürebilir. Sonraki çalıştırmalarda sadece güncelleme yapar.

Docker ile Kullanım

CI/CD ortamlarında kurulum derdi yaşamamak için Docker imajını tercih ediyorum. Hem temiz kalıyor ortam, hem de sürüm yönetimi kolaylaşıyor.

docker run --rm 
  -v $(pwd):/src 
  -v $(pwd)/reports:/reports 
  owasp/dependency-check:latest 
  --project "MyApp" 
  --scan /src 
  --format "HTML,JSON" 
  --out /reports 
  --nvdApiKey YOUR_NVD_API_KEY

NVD API key almak için nvd.nist.gov adresine kayıt olmanız gerekiyor. Ücretsiz ve birkaç dakika sürüyor. API key olmadan da çalışır ama rate limiting nedeniyle veritabanı güncellemeleri çok yavaş kalır; özellikle CI ortamlarında bu ciddi sorun yaratır.

Maven Projelerinde Entegrasyon

Java ekosistemi için en temiz entegrasyon Maven plugin üzerinden oluyor. pom.xml’e şunu ekleyin:

<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>9.0.9</version>
    <configuration>
        <nvdApiKey>${env.NVD_API_KEY}</nvdApiKey>
        <failBuildOnCVSS>7</failBuildOnCVSS>
        <format>HTML,JSON,JUNIT</format>
        <outputDirectory>${project.build.directory}/dependency-check-report</outputDirectory>
        <suppressionFiles>
            <suppressionFile>dependency-check-suppressions.xml</suppressionFile>
        </suppressionFiles>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Bu konfigürasyonda dikkat çekmek istediğim birkaç şey var. failBuildOnCVSS parametresi 7 ve üzeri CVSS skoruna sahip açıklar bulunduğunda build’i başarısız sayar. Bu eşiği ekibinizin risk toleransına göre ayarlayabilirsiniz; production ortamı için 7, geliştirme ortamı için belki 9 gibi. suppressionFile parametresi ise yanlış pozitifler veya kabul edilen riskler için kullanılıyor, buna ilerleyen bölümlerde döneceğiz.

Taramayı çalıştırmak için:

mvn org.owasp:dependency-check-maven:check -DnvdApiKey=$NVD_API_KEY

Jenkins Pipeline Entegrasyonu

Pratikte en çok karşılaştığım senaryo Jenkins CI üzerinde Dependency Check çalıştırmak. Şöyle bir Jenkinsfile örneği üzerinden gidelim:

pipeline {
    agent any
    
    environment {
        NVD_API_KEY = credentials('nvd-api-key')
    }
    
    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
        }
        
        stage('Dependency Check') {
            steps {
                sh """
                    docker run --rm 
                      -v ${WORKSPACE}:/src 
                      -v ${WORKSPACE}/dc-reports:/reports 
                      -e NVD_API_KEY=${NVD_API_KEY} 
                      owasp/dependency-check:latest 
                      --project "${JOB_NAME}" 
                      --scan /src/target 
                      --format "HTML,JUNIT,JSON" 
                      --out /reports 
                      --nvdApiKey ${NVD_API_KEY} 
                      --failOnCVSS 7 
                      --suppressionFile /src/dependency-check-suppressions.xml
                """
            }
            post {
                always {
                    publishHTML([
                        allowMissing: false,
                        alwaysLinkToLastBuild: true,
                        keepAll: true,
                        reportDir: 'dc-reports',
                        reportFiles: 'dependency-check-report.html',
                        reportName: 'Dependency Check Report'
                    ])
                    junit 'dc-reports/dependency-check-junit.xml'
                }
            }
        }
        
        stage('Tests') {
            steps {
                sh 'mvn test'
            }
        }
    }
}

Bu pipeline’da Dependency Check sonuçlarını hem HTML rapor hem de JUnit sonuçları olarak yayınlıyoruz. JUnit formatı sayesinde Jenkins test sonuçları arayüzünde her CVE ayrı bir “test vakası” gibi görünüyor; bu da hangi açıkların hangi build’de çıktığını takip etmeyi kolaylaştırıyor.

Önemli bir pratik not: Dependency Check’i pipeline’ın başına mı koymalısınız yoksa sonuna mı? Ben genellikle build adımından sonra, testlerden önce çalıştırıyorum. Neden? Çünkü build başarısız olursa security taraması çalışmış oluyor ve boşa gidiyor. Ama testlerden önce koyarsanız, kritik güvenlik açığı olan bir build’in test aşamasında zaman harcamasını da önlemiş oluyorsunuz.

GitLab CI Entegrasyonu

GitLab kullanan ekipler için .gitlab-ci.yml örneği:

dependency_check:
  stage: security
  image: owasp/dependency-check:latest
  variables:
    NVD_API_KEY: $NVD_API_KEY
  script:
    - /usr/share/dependency-check/bin/dependency-check.sh
        --project "$CI_PROJECT_NAME"
        --scan "$CI_PROJECT_DIR"
        --format "HTML,JSON,JUNIT"
        --out "$CI_PROJECT_DIR/dependency-check-report"
        --nvdApiKey "$NVD_API_KEY"
        --failOnCVSS 7
        --suppression "$CI_PROJECT_DIR/dependency-check-suppressions.xml"
  artifacts:
    when: always
    paths:
      - dependency-check-report/
    reports:
      junit: dependency-check-report/dependency-check-junit.xml
    expire_in: 1 week
  cache:
    key: dependency-check-db
    paths:
      - /usr/share/dependency-check/data/
  allow_failure: false
  only:
    - main
    - develop
    - merge_requests

Cache konfigürasyonuna özellikle dikkat edin. NVD veritabanını her pipeline çalışmasında sıfırdan indirmek hem zaman hem de bant genişliği israfı. Cache’i doğru yapılandırırsanız sadece delta güncellemeler indirilir.

Yanlış Pozitifler ve Suppress Dosyası Yönetimi

Dependency Check’in en can sıkıcı taraflarından biri yanlış pozitifler. Özellikle bazı kütüphaneler farklı artifact’larla karıştırılabiliyor ya da bir CVE sizin kullandığınız özelliği hiç etkilemeyebiliyor. Bunları suppress etmek için XML formatında bir dosya oluşturuyoruz:

<?xml version="1.0" encoding="UTF-8"?>
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
    
    <!-- Spring Framework false positive - etkilenen özelliği kullanmıyoruz -->
    <suppress>
        <notes>
            <![CDATA[
            CVE-2022-22965 (Spring4Shell) - Uygulamamız DataBinder ile
            ilgili etkilenen endpoint pattern'ini kullanmıyor.
            Güvenlik ekibinin onayı: SEC-2023-042
            Gözden geçirme tarihi: 2024-03-01
            ]]>
        </notes>
        <cve>CVE-2022-22965</cve>
        <gav regex="true">org.springframework:spring-webmvc:.*</gav>
    </suppress>
    
    <!-- Test scope bağımlılığı, production'ı etkilemiyor -->
    <suppress>
        <notes>
            <![CDATA[
            Bu kütüphane sadece test scope'unda kullanılıyor.
            Production artifact'ına dahil değil.
            ]]>
        </notes>
        <cve>CVE-2023-44487</cve>
        <filePath regex="true">.*mockito-core.*.jar</filePath>
    </suppress>

</suppressions>

Suppress dosyası yönetiminde ekipler çok gevşek davranabiliyor. “Şu CVE’yi kapat geç” mentalitesi zamanla suppress dosyasını anlamsız bir listeye dönüştürüyor. Bunun önüne geçmek için birkaç kural koymanızı öneririm:

  • Her suppress kaydı mutlaka bir açıklama içermeli
  • Güvenlik ekibinin onay referansı eklenebilirse eklenmeli
  • Periyodik gözden geçirme tarihi belirtilmeli
  • Suppress dosyası değişikliklerinde ayrı bir onay akışı (pull request review) zorunlu olmalı

NVD Veritabanı Önbellekleme Stratejisi

Kurumsal ortamlarda sık karşılaştığım bir sorun: Her build’de NVD veritabanını güncellemek hem yavaş hem de NVD’nin rate limit’lerine çarpmaya neden oluyor. Özellikle büyük ekiplerde onlarca paralel pipeline çalışırken bu ciddi bir bottleneck oluşturuyor.

Çözüm olarak merkezi bir NVD mirror veya yerel bir veritabanı cache katmanı kurabilirsiniz. İşte basit bir Ansible ile NVD veritabanını merkezi bir lokasyona güncelleyen ve build agent’larına NFS üzerinden mount eden bir yaklaşım:

#!/bin/bash
# /usr/local/bin/update-nvd-cache.sh
# Bu script cron ile günde bir kez çalışır

NVD_DATA_DIR="/data/shared/dependency-check-data"
NVD_API_KEY="${NVD_API_KEY}"
LOCK_FILE="/tmp/nvd-update.lock"

if [ -f "$LOCK_FILE" ]; then
    echo "Update already in progress, exiting."
    exit 0
fi

touch "$LOCK_FILE"
trap "rm -f $LOCK_FILE" EXIT

echo "[$(date)] NVD veritabanı güncelleniyor..."

docker run --rm 
  -v "${NVD_DATA_DIR}:/usr/share/dependency-check/data" 
  owasp/dependency-check:latest 
  --updateonly 
  --nvdApiKey "${NVD_API_KEY}"

if [ $? -eq 0 ]; then
    echo "[$(date)] NVD veritabanı başarıyla güncellendi."
    # Tüm build agent'larına bildirim gönder
    curl -s -X POST "${SLACK_WEBHOOK}" 
      -d '{"text":"NVD veritabanı güncellendi. Build agent'''lar hazır."}'
else
    echo "[$(date)] NVD güncelleme başarısız!" >&2
    exit 1
fi

Bu scripti crontab’a ekleyin:

# Her gün sabah 03:00'da NVD veritabanını güncelle
0 3 * * * /usr/local/bin/update-nvd-cache.sh >> /var/log/nvd-update.log 2>&1

Build agent’larında ise bu merkezi veritabanını kullanmaları için Dependency Check’i şöyle çağırın:

./dependency-check.sh 
  --project "MyApp" 
  --scan /path/to/project 
  --data /mnt/shared/dependency-check-data 
  --noupdate 
  --format HTML 
  --out /tmp/reports

–noupdate parametresi kritik. Bu şekilde her agent kendi veritabanını güncellemeye çalışmaz, merkezi cache’i kullanır.

SonarQube ile Birlikte Kullanım

Dependency Check’i SonarQube’e entegre etmek mümkün ve özellikle security dashboard’larını tek bir yerde toplamak isteyenler için çok mantıklı. SonarQube’un Dependency Check plugin’ini kullanabilir veya JSON raporunu SonarQube’un generic issue import formatına dönüştürebilirsiniz.

Basit entegrasyon için dependency-check-sonar-plugin kullanılabilir, ancak ben çoğu zaman JSON raporunun CI/CD pipeline’ında ayrı arşivlenmesini ve raporlanmasını tercih ediyorum. Neden? Çünkü SonarQube’un ücretsiz Community Edition’ında security hotspot yönetimi kısıtlı. Dependency Check sonuçlarını doğrudan SonarQube’a aktardığınızda bunları takip etmek için Enterprise edition gerekebiliyor.

Alternatif olarak JSON çıktısını parse edip Grafana’ya göndermek, özellikle DevSecOps olgunluk seviyesi yüksek ekipler için çok daha esnek bir çözüm sunuyor. Bu konuyu ayrı bir yazıya bırakıyorum.

Gerçek Hayat Senaryosu: E-Ticaret Platformunda Açık Tespiti

Bir müşteride yaşadığım gerçek bir senaryoyu aktarayım. Java tabanlı bir e-ticaret platformuna Dependency Check entegrasyonu yapıyorduk. İlk taramada 3 kritik, 12 yüksek, 34 orta seviye açık çıktı. Ekip başta “hepsini düzeltemeyiz, çok iş” dedi.

Biz de şöyle bir yaklaşım önerdik: Önce failOnCVSS eşiğini 9’a ayarlayın. Sadece kritik açıkları kapatın ve yeni açıkların girmesini engelleyin. Sonra her sprint’te bir-iki tane “teknik borç” olarak yüksek seviye açıkları kapatın.

Pratik olarak şöyle ilerlendi:

# İlk aşama: Sadece kritik açıklar için build'i durdur
--failOnCVSS 9

# 2 ay sonra: Yüksek açıklar da dahil
--failOnCVSS 7

# 4 ay sonra: Orta seviye açıklar için uyarı (build durdurmadan)
--failOnCVSS 7 --junitFailOnCVSS 4

Bu kademeli yaklaşım sayesinde ekip paniklemeden süreci yönetebildi. 6 ay sonunda kritik ve yüksek açıkların tamamı kapatılmıştı. Bu yaklaşımı küçük ekiplere de tavsiye ediyorum; her şeyi bir anda çözmek yerine sürdürülebilir bir güvenlik kültürü oluşturmak daha değerli.

Raporları Anlamlandırmak

Dependency Check HTML raporu ilk bakışta bunaltıcı gelebilir. Öncelik sırasına göre yaklaşmanızı öneriyorum:

  • CVSS 9.0-10.0 (Critical): Hemen müdahale. Eğer yükseltme mümkün değilse güvenlik ekibiyle birlikte risk değerlendirmesi yapın ve geçici mitigasyon önlemleri alın
  • CVSS 7.0-8.9 (High): Bir sonraki sprint içinde ele alın. Sürüm yükseltme planı oluşturun
  • CVSS 4.0-6.9 (Medium): Teknik borç listesine ekleyin, periyodik gözden geçirme sürecine dahil edin
  • CVSS 0.1-3.9 (Low): Bilgi amaçlı takip edin, acil eylem gerektirmez
  • Yanlış pozitif şüphesi: CPE bilgisini ve etkilenen bileşeni dikkatlice okuyun. Bazen araç farklı bir ürünün açığını sizin kütüphanenizle eşleştiriyor olabilir

Sonuç

OWASP Dependency Check, bağımlılık güvenliğini otomatize etmenin en olgun ve geniş topluluk desteğine sahip yollarından biri. Kurulumu basit, entegrasyonları güçlü ve ücretsiz. Bu üçü bir arada pek sık bulunmuyor.

Ama asıl mesele aracı çalıştırmak değil, sonuçlara göre aksiyon almak. Pek çok ekipte Dependency Check çalışıyor, rapor üretiyor, kimse bakmıyor. Bu durumu önlemek için raporları CI/CD sonuçlarıyla doğrudan ilişkilendirin, kritik açıkları build’i durduracak şekilde yapılandırın ve suppress mekanizmasını disiplinli kullanın.

DevSecOps yolculuğunun başındaki ekipler için başlangıç noktası olarak şunları öneririm: Önce mevcut durumu görün (failBuildOnCVSS olmadan çalıştırın), sonra kritik açıkları kapatın, ardından CI/CD’ye entegre edin ve son olarak kademeli eşik düşürme stratejisiyle devam edin. Güvenlik, bir ürün değil süreçtir. Dependency Check bu sürecin otomatize edilmesine ciddi katkı sağlar.

Bir yanıt yazın

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