Uncontrolled format string¶
Attacker-controlled veri
printfailesinin fonksiyonlarına format argument'ı olarak geçtiğinde,%pve%ngibi conversion specifier'lar fonksiyonun variadic argument slot'larını okur ve yazar — belleği leak ederek ve arbitrary adreslere yazarak.
Mechanism¶
CWE-134 (Use of Externally-Controlled Format String) şu durumda oluşur: "ürün, argument olarak
bir format string kabul eden bir fonksiyon kullanır, ama format string harici bir kaynaktan
gelir." printf(user) kanonik bug'dır: printf ilk argument'ın güvenilir bir template ve
herhangi bir % directive'inin caller'ın sağladığı variadic argument'larla desteklendiğini
bekler. Kullanıcı template'i kontrol ettiğinde, her % specifier'ı variadic ABI'deki sonraki
slot'u — önce register'lar, sonra stack — caller'ın oraya bir şey geçirip geçirmediğine
bakılmaksızın tüketir.
Note
İhlal edilen invariant, "conversion specifier'ların sayısı ve tipi, gerçekten geçirilen variadic argument'larla eşleşir"dir. Onu kır ve:
%p/%xardışık argument slot'larını okur ve yazdırır — stack içeriğini, kaydedilmiş pointer'ları, canary'leri ve libc/PIE adreslerini leak eder (birformat-string-read).- Direct parameter access
%N$, kendinden öncekileri dolaşmadan N'inci argument'ı seçer:%7$pdoğrudan 7. slot'u yazdırır. Bu, bir attacker'ın doğrudan ilgilendiği bir değere ve (stack'e yerleştirilmiş bir hedef adresle birleştirildiğinde) bir write'ın kullandığı slot'a index'lemesine olanak verir. %nyazdırmaz; o ana kadar çıktıya verilen karakter sayısını, ilgili argument slot'undan alınanint *'a yazar. Çıktıyı field-width (%100c) ile doldurarak ve bir argument slot'una bir hedef adres yerleştirerek attacker hem değeri hem de hedefi kontrol eder — bir write primitive'i. Length modifier'lar%hn(2 byte) ve%hhn(1 byte) dar, aşamalı write'lara izin verir, böylece milyarlarca karakter yazdırmadan tam bir pointer inşa edilebilir.
Walkthrough¶
Güvenlik açığı olan program (CWE-134 gösterici biçim):
#include <stdio.h>
int main(int argc, char **argv) {
char buf[256];
fgets(buf, sizeof buf, stdin);
printf(buf); // VULNERABLE: user data is the format string
return 0;
}
Pointer conversion'ları ve direct parameter access ile stack'i leak et:
$ printf '%p %p %p %p\n' | ./fmt
0x7ffd... 0x7f... 0x55... (nil)
$ printf 'AAAA.%6$p\n' | ./fmt # direct access to the 6th argument slot
AAAA.0x41414141 # our "AAAA" appears -> offset 6 is our buffer
Kontrol edilen offset bilindiğinde, %n ile yaz. Hedef adresi buffer'a yerleştir, yazdırılan
uzunluğu istenen değere kadar doldur, sonra %n'i ona nişanla:
# pseudo-layout: [target addr][padding...]%<width>c%<offset>$hn
# %<offset>$hn writes (chars printed so far) into *target as a 2-byte value
Warning
%n write'larını yanlış yapmak kolaydır: karakter sayısı kümülatiftir, dolayısıyla önce
daha büyük 16-bit yarıları inşa et ve write'ları değere göre sırala. _FORTIFY_SOURCE'lu
modern glibc, writable-format string'lerde %n'i reddeder ve birçok distro
-Wformat-security / -Werror=format-security ile build eder, dolayısıyla literal bir
printf(user) çoğunlukla compile olmaz ya da runtime'da abort eder.
Offset neden önemli
"offset" (örn. 6$), printf'in ilk variadic slot'undan input byte'larının stack'te
oturduğu yere kadar argument slot'ları cinsinden mesafedir. AAAA gibi bir marker'ı ardından
%1$p %2$p ... göndererek ve hangi index'in 0x41414141 yazdırdığını not ederek bul. Oradan
%offset$p kendi kontrol ettiğin word'leri okur ve %offset$n yerleştirdiğin bir pointer
üzerinden yazar.
Detection¶
-Wformat -Wformat-securityile compile et; GCC/Clang non-literal format string'leri işaretler.-Werror=format-securityonları build hatalarına çevirir.- Format argument'ı bir string literal olmayan herhangi bir
printf/fprintf/sprintf/syslog/errcall'u için static analysis ve code review. - Runtime:
_FORTIFY_SOURCE, writable bir format string'teki%n'de abort eder.
Mitigation¶
- Format string'i statik ve asla user-controlled olmayacak şekilde tut:
printf(user)yerineprintf("%s", user)(CWE-134'ün birincil mitigation'ı). - Mümkün olduğunda
%n'siz API'leri tercih et; glibc'de format string'in read-only bellekteki mapping'leri%n'i devre dışı bırakır. - Harici kaynaklı veriyi bir formatting fonksiyonuna ulaşmadan önce validate/encode et.
- Full RELRO + ASLR, ortaya çıkan herhangi bir write'ın etkisini sınırlar.
İlgili notlar
Bu, genel CWE-134 bug sınıfıdır (read + write birlikte). Yalnızca leak tarafının
(%x/%p/%s) ayrıntısı için format-string-read
sub-primitive'ine bak.