Skip to content

Time-of-check time-of-use (TOCTOU)

Bir kaynağın durumunun doğrulandığı (check) ve sonra üzerinde işlem yapıldığı (use) bir race; ama attacker, iki işlem arasındaki pencerede kaynağı değiştirir, böylece işlem amaçlanmayan bir hedefe çarpar.

Mechanism

Note

CWE-367 ("Time-of-check Time-of-use (TOCTOU) Race Condition"): ürün, bir kaynağı kullanmadan önce bir durum doğrulaması yapar, ama kaynağın durumu check ile use arasında değişebilir ve check'in sonuçlarını geçersiz kılar. Bu bug bir atomicity violation'dır — check ve use, kaynağa kararlı bir handle yerine bir dolaylı isim (bir path string) ile atıfta bulunan iki ayrı, non-atomic işlemdir. İsim iki kez resolve edildiği için, namespace'i kontrol eden bir attacker (örn. yazabildiği bir dizin), iki resolution arasında ismin neyi gösterdiğini takas edebilir — klasik olarak normal bir dosyayı bir symlink ile değiştirerek.

Kanonik desen, bir setuid programda access()-sonra-open()'dır: access() "kibar" olmak için real UID'ye karşı check yapar, ama open() effective (privileged) UID ile çalışır, dolayısıyla race'i kazanmak privileged process'in, unprivileged kullanıcının asla doğrudan açamayacağı bir dosyayı açmasını sağlar.

Walkthrough

Güvenlik açığı olan desen (doğrudan CWE-367'nin örneğinden): privileged bir program çağıranın real UID'siyle izin check eder, sonra dosyayı effective UID'siyle açar.

/* VULNERABLE: setuid-root helper */
if (access(file, W_OK) == 0) {     /* CHECK: real-UID permission test */
    f = fopen(file, "w+");          /* USE: opens with effective (root) UID */
    operate(f);                     /* attacker's privileged write */
}

Exploit döngüye girer, file'ı kullanıcının sahip olduğu bir dosya ile korumalı bir hedefe giden bir symlink arasında flip'ler ve access() ile fopen() arasındaki boşlukla yarışır:

# attacker shell: keep swapping the path between check and use
touch /tmp/race/ownedfile          # passes access(W_OK)
while true; do
  ln -sf /tmp/race/ownedfile   /tmp/race/target   # check sees a writable file
  ln -sf /etc/passwd           /tmp/race/target   # use follows symlink to root file
done
# meanwhile run the setuid victim in a tight loop against /tmp/race/target

Symlink access() sırasında sahip olunan dosyayı, fopen() sırasında ise /etc/passwd'yi gösterdiğinde, privileged process /etc/passwd'yi yazma için açar.

Neden tek bir çalıştırma nadiren kazanır — ve attacker'lar pencereyi nasıl genişletir

Race window'u sub-microsecond olabilir, dolayısıyla naive döngünün isabet oranı düşüktür. Pratik TOCTOU exploit'leri pencereyi büyütür veya geciktirir: kernel'i use sırasında derin bir path'i resolve etmek için zaman harcamaya zorlayan filesystem-maze symlink'leri (böylece takasın yerleşmek için zamanı olur), takasın tam olarak syscall'lar arasında ateşlenmesi için inotify/ptrace tabanlı scheduling veya victim'i yavaşlatmak için bellek/FS baskısı. (Kernel tarafı race'ler aynı fikri kullanır — pencereyi interrupt'larla uzatan EXPRACE'e bakın.)

Warning

open()'dan önce access() ders kitabı niteliğinde bir TOCTOU footgun'udur, ama desen geniş ölçüde tekrar eder: stat()-sonra-open(), lstat()-sonra-unlink(), bir dosyanın owner/mode'unu check edip sonra isimle yeniden açma, "yoksa bir temp file oluştur" (tmpnam/mktempopen) ve path ile bir config/binary'yi doğrulayıp sonra yükleme. Bir yield point boyunca use-by-name ile takip edilen herhangi bir check-by-name şüphelidir.

Detection

  • Static analysis / linter'lar access()+open(), mktemp+open ve benzeri check-sonra-use-by-name dizilerini işaretler.
  • Runtime'da, attacker'ın yazabileceği path'leri takip eden setuid binary'leri denetle; bir strace'te beklenmeyen bir symlink resolution'ı (path iki syscall arasında farklılaşır) bir ipucudur.

Mitigation

  • İsimler üzerinde değil, handle'lar üzerinde çalış: kaynağı bir kez open() et, sonra açık file descriptor'ı fstat() ile doğrula — path'i asla yeniden resolve etme.
  • O_NOFOLLOW (ve yeni dosyalar için O_CREAT|O_EXCL) ile aç, böylece symlink'ler/önceden var olan dosyalar yerine geçirilemez.
  • Kullanıcının kontrol edebileceği dosyalara dokunmadan önce privilege'leri drop et (effective UID/GID'yi real UID'ye ayarla), böylece check ve use aynı yetkiyi taşır — access() hilesini ortadan kaldırarak.
  • Özel, attacker'ın yazamayacağı bir dizin kullan veya tüm işlem boyunca bir lock tut.

References