Defensiv programmering

Defensiv programmering är en form av defensiv design avsedd att utveckla program som kan upptäcka potentiella säkerhetsavvikelser och ge förutbestämda svar. Det säkerställer den fortsatta funktionen hos en mjukvara under oförutsedda omständigheter. Defensiva programmeringsmetoder används ofta där hög tillgänglighet , säkerhet eller säkerhet krävs.

Defensiv programmering är ett tillvägagångssätt för att förbättra programvara och källkod , i termer av:

  • Allmän kvalitet – minska antalet programvarubuggar och problem.
  • Göra källkoden begriplig – källkoden ska vara läsbar och begriplig så att den godkänns i en kodgranskning .
  • Att få programvaran att bete sig på ett förutsägbart sätt trots oväntade input eller användaråtgärder.

Alltför defensiv programmering kan dock skydda mot fel som aldrig kommer att uppstå, vilket medför kostnader för drifttid och underhåll. Det finns också en risk att kodfällor förhindrar för många undantag , vilket kan resultera i omärkta, felaktiga resultat.

Säker programmering

Säker programmering är den delmängd av defensiv programmering som handlar om datorsäkerhet . Säkerhet är problemet, inte nödvändigtvis säkerhet eller tillgänglighet (programvaran kan tillåtas att misslyckas på vissa sätt). Som med alla typer av defensiv programmering är att undvika buggar ett primärt mål; motivationen är dock inte så mycket att minska sannolikheten för fel i normal drift (som om säkerheten var problemet), utan att minska attackytan – programmeraren måste anta att programvaran kan missbrukas aktivt för att avslöja buggar, och att buggar kan utnyttjas på ett skadligt sätt.

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

Funktionen kommer att resultera i odefinierat beteende när inmatningen är över 1000 tecken. Vissa programmerare kanske inte känner att detta är ett problem, förutsatt att ingen användare kommer att ange en så lång inmatning. speciella bugg visar en sårbarhet som möjliggör buffertspill . Här är en lösning på detta exempel:

   
     

  

  
    

  
  
  
  
  
      

  
 int  secure_programming  (  char  *  input  )  {  char  str  [  1000  +  1  ];  // En till för noll-tecknet.  // ...  // Kopiera indata utan att överskrida destinationens längd.  strncpy  (  str  ,  input  ,  sizeof  (  str  ));  // Om strlen(input) >= sizeof(str) kommer strncpy inte att null avslutas.  // Vi motverkar detta genom att alltid sätta det sista tecknet i bufferten till NUL,  // effektivt beskära strängen till den maximala längden vi kan hantera.  // Man kan också bestämma sig för att explicit avbryta programmet om strlen(input) är  // för lång.  str  [  sizeof  (  str  )  -  1  ]  =  '\0'  ;  // ...  } 

Stötande programmering

Offensiv programmering är en kategori av defensiv programmering, med den extra betoningen att vissa fel inte ska hanteras defensivt . I denna praxis ska endast fel som ligger utanför programmets kontroll hanteras (som användarinmatning); själva programvaran, såväl som data från programmets försvarslinje, är att lita på i denna metodik .

Lita på intern datavaliditet

Alltför defensiv programmering
     
      
              
           
            
    
      
    
    
    
 const  char  *  trafficlight_colorname  (  enum  traffic_light_color  c  )  {  switch  (  c  )  {  case  TRAFFICLIGHT_RED  :  returnera  "red"  ;  case  TRAFFICLIGHT_YELLOW  :  returnera  "gul"  ;  fall  TRAFFICLIGHT_GREEN  :  returnera  "grönt"  ;  }  returnera  "svart"  ;  // Ska hanteras som ett dött trafikljus.  // Varning: Denna sista 'retur'-sats kommer att tas bort av en optimerande  //-kompilator om alla möjliga värden för 'traffic_light_color' är listade i  // föregående 'switch'-sats...  } 
Stötande programmering
     
      
              
           
            
    
    0 
    
    
    
 const  char  *  trafficlight_colorname  (  enum  traffic_light_color  c  )  {  switch  (  c  )  {  case  TRAFFICLIGHT_RED  :  returnera  "red"  ;  case  TRAFFICLIGHT_YELLOW  :  returnera  "gul"  ;  fall  TRAFFICLIGHT_GREEN  :  returnera  "grönt"  ;  }  hävda  (  );  // Säkerställ att det här avsnittet inte går att nå.  // Varning: Detta 'assert'-funktionsanrop kommer att avbrytas av en optimerande  //-kompilator om alla möjliga värden för 'traffic_light_color' är listade i //  föregående 'switch'-sats...  } 

Lita på mjukvarukomponenter

Alltför defensiv programmering
  
    
    
  
    
        
        
    
 if  (  is_legacy_compatible  (  user_config  ))  {  // Strategi: Lita inte på att den nya koden beter sig på samma  old_code  (  user_config  );  }  else  {  // Fallback: Lita inte på att den nya koden hanterar samma fall  if  (  new_code  (  user_config  )  !=  OK  )  {  old_code  (  user_config  );  }  } 
Stötande programmering

    
    
    
    
 // Räkna med att den nya koden inte har några nya buggar  if  (  new_code  (  user_config  )  !=  OK  )  {  // Rapportera högt och abrupt avsluta programmet för att få ordentlig uppmärksamhet  report_error  (  "Något gick väldigt fel" )  ;  utgång  (  -1  );  } 

Tekniker

Här är några defensiva programmeringstekniker:

Intelligent återanvändning av källkod

Om befintlig kod är testad och känd för att fungera, kan återanvändning av den minska risken för att buggar introduceras.

alltid bra att återanvända kod . Återanvändning av befintlig kod, särskilt när den är allmänt spridd, kan göra det möjligt att skapa exploateringar som riktar sig till en bredare publik än vad som annars skulle vara möjligt och för med sig all säkerhet och sårbarhet hos den återanvända koden.

När man överväger att använda befintlig källkod, kommer en snabb genomgång av modulerna (undersektioner som klasser eller funktioner) att hjälpa till att eliminera eller göra utvecklaren medveten om eventuella sårbarheter och säkerställa att den är lämplig att använda i projektet. [ citat behövs ]

Legacy problem

Innan du återanvänder gammal källkod, bibliotek, API:er, konfigurationer och så vidare, måste det övervägas om det gamla verket är giltigt för återanvändning, eller om det sannolikt är benäget att få äldre problem.

Äldre problem är problem som är inneboende när gamla konstruktioner förväntas fungera med dagens krav, särskilt när de gamla konstruktionerna inte utvecklades eller testades med dessa krav i åtanke.

Många mjukvaruprodukter har haft problem med gammal äldre källkod; till exempel:

  • Äldre kod kanske inte har designats under ett defensivt programmeringsinitiativ och kan därför vara av mycket lägre kvalitet än nydesignad källkod.
  • Äldre kod kan ha skrivits och testats under förhållanden som inte längre gäller. De gamla kvalitetssäkringstesterna har kanske ingen giltighet längre.
    • Exempel 1 : äldre kod kan ha designats för ASCII-ingång men nu är ingången UTF-8.
    • Exempel 2 : äldre kod kan ha kompilerats och testats på 32-bitarsarkitekturer, men när den kompileras på 64-bitarsarkitekturer kan nya aritmetiska problem uppstå (t.ex. ogiltiga teckentester, ogiltiga typkast, etc.).
    • Exempel 3 : äldre kod kan ha varit inriktad på offline-datorer, men blir sårbar när nätverksanslutning läggs till.
  • Äldre kod är inte skriven med nya problem i åtanke. Till exempel kommer källkod skriven 1990 sannolikt att vara utsatt för många kodinjektionssårbarheter , eftersom de flesta sådana problem inte var allmänt förstått vid den tiden.

Anmärkningsvärda exempel på äldre problem:

  • BIND 9 , presenterad av Paul Vixie och David Conrad som "BINDv9 är en fullständig omskrivning ", "Säkerhet var en nyckelfaktor i designen", och nämner säkerhet, robusthet, skalbarhet och nya protokoll som viktiga frågor för att skriva om gammal äldre kod.
  • Microsoft Windows drabbades av "den" Windows Metafile-sårbarheten och andra missbruk relaterade till WMF-formatet. Microsoft Security Response Center beskriver WMF-funktionerna som "Omkring 1990 lades WMF-stöd till... Det här var en annan tid i säkerhetslandskapet... alla var helt betrodda", som inte utvecklades under säkerhetsinitiativen hos Microsoft.
  • Oracle bekämpar äldre problem, såsom gammal källkod skriven utan att ta itu med problem med SQL-injektion och privilegieskalering , vilket resulterar i många säkerhetsbrister som har tagit tid att fixa och även genererat ofullständiga korrigeringar. Detta har gett upphov till hård kritik från säkerhetsexperter som David Litchfield , Alexander Kornbrust, Cesar Cerrudo. En ytterligare kritik är att standardinstallationer (till stor del ett arv från gamla versioner) inte är anpassade till deras egna säkerhetsrekommendationer, såsom Oracle Database Security Checklist , som är svår att ändra eftersom många applikationer kräver de mindre säkra äldre inställningarna för att fungera korrekt.

Kanonisering

Skadliga användare kommer sannolikt att uppfinna nya typer av representationer av felaktig data. Till exempel, om ett program försöker neka åtkomst till filen "/etc/ passwd ", kan en cracker skicka en annan variant av detta filnamn, som "/etc/./passwd". Kanoniseringsbibliotek kan användas för att undvika buggar på grund av icke- kanonisk inmatning.

Låg tolerans mot "potentiella" buggar

Antag att kodkonstruktioner som verkar vara problembenägna (liknande kända sårbarheter, etc.) är buggar och potentiella säkerhetsbrister. Den grundläggande tumregeln är: "Jag är inte medveten om alla typer av säkerhetsmissbruk . Jag måste skydda mot dem jag känner till och då måste jag vara proaktiv!".

Andra tips för att säkra din kod

  • Ett av de vanligaste problemen är okontrollerad användning av strukturer med konstant storlek eller förallokerade strukturer för dynamisk storleksdata såsom indata till programmet ( problemet med buffertspill) . Detta är särskilt vanligt för strängdata i C . C-biblioteksfunktioner som gets bör aldrig användas eftersom den maximala storleken på indatabufferten inte skickas som ett argument. C-biblioteksfunktioner som scanf kan användas säkert, men kräver att programmeraren tar hand om valet av säkra formatsträngar genom att sanera det innan det används.
  • Kryptera/autenticera all viktig data som överförs över nätverk. Försök inte implementera ditt eget krypteringsschema, använd en beprövad istället. Meddelandekontroll med CRC eller liknande teknik hjälper också till att säkra data som skickas över ett nätverk.

De tre reglerna för datasäkerhet

 * Alla  uppgifter  är viktiga tills motsatsen bevisats. * All data är förfalskad tills motsatsen bevisats.  * All kod är osäker tills motsatsen bevisats.  
    • Du kan inte bevisa säkerheten för någon kod i användarland , eller, mer känt som: "lita aldrig på klienten" .

Dessa tre regler om datasäkerhet beskriver hur man hanterar data, internt eller externt:

All data är viktig tills motsatsen bevisats - innebär att all data måste verifieras som skräp innan den förstörs.

All data är nedsmutsad tills motsatsen bevisats - innebär att all data måste hanteras på ett sätt som inte exponerar resten av runtime-miljön utan att verifiera integriteten.

All kod är osäker tills motsatsen bevisats - även om det är en liten felaktig benämning, gör den ett bra jobb och påminner oss om att aldrig anta att vår kod är säker eftersom buggar eller odefinierat beteende kan utsätta projektet eller systemet för attacker som vanliga SQL-injektionsattacker .

Mer information

  • Om data ska kontrolleras för korrekthet, kontrollera att de är korrekta, inte att de är felaktiga.
  • Design enligt kontrakt
  • Påståenden (även kallad assertiv programmering )
  • Föredrar undantag för att returnera koder
    • Generellt sett är det att föredra att skicka undantagsmeddelanden som upprätthåller en del av ditt API- kontrakt och vägleder utvecklaren istället för att returnera felkodsvärden som inte pekar på var undantaget inträffade eller hur programstacken såg ut, bättre loggning och undantagshantering kommer att öka robustheten och säkerheten för din programvara, samtidigt som utvecklarens stress minimeras.

Se även

  1. ^   Boulanger, Jean-Louis (2016-01-01), Boulanger, Jean-Louis (red.), "6 - Technique to Manage Software Safety" , Certifierbara programvaruapplikationer 1 , Elsevier, s. 125–156, ISBN 978- 1-78548-117-8 , hämtad 2022-09-02
  2. ^ "fogo-arkiv: Paul Vixie och David Conrad om BINDv9 och Internetsäkerhet av Gerald Oskoboiny <[email protected]>" . imponerande.net . Hämtad 2018-10-27 .
  3. ^ "När man tittar på WMF-frågan, hur kom den dit?" . MSRC . Arkiverad från originalet 2006-03-24 . Hämtad 2018-10-27 .
  4. ^ Litchfield, David. "Bugtraq: Oracle, var är lapparna???" . seclists.org . Hämtad 2018-10-27 .
  5. ^ Alexander, Kornbrust. "Bugtraq: RE: Oracle, var är lapparna???" . seclists.org . Hämtad 2018-10-27 .
  6. ^ Cerrudo, Cesar. "Bugtraq: Re: [Fullständig avslöjande] RE: Oracle, var är lapparna???" . seclists.org . Hämtad 2018-10-27 .

externa länkar