İçeriğe atla
0 XP 0
12 dkderinleşmeyle 13 dk45 XP6 etkinlik

Kod tarama: SAST ile açığı yazılmadan önce yakalamak

Bu derste
  • SAST kaynak kodu çalıştırmadan okur, kullanıcı verisini tehlikeli işleme kadar izler ve açığı çekme isteği aşamasında yakalar.
  • Bandit ve Semgrep çıktısını satır satır oku. Güvenilmeyen verinin kod içinde izlediği yolu bulan bir kural yaz. Yanlış alarmı gerekçesiyle işaretle ve SARIF sonucunu incele.

Puan bütün etkinlikleri cevaplayınca hesaba yazılır. Bu dersi tamamlayınca +45 XP alacaksın. Giriş yapmadan ilerlersen puan yalnız bu tarayıcıda durur.

Altay Kargo'nun geliştirme ekibi geçen ay bir açık yüzünden gece nöbetine kaldı. İade listesi ucu, durum parametresini doğrudan SQL sorgusuna ekliyordu. Kodu inceleyen iki kişi de satırı fark etmedi.

Açığı üretimde dışarıdan bir araştırmacı buldu ve bildirdi. Aynı hatayı satır yazılırken yakalamanın bir yolu var: durağan uygulama güvenlik testi, kısaca SAST (Static Application Security Testing). SAST kaynak kodu okur, uygulamayı hiç çalıştırmaz.

Derleyici gibi çalışır, ama söz dizimi hatası yerine güvenlik zayıflığı arar. Tarama saniyeler ya da dakikalar sürer ve geliştirici sonucu kod birleşmeden önce görür. Bu derste SAST'ın kodu nasıl okuduğunu öğrenir, iki araçla çözümlü tarama yapar, yanlış pozitifi yönetir ve aracı geliştirme hattına yerleştirirsin.

SAST kodu nasıl okur

SAST aracı kodu düz metin gibi taramaz. İlk adımda kodu ayrıştırır ve soyut söz dizimi ağacını (AST) çıkarır. Ağaçta her atama, her fonksiyon çağrısı ve her dizge birleştirme ayrı bir düğümdür. İkinci adımda kontrol akışını kurar: hangi satırdan sonra hangisi çalışabilir, hangi dal hangi koşulda açılır.

Üçüncü adımda veri akışını izler. Bir değişkene giren değerin hangi değişkenlere ve hangi çağrılara taşındığını adım adım takip eder. Basit araçlar ilk adımda kalır ve ağaçta riskli bir desen arar.

Gelişmiş araçlar üç katmanı birlikte kullanır. Desen arayan araç hızlıdır ama gürültülüdür. Akış izleyen araç daha isabetlidir, ama daha yavaş çalışır ve daha çok bellek ister.

Veri akışı analizi üç kavrama dayanır. Kaynak (source), dışarıdan gelen ve güvenilmeyen veridir: istek parametresi, başlık, yüklenen dosya. Hedef (sink), bu verinin zarar verebileceği işlemdir: SQL sorgusu, kabuk komutu, HTML çıktısı. Temizleyici (sanitizer), veriyi güvenli hâle getiren adımdır: tam sayıya çevirme, parametreli sorgu, bağlama uygun kaçış.

Araç kaynaktan hedefe bir yol bulursa ve yolda temizleyici yoksa bulgu üretir. Buna kirli veri takibi (taint analysis) denir. Analizin kapsamı da önemlidir.

Fonksiyon içi analiz yalnız tek bir fonksiyonun gövdesine bakar. Fonksiyonlar arası (interprocedural) analiz değeri bir çağrıdan ötekine taşır, dosyalar arası analiz bunu bütün depoya yayar. Kaynak bir fonksiyonda, hedef başka bir fonksiyondaysa yolu yalnız fonksiyonlar arası analiz görür.

Etkinlik 1 / 6

SAST bir kod tabanını nasıl inceler?

grep ve Bandit ile desen taraması

Araç kurmadan önce bile hedefleri hızla listeleyebilirsin. Elle inceleme çoğu zaman tehlikeli çağrıları aramakla başlar. Altay Kargo'nun kodunda SQL çalıştıran iki çağrı var: imlecin execute yöntemi ve ekibin calistir yardımcısı.

grep bu çağrıların geçtiği her satırı dosya adı ve satır numarasıyla döker. Liste sana nereye bakacağını söyler, ama hangisinin açık olduğunu söylemez. 24. satır parametreli sorgudur ve güvenlidir.

7. satırdaki sorgunun nereden geldiğini görmek için fonksiyonu yukarı doğru okuman gerekir. Araçlar bu okumayı otomatikleştirir. Ardından yine insan gözü devreye girer ve aracın bulgusunu doğrular.

$ grep -rn -e 'execute(' -e 'calistir(' --include='*.py' src/
src/sepet.py:7:    sonuc = veritabani.calistir(sorgu)
src/sepet.py:18:    cursor.execute("SELECT * FROM iade WHERE durum = '" + durum + "'")
src/sepet.py:24:    cursor.execute("SELECT toplam FROM sepet WHERE kullanici_id = %s", (kid,))
Hedef çağrıların hızlı listesi. Hangisinin gerçek açık olduğuna karar vermek için akışı okursun.

Bandit, Python için yazılmış açık kaynak bir SAST aracıdır. Her dosyanın söz dizimi ağacını çıkarır ve düğümleri eklenti adı verilen kontrollerden geçirir. Her kontrolün bir kimliği vardır. B608, dizge birleştirmeyle kurulan SQL sorgusunu arar.

Altay Kargo'nun sepet servisinde aracı src klasöründe çalıştırırsın. -r seçeneği klasörü alt klasörleriyle birlikte tarar. Taranan sepet.py dosyası iki uç taşır: sepet toplamı ve iade listesi. Çıktının bulgu bölümü şöyle görünür.

$ bandit -r src/
Test results:
>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction.
   Severity: Medium   Confidence: Low
   CWE: CWE-89
   Location: src/sepet.py:6:12
5	def sepet_toplam(kullanici_id):
6	    sorgu = "SELECT toplam FROM sepet WHERE kullanici_id = '" + kullanici_id + "'"
7	    sonuc = veritabani.calistir(sorgu)
--------------------------------------------------
>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction.
   Severity: Medium   Confidence: Medium
   CWE: CWE-89
   Location: src/sepet.py:18:19
17	    durum = request.args.get("durum")
18	    cursor.execute("SELECT * FROM iade WHERE durum = '" + durum + "'")
19	    return cursor.fetchall()
Bandit 1.9 çıktısının bulgu bölümü, bağlantı satırları kısaltılmış.

Her bulgu aynı düzende gelir. İlk satır kontrol kimliğini ve adını verir: B608, hardcoded_sql_expressions. Severity, açık gerçekse etkinin ne kadar büyük olacağıdır. Confidence ise aracın bulguya ne kadar güvendiğidir.

İki bulgu aynı ciddiyeti taşır ama güvenleri farklıdır. İlk bulguda sorgu önce bir değişkene atanır, sonra başka bir fonksiyona gider. Bandit sorgunun nereye gittiğini izlemez ve güveni Low tutar.

İkinci bulguda birleştirme doğrudan execute çağrısının içindedir. Bandit bu çağrıyı tanır ve güveni Medium düzeyine çıkarır. CWE-89, SQL enjeksiyonunun ortak zayıflık kimliğidir.

Location satırı dosyayı, satırı ve sütunu verir, altındaki numaralı satırlar bağlamdır. Bandit verinin kullanıcıdan gelip gelmediğini sormaz. Sorgu sabit değerlerle kurulsa da aynı uyarıyı verirdi.

Etkinlik 2 / 6

SAST aracı bu fonksiyonda hangi satırı işaretler?

Zafiyetli satıra tıkla.

Semgrep ile kirli veri kuralı yazmak

Bandit desen arar. Semgrep ise taint modunda kaynaktan hedefe giden akışı izler. Kural bir YAML dosyasıdır ve desenler hedef dilin kendi söz dizimiyle yazılır.

Hazır kural setleri yaygın kalıpları yakalar. Özel kural ise senin kodundaki fonksiyon adlarını tanır. Altay Kargo'nun güvenlik ekibi, istek parametresinin SQL sorgusuna ulaştığı yolları bulan bir kural yazdı. Kuralı kural.yaml dosyasına kaydeder ve src klasörüne uygularsın.

rules:
  - id: sepet-sql-enjeksiyonu
    mode: taint
    languages: [python]
    severity: ERROR
    message: İstek verisi süzülmeden SQL sorgusuna ulaşıyor.
    pattern-sources:
      - pattern: request.args.get(...)
    pattern-sanitizers:
      - pattern: int(...)
    pattern-sinks:
      - pattern: veritabani.calistir(...)
      - pattern: $CUR.execute(...)
kural.yaml: kaynak, temizleyici ve iki hedef.

mode: taint satırı kuralı akış izleme moduna geçirir. pattern-sources kaynağı tanımlar: request.args.get ile okunan her değer kirlidir. Parantez içindeki üç nokta, argüman ne olursa olsun eşleşme demektir.

pattern-sanitizers temizleyiciyi tanımlar. int() içinden geçen değer artık sayıdır ve sorguya metin taşıyamaz. pattern-sinks hedefleri listeler. İlki ekibin kendi veritabanı yardımcısıdır, ikincisi herhangi bir imleç nesnesinin execute yöntemidir.

$CUR bir üst değişkendir (metavariable) ve imlecin adı ne olursa olsun eşleşir. severity bulgunun düzeyini, message ise geliştiricinin göreceği açıklamayı belirler. Kuralı çalıştırınca şu sonuç gelir.

$ semgrep --config kural.yaml src/
┌─────────────────┐
│ 2 Code Findings │
└─────────────────┘

    src/sepet.py
   ❯❯❱ sepet-sql-enjeksiyonu
          İstek verisi süzülmeden SQL sorgusuna ulaşıyor.

           18┆ cursor.execute("SELECT * FROM iade WHERE durum = '" + durum + "'")
            ⋮┆----------------------------------------
           24┆ cursor.execute("SELECT toplam FROM sepet WHERE kullanici_id = %s", (kid,))
Semgrep 1.128 Community Edition çıktısı.

18. satır beklenen bulgudur: durum parametresi doğrudan sorguya birleşiyor. 24. satır ise yanlış pozitiftir.

Orada sorgu parametrelidir ve kirli kid değeri metne değil, ayrı bir demete girer. Sorun kuraldadır. Hedef deseni bütün execute çağrısını kapsıyor, bu yüzden herhangi bir argümandaki kirli veri uyarı üretiyor. Düzeltme, hedefi yalnız ilk argümana, yani sorgu metnine daraltmaktır.

Aşağıdaki parçada focus-metavariable bunu yapar ve tarama tek bulguya iner: 18. satır. Bir şey daha dikkat çeker. Bandit'in işaretlediği sepet_toplam, Semgrep çıktısında hiç yok.

Kaynak sepet_sayfasi fonksiyonunda, hedef sepet_toplam içinde. Semgrep'in ücretsiz Community Edition sürümü fonksiyon içi analiz yapar. Fonksiyonlar arası izleme ücretli Pro motoruyla gelir.

    pattern-sinks:
      - patterns:
          - pattern: $CUR.execute($SORGU, ...)
          - focus-metavariable: $SORGU
      - pattern: veritabani.calistir(...)
Hedef yalnız sorgu metnine daraltıldı. Parametre demetindeki değer artık uyarı üretmez.

Etkinlik 3 / 6

Altay Kargo'nun kural.yaml dosyasını src klasörüne uygulayan Semgrep komutunu yaz.

ogrenci@lab

Yanlış pozitif, kör nokta ve bastırma

Yanlış pozitif, güvenli bir satırın açık diye işaretlenmesidir. Az önce 24. satırda gördüğün buydu. Yanlış negatif daha sinsidir: açık vardır ama araç susar.

sepet_toplam, Semgrep'in ücretsiz sürümünde tam böyle kaldı. Yanlış negatifin tanıdık üç nedeni var. Birincisi kuralın kaynağı ya da hedefi tanımamasıdır.

İkincisi çerçeve sihridir: ORM ya da bağımlılık enjeksiyonu, değeri aracın göremediği bir yoldan taşır. Üçüncüsü dinamik dildir. getattr ya da eval ile kurulan bir çağrının hedefi, kod çalışmadan bilinmez.

SAST'ın daha geniş kör noktaları da var. Sunucu ayarını, açık portu ve çalışma anında atlanan kontrolü görmez. Bir kullanıcının başka kullanıcının siparişini görebilmesi gibi iş mantığı hataları da kod deseninden anlaşılmaz.

İncelenip zararsız bulunan bulgu bastırılır. Her aracın satır içi bir işareti var. Semgrep için # nosemgrep: kural-kimliği, Bandit için # nosec B608 yazarsın.

Kimliği her zaman yaz. Kimliksiz işaret o satırdaki bütün kuralları susturur ve ileride eklenecek gerçek bir açığı da gizler. Aşağıdaki örnekte tablo adı iki sabitten biridir.

SQL'de tablo adı parametre olarak geçilemez, bu yüzden birleştirme zorunludur. Kullanıcıdan gelen değer yine parametreyle gider. Altay Kargo'da her bastırma bir gerekçe, bir sahip ve bir bitiş tarihi taşır.

def sepet_toplam(cursor, kid, arsiv):
    # Gerekçe: tablo adı iki sabitten biri, kullanıcı girdisi değil.
    # Sahip: sepet-ekibi. Bitiş: 2026-12-31.
    tablo = "sepet_arsiv" if arsiv else "sepet"
    sorgu = "SELECT toplam FROM " + tablo + " WHERE kullanici_id = %s"  # nosec B608
    cursor.execute(sorgu, (kid,))
    return cursor.fetchone()
Bandit bu satırı artık bulgu olarak göstermez, özet bölümünde bastırılan bulgu olarak sayar.

Etkinlik 4 / 6

Doğru mu, yanlış mı?

SAST, çalışma zamanında ortaya çıkan yanlış yapılandırmaları kod taramasıyla yakalar.

Hattaki yeri ve SARIF

Gereksinimgüvenlik ihtiyacıTasarımtehdit modeliKodlamagözden geçirmeTestSAST, DASTYayıngizli anahtar, izleme
Güvenli geliştirme hattı. SAST kodlama durağına oturur, dinamik testler test durağında çalışır.

SAST kaynak koda ihtiyaç duyar ama çalışan uygulamaya ihtiyaç duymaz. Bu yüzden şemadaki kodlama durağına oturur. Testi hattın erken durağına yaklaştırma yaklaşımına sola kaydırma (shift left) denir. Hata, satırı yazan kişinin aklı hâlâ koddayken ve düzeltmesi ucuzken yakalanır.

Altay Kargo SAST'ı dört noktada çalıştırır. Editör eklentisi satır yazılırken uyarır. pre-commit kancası commit'ten önce hızlı kuralları koşturur. Çekme isteğinde yalnız yeni bulgular gösterilir.

Semgrep bunun için --baseline-commit seçeneğini kullanır ve verilen commit'te zaten bulunan sonuçları gizler. Gece ise bütün depo yavaş ama derin kurallarla taranır. Aracı ilk kurduğunda yüzlerce eski bulgu çıkar. Bunları temel çizgiye (baseline) alır ve ayrı bir iş listesinde eritirsin.

Kapı yalnız yeni bulguda kırılır. Böylece eski borç yeni kodun önünü kesmez. Bandit de -b seçeneğiyle eski bir raporu temel çizgi olarak kullanır.

Derin taramanın tanınmış bir örneği CodeQL'dir. CodeQL önce kodu sorgulanabilir bir veritabanına çevirir, sonra bu veritabanında güvenlik sorguları çalıştırır. Python gibi derlenmeyen dillerde derleme komutu gerekmez. Sonuç bir SARIF dosyasına yazılır.

SARIF (Static Analysis Results Interchange Format), OASIS'in 2.1.0 sürümüyle standartlaştırdığı JSON tabanlı bir biçimdir. Semgrep de --sarif seçeneğiyle aynı biçimi üretir. Ortak biçim sayesinde farklı araçların bulguları aynı panoda toplanır.

$ codeql database create codeql-db --language=python --source-root=.
$ codeql database analyze codeql-db codeql/python-queries \
    --format=sarif-latest --output=sonuc.sarif
Önce veritabanı kurulur, sonra Python sorgu paketi çalıştırılır.
{
  "ruleId": "py/sql-injection",
  "message": { "text": "This SQL query depends on a [user-provided value](1)." },
  "locations": [{ "physicalLocation": {
    "artifactLocation": { "uri": "src/sepet.py" },
    "region": { "startLine": 18 } } }],
  "codeFlows": [{ "threadFlows": [{ "locations": [
    { "location": { "physicalLocation": { "region": { "startLine": 17 } } } },
    { "location": { "physicalLocation": { "region": { "startLine": 18 } } } }
  ] }] }]
}
sonuc.sarif dosyasındaki tek bir sonuç, kısaltılmış.

ruleId bulguyu üreten kuralın kimliğidir. message insan için yazılmış açıklamadır; köşeli parantezdeki bağlantı kaynağın konumuna işaret eder. locations bulgunun durduğu dosyayı ve satırı verir; burada hedef satır, yani 18'dir.

level alanı bulgunun düzeyidir ve error, warning, note ya da none değerini alır. Sonuçta yazmıyorsa kuralın varsayılan düzeyi geçerli olur.

codeFlows en değerli alandır. Kaynaktan hedefe adımları sırayla listeler: veri 17. satırda istekten okunur, 18. satırda sorguya girer.

İnceleyen kişi bu akışı izleyerek bulgunun gerçek olup olmadığına hızla karar verir. GitHub kod tarama ekranı ve bulgu yönetim sistemleri bu alanları okur ve akışı adım adım çizer.

Etkinlik 5 / 6

Testi geliştirme hattının erken durağına yaklaştırıp hatayı ucuzken yakalama yaklaşımının adı: sola ____.

SAST çalışan bir uygulamaya ihtiyaç duymadığı için en erken durakta koşar. Bedeli de buradan gelir: kod çalışırken ortaya çıkan davranışı göremez. Eksik bir güvenlik başlığı, yanlış ayarlanmış bir sunucu ya da çalışma anında atlanan bir yetki denetimi, kaynak kodun hiçbir satırında görünmeyebilir.

Sıradaki derste uygulamayı ayağa kaldırıp dışarıdan sınayan yöntemi göreceksin. Aynı derste altyapı tanım dosyalarını tarayan araçları da tanıyacaksın.

Etkinlik 6 / 6

SAST taraması için hangisi gerekli değildir?

Derinleş:CodeQL kodu nasıl sorgular

CodeQL kaynak kodu ilişkisel bir veritabanına çevirir. Fonksiyonlar, çağrılar, değişkenler ve aralarındaki veri akışı tablolara yazılır. Sorgular QL adlı mantıksal bir dille yazılır ve bu tablolardan desen seçer. Bir SQL enjeksiyonu sorgusu kabaca şunu söyler: uzak kullanıcıdan gelen bir değerden SQL çalıştıran bir çağrıya giden yolu bul.

Hazır sorgu paketleri her dil için ayrı yayımlanır. Python için codeql/python-queries paketi kullanılır. Sorgular açık kaynak olarak yayımlandığı için bir bulgunun hangi mantıkla üretildiğini kendin okuyabilirsin. Derlenen dillerin çoğunda veritabanı derleme sırasında kurulur, bu yüzden create komutuna derleme komutu da verilebilir.

Lisans konusuna dikkat et. CodeQL CLI açık kaynak kod tabanlarında ücretsizdir. Kurumun özel depolarında kullanmak için GitHub Code Security lisansı gerekir.

Derinleş:Çerçeveler SAST hakkında ne der

NIST'in güvenli yazılım geliştirme çerçevesi SSDF (SP 800-218), PW.7 uygulamasında insanın okuyabildiği kodun incelenmesini ya da analiz edilmesini ister. Kurum, elle kod incelemesini mi, araçla analizi mi, yoksa ikisini birden mi kullanacağını kendi belirler. PW.7.2 ayrıca bulunan her sorunun ekibin iş akışına ya da iş takip sistemine kaydedilmesini ve önceliklendirilmesini ister. Çalıştırılabilir kodun testi ayrı bir uygulamadır ve PW.8 başlığında durur.

ISC2'nin CSSLP sınav planında 5. alan güvenli yazılım uygulamasını kapsar. Bu alanın 5.2 hedefi koddaki güvenlik risklerinin analizini anlatır ve SAST ile elle kod incelemesini yan yana sayar. İki çerçeve de aracı tek başına yeterli saymaz; araç ile insan gözünü birlikte ister.

Bu derste bir hata mı gördün? Bildir