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/mktemp → open) 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+openve 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çinO_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.