Obranné programování - Defensive programming

Defenzivní programování je forma obranného designu, která má zajistit nepřetržitou funkci kusu softwaru za nepředvídaných okolností. Obranné programovací postupy se často používají tam, kde je potřeba vysoká dostupnost , bezpečnost nebo zabezpečení .

Defenzivní programování je přístup ke zlepšení softwaru a zdrojového kódu , pokud jde o:

  • Obecná kvalita - snížení počtu chyb a problémů se softwarem .
  • Zajištění srozumitelnosti zdrojového kódu - zdrojový kód by měl být čitelný a srozumitelný, aby byl schválen při auditu kódu .
  • Nechání softwaru se chovat předvídatelným způsobem i přes neočekávané vstupy nebo akce uživatelů.

Příliš defenzivní programování však může chránit před chybami, se kterými se nikdy nesetkáte, a tím vzniknou náklady na běh a údržbu. Existuje také riziko, že pasti kódu zabrání příliš mnoha výjimkám , což může mít za následek nepozorované nesprávné výsledky.

Zabezpečené programování

Zabezpečené programování je podmnožinou obranného programování, které se týká počítačové bezpečnosti . Jde o zabezpečení, ne nutně o bezpečnost nebo dostupnost ( softwaru může být povoleno určitým způsobem selhat). Stejně jako u všech druhů obranného programování je vyhýbání se chybám primárním cílem; motivací však není ani tak snížit pravděpodobnost selhání v normálním provozu (jako by šlo o bezpečnost), ale zmenšit povrch útoku - programátor musí předpokládat, že software může být aktivně zneužíván k odhalování chyb a že chyby lze zneužívat zlomyslně.

int risky_programming(char *input) {
  char str[1000]; 
  
  // ...
  
  strcpy(str, input);  // Copy input.
  
  // ...
}

Funkce bude mít za následek nedefinované chování, pokud má vstup více než 1000 znaků. Někteří začínající programátoři nemusí mít pocit, že se jedná o problém, za předpokladu, že žádný uživatel nezadá tak dlouhý vstup. Tento konkrétní chyba ukazuje chybu, která umožňuje přetečení vyrovnávací paměti využije . Zde je řešení tohoto příkladu:

int secure_programming(char *input) {
  char str[1000+1];  // One more for the null character.

  // ...

  // Copy input without exceeding the length of the destination.
  strncpy(str, input, sizeof(str)); 

  // If strlen(input) >= sizeof(str) then strncpy won't null terminate. 
  // We counter this by always setting the last character in the buffer to NUL,
  // effectively cropping the string to the maximum length we can handle.
  // One can also decide to explicitly abort the program if strlen(input) is 
  // too long.
  str[sizeof(str) - 1] = '\0';

  // ...
}

Útočné programování

Útočné programování je kategorie obranného programování s přidaným důrazem, že určité chyby by neměly být řešeny obranně . V této praxi se mají zpracovávat pouze chyby mimo kontrolu programu (například vstup uživatele); v tuto metodiku je třeba důvěřovat samotnému softwaru a datům z obranné linie programu .

Důvěryhodná platnost interních dat

Příliš defenzivní programování
const char* trafficlight_colorname(enum traffic_light_color c) {
    switch (c) {
        case TRAFFICLIGHT_RED:    return "red";
        case TRAFFICLIGHT_YELLOW: return "yellow";
        case TRAFFICLIGHT_GREEN:  return "green";
    }
    return "black"; // To be handled as a dead traffic light.
    // Warning: This last 'return' statement will be dropped by an optimizing
    // compiler if all possible values of 'traffic_light_color' are listed in
    // the previous 'switch' statement...
}
Útočné programování
const char* trafficlight_colorname(enum traffic_light_color c) {
    switch (c) {
        case TRAFFICLIGHT_RED:    return "red";
        case TRAFFICLIGHT_YELLOW: return "yellow";
        case TRAFFICLIGHT_GREEN:  return "green";
    }
    assert(0); // Assert that this section is unreachable.
    // Warning: This 'assert' function call will be dropped by an optimizing
    // compiler if all possible values of 'traffic_light_color' are listed in
    // the previous 'switch' statement...
}

Důvěryhodné softwarové komponenty

Příliš defenzivní programování
if (is_legacy_compatible(user_config)) {
    // Strategy: Don't trust that the new code behaves the same
    old_code(user_config);
} else {
    // Fallback: Don't trust that the new code handles the same cases
    if (new_code(user_config) != OK) {
        old_code(user_config);
    }
}
Útočné programování
// Expect that the new code has no new bugs
if (new_code(user_config) != OK) {
    // Loudly report and abruptly terminate program to get proper attention
    report_error("Something went very wrong");
    exit(-1);
}

Techniky

Zde jsou některé obranné programovací techniky:

Inteligentní opětovné použití zdrojového kódu

Pokud je existující kód testován a je známo, že funguje, jeho opětovné použití může snížit pravděpodobnost zavedení chyb.

Opětovné použití kódu však není vždy dobrou praxí, protože také zesiluje poškození potenciálního útoku na původní kód. Opětovné použití v tomto případě může způsobit vážné chyby v obchodních procesech .

Starší problémy

Před opětovným použitím starého zdrojového kódu, knihoven, rozhraní API, konfigurací atd. Je třeba zvážit, zda je staré dílo platné pro opětovné použití, nebo zda je pravděpodobné, že bude náchylné k problémům se staršími verzemi.

Starší problémy jsou problémy, když se od starých návrhů očekává, že budou fungovat s dnešními požadavky, zvláště když staré návrhy nebyly vyvinuty nebo testovány s ohledem na tyto požadavky.

Mnoho softwarových produktů má problémy se starým starým zdrojovým kódem; například:

  • Starší kód možná nebyl navržen v rámci iniciativy obranného programování, a proto může mít mnohem nižší kvalitu než nově navržený zdrojový kód.
  • Starší kód mohl být napsán a testován za podmínek, které již neplatí. Staré testy zajištění kvality již nemusí mít žádnou platnost.
    • Příklad 1 : Starší kód mohl být navržen pro vstup ASCII, ale nyní je vstupem UTF-8.
    • Příklad 2 : Starší kód mohl být zkompilován a testován na 32bitových architekturách, ale při kompilaci na 64bitových architekturách mohou nastat nové aritmetické problémy (např. Neplatné testy podepsanosti, přetypování neplatných typů atd.).
    • Příklad 3 : Starší kód mohl být cílen pro offline počítače, ale po přidání připojení k síti se stane zranitelným.
  • Starší kód není napsán s ohledem na nové problémy. Například zdrojový kód napsaný v roce 1990 bude pravděpodobně náchylný k mnoha zranitelnostem při vkládání kódu , protože většina takových problémů nebyla v té době široce chápána.

Pozoruhodné příklady staršího problému:

  • BIND 9 , který představili Paul Vixie a David Conrad jako „BINDv9 je úplné přepsání “, „Zabezpečení bylo klíčovým faktorem při návrhu“, přičemž zabezpečení, robustnost, škálovatelnost a nové protokoly byly pojmenovány jako klíčové záležitosti při přepisování starého kódu.
  • Microsoft Windows trpěl zranitelností Windows Metafile a dalšími exploity souvisejícími s formátem WMF. Microsoft Security Response Center popisuje funkce WMF jako „Kolem roku 1990 byla přidána podpora WMF ... Byla to jiná doba v prostředí zabezpečení ... všichni byli zcela důvěryhodní“ , nevyvíjí se v rámci bezpečnostních iniciativ společnosti Microsoft.
  • Oracle bojuje se staršími problémy, jako je starý zdrojový kód napsaný bez řešení obav z injekce SQL a eskalace oprávnění , což má za následek mnoho bezpečnostních chyb, jejichž oprava si vyžádala čas a také generovala neúplné opravy. To vyvolalo silnou kritiku bezpečnostních expertů, jako jsou David Litchfield , Alexander Kornbrust , Cesar Cerrudo . Další kritika je, že výchozí instalace (převážně ze starších verzí) nejsou v souladu s jejich vlastními bezpečnostními doporučeními, jako je například Oracle Database Security Checklist , který je těžké změnit, protože mnoho aplikací vyžaduje pro správnou funkci méně bezpečná starší nastavení.

Kanonikalizace

Škodliví uživatelé pravděpodobně vymyslí nové druhy reprezentací nesprávných dat. Pokud se například program pokusí odmítnout přístup k souboru „/etc/ passwd “, cracker může předat jinou variantu tohoto názvu souboru, například „/etc/./passwd“. Kanonikalizace knihovny mohou být použity, aby se zabránilo chybám v důsledku nedodržení kanonický vstup.

Nízká tolerance vůči „potenciálním“ chybám

Předpokládejme, že konstrukce kódu, které se zdají být náchylné k problémům (podobné známým zranitelnostem atd.), Jsou chyby a potenciální chyby zabezpečení. Základní pravidlo zní: „Nejsem si vědom všech typů bezpečnostních exploitů . Musím se chránit před těmi, které znám, a pak musím být proaktivní!“.

Jiné techniky

  • Jedním z nejčastějších problémů je nekontrolované používání struktur a funkcí konstantní velikosti pro data dynamické velikosti ( problém s přetečením vyrovnávací paměti ). To je běžná zejména u řetězcových dat v C . Funkce knihovny C, jako getsby se nikdy neměly používat, protože maximální velikost vstupní vyrovnávací paměti není předána jako argument. Funkce knihovny C, jako scanfje, lze bezpečně používat, ale vyžadují, aby se programátor postaral o výběr řetězců bezpečného formátu tím, že jej před použitím dezinfikuje.
  • Šifrovat/autentizovat všechna důležitá data přenášená přes sítě. Nepokoušejte se implementovat vlastní šifrovací schéma, ale použijte osvědčené.
  • Všechna data jsou důležitá, dokud se neprokáže opak.
  • Všechna data jsou poškozena, dokud se neprokáže opak.
  • Veškerý kód je nejistý, dokud se neprokáže opak.
    • Nemůžete prokázat bezpečnost žádného kódu v uživatelské zemi , nebo, kanoničtěji: „nikdy nedůvěřujte klientovi“ .
  • Pokud mají být data zkontrolována na správnost, ověřte, zda jsou správná, ne že jsou nesprávná.
  • Design podle smlouvy
    • Design by contract využívá předběžné podmínky , postkondice a invarianty k zajištění toho, že poskytovaná data (a stav programu jako celku) budou dezinfikována. To umožňuje kódu dokumentovat jeho předpoklady a bezpečně je vytvářet. To může zahrnovat ověření platnosti argumentů funkce nebo metody před spuštěním těla funkce. Po těle funkce je také moudré provést kontrolu stavu nebo jiných zadržených dat a návratovou hodnotu před ukončením (break/return/throw/error code).
  • Tvrzení (také nazývané asertivní programování )
    • V rámci funkcí můžete chtít zkontrolovat, že neodkazujete na něco, co není platné (tj. Null) a že délky polí jsou platné před odkazováním na prvky, zejména u všech dočasných/místních instancí. Dobrá heuristika je nedůvěřovat ani knihovnám, které jste nenapsali. Kdykoli jim tedy zavoláte, zkontrolujte si, co od nich dostanete zpět. Často pomůže vytvořit malou knihovnu funkcí „prosazování“ a „kontroly“ společně s loggerem, abyste mohli trasovat svoji cestu a omezit především potřebu rozsáhlých ladicích cyklů. S příchodem knihoven protokolování a programování orientovaného na aspekty se mnohé únavné aspekty obranného programování zmírňují.
  • Chcete -li vrátit kódy, upřednostněte výjimky
    • Obecně lze říci, že je vhodnější házet srozumitelné zprávy o výjimkách, které vynucují část vaší smlouvy API a vést klientského programátora namísto vracení hodnot, na které je klientský programátor pravděpodobně nepřipravený, a proto minimalizuje jejich stížnosti a zvyšuje robustnost a zabezpečení vašeho softwaru .

Viz také

Reference

  1. ^ "archiv fogo: Paul Vixie a David Conrad na BINDv9 a Internet Security od Geralda Oskoboiny <gerald@impressive.net>" . působivé.net . Citováno 2018-10-27 .
  2. ^ "Když se podíváme na problém WMF, jak se tam dostal?" . MSRC . Archivovány od originálu na 2006-03-24 . Citováno 2018-10-27 .
  3. ^ Litchfield, David. "Bugtraq: Oracle, kde jsou patche ???" . seclists.org . Citováno 2018-10-27 .
  4. ^ Alexander, Kornbrust. "Bugtraq: RE: Oracle, kde jsou patche ???" . seclists.org . Citováno 2018-10-27 .
  5. ^ Cerrudo, Cesar. "Bugtraq: Re: [Úplné odhalení] RE: Oracle, kde jsou záplaty ???" . seclists.org . Citováno 2018-10-27 .

externí odkazy