Kihagyás

Buffer overflow/Puffertúlcsordulás

Az a jelenség, amikor egy fix hosszúságú tömbbe (pufferbe) való íráskor nincs ellenőrizve a bemenet hossza, ezért szomszédos memóriaterületek is felülírásra kerülnek.

Egy szemléltető példa, melyben az A tömb melletti 2 bájtos B változó értéke a hibás beolvasás után elvész:

bof1

bof2

A hozzá tartozó kódrész, ami a BOF miatt váratlanul fog működni (a két változó verembeli sorrendje függhet a fordítótól, de feltételezzük, hogy az ábrának megfelelően a B követi az A-t)

1
2
3
4
5
6
7
8
9
int main(){
  char A[8];
  short B = 3;
  scanf("%s", A);
  if(B != 3){
    printf("Oh no!");
  }
  return 0;
}

A buffer overflow eredménye undefined behavior (ami azt jelenti, hogy a C szabvány nem írja elő, hogyan viselkedjen, kis túlzással bármi megtörténhet) Előforduló hatások:

  • A leggyakrabban programelszállás, mert a verembe szemét kerül (elmentett ebp, visszatérési cím elromlik). Ilyenkor kapunk szegmentálási hiba/segmentation fault hibaüzenetet. Az operációs rendszer ennek a szignálnak a formájában jelzi a program számára, hogy tiltott (nem létező, számára elérhetetlen) memóriaterületet próbált elérni.
  • Megváltozott programműködés. Olyan ágak futnak le, amiknek nem szabadna, nem várt függvények hívódnak meg stb. Ezt akarja elérni egy rosszindulatú felhasználó. Ilyen inputot összerakni nem könnyű. (Ha nem sikerül neki, akkor megelégszik az előző ponttal, ami Denial of Service attacknak minősül)
  • Előfordulhat, hogy fejlesztéskor észre sem vesszük a hibát, mert egy nagyon kis mértékű túlcsordulás nem biztos, hogy láthatóan befolyásolja a program működését. Pl. a karakter tömböt csak a lezáró nullával írjuk túl.

A példa egy vermen foglalt tömb túlcsordulását mutatja be, azonban ez dinamikusan, heap-en foglalt tömböknél is felléphet. Heap túlcsordulás esetén az említett rosszindulatú felhasználónak másképp kell egy eljárnia, mint veremtúlcsordulás kihasználásakor.

Milyen nyelvek érintettek?

A buffer overflow sérülékenység leginkább a C/C++ nyelvek sajátossága. Olyan menedzselt nyelveknél, mint a Java, C#, Python ez a hiba nem jöhet elő olyan egyszerűen, mivel a programok virtuális környezetben, tömb-határ ellenőrzés mellett futnak. Pl. Java esetén ArrayIndexOutOfBounds kivétel dobódik, ami programelszállást okozhat, ha nincs lekezelve, de legalább trükkös inputtal sem lehet rávenni a programot, hogy másképp működjön.

Védelmi vonalak

  • Biztonságos programozás: akadályozzuk meg, hogy legyen lehetőség buffer overflow-ra a kódunkban!

  • Ha van alternatíva, akkor használjunk olyan függvényeket, amelyek biztosítják a tömbhatárok ellenőrzését

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    char tomb[10];
    scanf("%s", tomb); //nincs ellenőrizve a tömb határ, BOF veszély
    sprintf(tomb, "Can be too long"); //nincs ellenőrizve a tömb határ, BOF veszély
    
    scanf("%9s", tomb); //biztonságos (+1 karakter kell a lezáró 0-nak)
    fgets(tomb, sizeof(tomb), stdin); //biztonságos
    snprintf(tomb, sizeof(tomb), "Can't be too long"); //biztonságos
    
    strcpy(tomb, "Too loooooooooong"); //nem biztonságos, a céltömb túlírható
    strncpy(tomb, "Too loooooooooong", sizeof(tomb));//bof szempontból biztonságos, de nem garantálja a termináló 0 karakter odamásolását a tömb végére!!!!
    
    char second[] = "An ending once and for all";
    unsigned buffer_size = 10;
    strcat(tomb, second); //nem biztonságos, a céltömb túlírható
    strncat(tomb, second, buffer_size - strlen(second) - 1);
    

Amit a fordító tesz/tehet értünk:

  • Position independent kód generálása: az utasítások csak relatív címekkel szerepelnek a binárisban így a virtuális memória tetszőleges helyére berakhatja őket az OS. Defaultból ez szokott lenni a gcc beállítása, de érdemes ellenőrizni.
  • Stack protector/kanári/verem süti érték használata: függvényhíváskor bekerül egy jelzőérték a verembe a tömb és a program futását vezérlő értékek (elmentett ebp, visszatérési cím) közé. Ha felülíródik túlindexelés esetén, elszáll a program (stack smashing detected) mielőtt rosszindalú irányba fordulhatna a futása. Így legalább csak az elszállás lesz a legnagyobb baj. Persze nem biztos, hogy felülíródik, ha a tömb és a kanári között van egyéb értékes adat. Az -fstack-protector kapcsolóval kell bekapcsolni, defaultból ki lehet kapcsolva. (Kanári elnevezés eredete: régen a bányákban kanári madarakat használtak a mérges gázok megjelenésének kimutatására. A kanárik hamarabb elhaláloztak a gázoktól, mint az emberek, így volt még idő kimenekülni)
  • Nem futtatható verem: a program verme nem futtathatóként lesz megjelölve. Így hiába injektál bele a rosszindulatú felhasználó futtatható kódot (pl. shellcode). A gcc defaultból ilyen binárist állít elő, külön kell kérni, hogy futtatható legyen a programunk verme.

Amit az OS tesz értünk:

  • ASLR (Adress Space Layout Randomization): A programoknak kiosztott memóriaterületet random választja meg a memóriában. Ha a bináris nem position independent, a memória többi részét akkor is randomizálja. Így lefutásról lefutásra más címen fog lefoglalódni pl. egy lokális változó és a rosszindulatú támadó kisebb valószínűséggel tudja kitapasztalni, hol keresse azt. Defaultból a randomizálás is be van kapcsolva.

Heap VS Stack

A stack és a heap overflow kihasználása kissé eltérő. A stack overflow esetén egy fő cél a függvények visszatérési címének felülírása szokott lenni. Ezzel szemben a heap-en dinamikusan lefoglalt program adatokat tárolunk. Ott például az adat manipulálása vagy a dinamikus foglalás meta-adatainak korrputtá tétele lehet egy támadó célja. A példákban a stack overflow van bemutatva.

Példafeladatok


Utolsó frissítés: 2022-04-05 13:34:39