Defensiv programmering - Defensive programming

Defensiv programmering er en form for defensiv design som er ment å sikre at en programvare fortsetter å fungere under uforutsette omstendigheter. Defensiv programmeringspraksis brukes ofte der høy tilgjengelighet , sikkerhet eller sikkerhet er nødvendig.

Defensiv programmering er en tilnærming for å forbedre programvare og kildekode , når det gjelder:

  • Generell kvalitet - redusering av antall programvarefeil og problemer.
  • Gjør kildekoden forståelig - kildekoden skal være lesbar og forståelig, slik at den blir godkjent i en koderevisjon .
  • Få programvaren til å oppføre seg på en forutsigbar måte til tross for uventede innspill eller brukerhandlinger.

En altfor defensiv programmering kan imidlertid beskytte mot feil som aldri vil oppstå, og dermed påløpe driftstid og vedlikeholdskostnader. Det er også en risiko for at kodefeller forhindrer for mange unntak , noe som potensielt kan resultere i upåaktede, feilaktige resultater.

Sikker programmering

Sikker programmering er delmengden av defensiv programmering som er opptatt av datasikkerhet . Sikkerhet er bekymringen, ikke nødvendigvis sikkerhet eller tilgjengelighet ( programvaren kan tillates å mislykkes på visse måter). Som med all slags defensiv programmering er det å unngå feil et hovedmål; motivasjonen er imidlertid ikke så mye for å redusere sannsynligheten for feil ved normal drift (som om det var sikkerhet), men å redusere angrepsflaten - programmereren må anta at programvaren kan bli misbrukt aktivt for å avsløre feil, og at feil kan utnyttes ondsinnet.

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

Funksjonen vil resultere i udefinert oppførsel når inngangen er over 1000 tegn. Noen uerfarne programmerere føler kanskje ikke at dette er et problem, forutsatt at ingen bruker vil skrive inn en så lang innspill. Denne spesielle feilen viser et sikkerhetsproblem som muliggjør buffer overflow utnyttelser . Her er en løsning på dette eksemplet:

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';

  // ...
}

Krenkende programmering

Offensiv programmering er en kategori av defensiv programmering, med den ekstra vekt at visse feil ikke skal håndteres defensivt . I denne praksisen skal bare feil utenfor programmets kontroll håndteres (for eksempel brukerinngang); selve programvaren, så vel som data fra programmets forsvarslinje, er til å stole på i denne metodikken .

Tillit til intern datagyldighet

Altfor defensiv programmering
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...
}
Krenkende programmering
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...
}

Stoler på programvarekomponenter

Altfor defensiv programmering
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);
    }
}
Krenkende programmering
// 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);
}

Teknikker

Her er noen defensive programmeringsteknikker:

Intelligent kildekode gjenbruk

Hvis eksisterende kode er testet og kjent for å fungere, kan gjenbruk av den redusere sjansen for at feil blir introdusert.

Gjenbruk av kode er imidlertid ikke alltid en god praksis, fordi det også forsterker skadene ved et potensielt angrep på den første koden. Gjenbruk i dette tilfellet kan forårsake alvorlige forretningsprosesser .

Eldre problemer

Før du bruker gammel kildekode, biblioteker, APIer, konfigurasjoner og så videre, må du vurdere om det gamle verket er gyldig for gjenbruk, eller om det sannsynligvis vil være utsatt for eldre problemer.

Eldre problemer er problemer iboende når gamle design forventes å fungere med dagens krav, spesielt når de gamle designene ikke ble utviklet eller testet med tanke på disse kravene.

Mange programvareprodukter har opplevd problemer med gammel eldre kildekode; for eksempel:

  • Eldre kode er kanskje ikke designet under et defensivt programmeringsinitiativ, og kan derfor ha mye lavere kvalitet enn nydesignet kildekode.
  • Eldre kode kan ha blitt skrevet og testet under forhold som ikke lenger gjelder. De gamle kvalitetssikringstestene har kanskje ikke lenger noen gyldighet.
    • Eksempel 1 : eldre kode kan ha blitt designet for ASCII-inngang, men nå er inngangen UTF-8.
    • Eksempel 2 : eldre kode kan ha blitt kompilert og testet på 32-biters arkitekturer, men når den er kompilert på 64-biters arkitekturer, kan det oppstå nye regningsproblemer (f.eks. Ugyldige signitetstester, ugyldige typekaster osv.).
    • Eksempel 3 : Eldre kode kan ha blitt målrettet for frakoblede maskiner, men blir sårbar når nettverkstilkobling er lagt til.
  • Eldre kode er ikke skrevet med tanke på nye problemer. For eksempel er kildekode skrevet i 1990 trolig bli utsatt for mange kode injeksjon sårbarhet, fordi de fleste slike problemer ikke var allment forstått på den tiden.

Viktige eksempler på det gamle problemet:

  • BIND 9 , presentert av Paul Vixie og David Conrad som "BINDv9 er en komplett omskriving ", "Sikkerhet var en sentral vurdering i design", og nevnte sikkerhet, robusthet, skalerbarhet og nye protokoller som sentrale bekymringer for omskriving av gammel eldre kode.
  • Microsoft Windows led av "det" Windows Metafile -sikkerhetsproblemet og andre bedrifter relatert til WMF -formatet. Microsoft Security Response Center beskriver WMF-funksjonene som "Rundt 1990 ble WMF-støtte lagt til ... Dette var en annen tid i sikkerhetslandskapet ... alle ble fullstendig klarert" , og ble ikke utviklet under sikkerhetstiltakene hos Microsoft.
  • Oracle bekjemper eldre problemer, for eksempel gammel kildekode skrevet uten å ta opp bekymringer for SQL -injeksjon og eskalering av privilegier , noe som resulterer i mange sikkerhetsproblemer som har tatt tid å fikse og også generert ufullstendige reparasjoner. Dette har gitt kraftig kritikk fra sikkerhetseksperter som David Litchfield , Alexander Kornbrust , Cesar Cerrudo . En ytterligere kritikk er at standardinstallasjoner (stort sett en eldre fra gamle versjoner) ikke er i tråd med sine egne sikkerhetsanbefalinger, for eksempel Oracle Database Security Checkliste , som er vanskelig å endre ettersom mange applikasjoner krever at de mindre sikre eldre innstillingene fungerer korrekt.

Kanonisering

Ondsinnede brukere vil sannsynligvis finne på nye typer representasjoner av feil data. For eksempel, hvis et program prøver å avvise tilgang til filen "/etc/ passwd ", kan en cracker passere en annen variant av dette filnavnet, for eksempel "/etc/./passwd". Kanoniseringsbiblioteker kan brukes for å unngå feil på grunn av ikke- kanonisk innspill.

Lav toleranse mot "potensielle" feil

Anta at kodekonstruksjoner som ser ut til å være utsatt for problemer (ligner på kjente sårbarheter, etc.) er feil og potensielle sikkerhetsfeil. Den grunnleggende tommelfingerregel er: "Jeg er ikke klar over alle typer sikkerhetsproblemer Jeg må beskytte mot de jeg. Vet vet om, og da må jeg være proaktiv!".

Andre teknikker

  • Et av de vanligste problemene er ukontrollert bruk av strukturer og funksjoner i konstant størrelse for data i dynamisk størrelse ( bufferoverløpsproblemet ). Dette er spesielt vanlig for strengdata i C . C biblioteksfunksjoner som getsbør aldri brukes siden maksimal størrelse på inndatabufferen ikke blir bestått som et argument. C biblioteksfunksjoner som scanfkan brukes trygt, men krever at programmereren tar vare på valg av sikre formatstrenger ved å rense den før du bruker den.
  • Krypter/autentiser alle viktige data som overføres over nettverk. Ikke prøv å implementere ditt eget krypteringsopplegg, men bruk et bevist i stedet.
  • Alle data er viktige inntil det motsatte er bevist.
  • Alle data er skadet til det motsatte er bevist.
  • All kode er usikker til det motsatte er bevist.
    • Du kan ikke bevise sikkerheten til noen kode i brukerlandet , eller, mer kanonisk: "aldri stole på klienten" .
  • Hvis data skal kontrolleres for riktighet, må du kontrollere at de er riktige, ikke at de er feil.
  • Design etter kontrakt
    • Design etter kontrakt bruker forutsetninger , postbetingelser og invarianter for å sikre at dataene (og tilstanden til programmet som helhet) blir sanert. Dette gjør at kode kan dokumentere sine forutsetninger og gjøre dem trygge. Dette kan innebære å kontrollere argumenter for en funksjon eller metode for validitet før funksjonen blir utført. Etter at kroppen i en funksjon, gjør en kontroll av tilstand eller andre lagrede data, og returverdien før utganger (pause/retur/kast/feilkode), er også klok.
  • Påstander (også kalt assertiv programmering )
    • Innen funksjoner kan det være lurt å sjekke at du ikke refererer til noe som ikke er gyldig (dvs. null) og at matriselengder er gyldige før det refereres til elementer, spesielt på alle midlertidige/lokale øyeblikk. En god heurist er å ikke stole på bibliotekene du ikke skrev heller. Så hver gang du ringer dem, sjekk hva du får tilbake fra dem. Det hjelper ofte å lage et lite bibliotek med "hevde" og "sjekke" funksjoner for å gjøre dette sammen med en logger, slik at du kan spore stien din og redusere behovet for omfattende feilsøkingssykluser i utgangspunktet. Med ankomsten av loggbiblioteker og aspektorientert programmering , dempes mange av de kjedelige aspektene ved defensiv programmering.
  • Foretrekker unntak fra returkoder
    • Generelt sett er det best å kaste forståelige unntaksmeldinger som fremtvinger en del av API kontrakt og veilede kunden programmerer istedenfor å returnere verdier som en klient programmerer er sannsynlig å være uforberedt på, og dermed minimere sine klager og øke robustheten og sikkerheten i programvaren .

Se også

Referanser

  1. ^ "fogo -arkiv: Paul Vixie og David Conrad på BINDv9 og Internett -sikkerhet av Gerald Oskoboiny <[email protected]>" . imponerende.net . Hentet 2018-10-27 .
  2. ^ "Når du så på WMF -problemet, hvordan kom det dit?" . MSRC . Arkivert fra originalen 2006-03-24 . Hentet 2018-10-27 .
  3. ^ Litchfield, David. "Bugtraq: Oracle, hvor er lappene ???" . seclists.org . Hentet 2018-10-27 .
  4. ^ Alexander, Kornbrust. "Bugtraq: RE: Oracle, hvor er lappene ???" . seclists.org . Hentet 2018-10-27 .
  5. ^ Cerrudo, Cesar. "Bugtraq: Re: [Full-disclosure] RE: Oracle, hvor er lappene ???" . seclists.org . Hentet 2018-10-27 .

Eksterne linker