Torben Mogensen header

Skyldes sikkerhedshuller altid dårlig programmering?

An ounce of prevention is worth a pound of cure

I sin blog fra 21. februar skriver Klaus Agnoletti om sikkerhedshuller og viser i den forbindelse en stribe fra tegneserien xkcd, hvor en mor har givet sin dreng et navn, der ved indtastning i skolens database sletter alle studenteroplysninger.

En af moralerne i både tegneseriestriben og blogindlægget er, at de fleste sikkerhedshuller skyldes sjusket programmering – man har undladt at fjerne indlejret SQL kode fra indtastet data, man har ikke checket for bufferoverløb, osv.

Det er ganske givet, at en omhyggelig programmør kan undgå den slags sikkerhedshuller i sin kode. Men det burde egentlig ikke være nødvendigt for programmøren at bekymre sig om indlejret SQL kode eller bufferoverløb – det er velkendte problemer, som er enkle at løse i design og implementering af programmeringsprog og biblioteker.

Lad os starte med SQL. SQL er et domænespecifikt sprog, som ofte er implementeret som et indlejret sprog – dvs., at det er lavet som en samling af procedurer, makroer og variabler i et andet sprog. Da SQL har en struktureret syntaks, som ikke let lader sig modellere med metodekald og lignende, angiver man ofte i SQL-implementeringer hele SQL-kommandoer som tegnfølger (strings). Det gør koden forholdsvis nem at skrive og læse (kommandoerne ser ud som i databasebogen), men det har nogle ulemper:

  • Da søgeord ligger i samme tegnfølge som SQL-kommandoen, kan søgeord ændre ved kommandoen ved at indeholde SQL syntaks.

  • Der er ikke nogen compile-tids check af SQL-kommandoens struktur, så syntaksfejl m.m. i SQL-kommandoer findes først på køretid.

  • Kode, der forsøger at rense'' tegnfølger for mulig SQL kode, gør det ofte ved kun at tillade alfanumeriske tegn i tegnfølgerne, hvilket betyder, at mange programmer ikke tillader navne som O'Donnel, Smith-Jones, !Xabbu og Sørensen, der allesammen indeholdermærkelige'' tegn.

Den første ulempe giver anledning til sikkerhedshuller, mens den anden ``bare'' betyder, at nogle fejl, som kunne have været opdaget af en oversætter, kræver grundig køretidstest at opdage. Den tredje ulempe er en uhensigtsmæssig begrænsning i

Problemet med SQL-injektion kunne være løst ved et anderledes biblioteksdesign: I stedet for at kode SQL-kommandoer som tegnfølger, kunne de kodes som datastrukturer, der repræsenterer syntakstræer. I sprog som Java og C, er træstrukturer dødbesværlige at bygge, så jeg forstår egentlig godt, hvorfor biblioteksdesignerne har fravalgt den løsning. Men i andre sprog, som f.eks. ML, Erlang, Haskell, Scheme og Prolog er træstrukturer en naturlig udtryksmåde, og kode, der bruger disse, er både læselig og sikret imod blanding af tegnfølger og syntaktisk struktur. I nogle af de nævnte sprog kan man tilmed bruge typesystemet til at checke strukturen af SQL-kommandoerne på oversættelsestid.

En anden mulig løsning er at udvide sit programmeringssprog med
SQL-lignende syntaks. Det er stort set det, LINQ i C# og relaterede
sprog gør. Det giver stor frihed til at lave en læselig og checkbar
syntaks, men det kræver en sprogændring for hvert nyt indlejret sprog.

Et andet ofte set sikkerhedshul er bufferoverløb, der tillader en ondskabsfuld bruger at overskrive kode eller returaddresser med andre værdier. Selvfølgelig bør en programmør, der laver en buffer med begrænset størrelse, altid checke for overløb. Men det sker tit ikke, og almindelig test vil ofte ikke afsløre det, da problemet måske først opstår, når hardwaren er blevet så meget hurtigere, at man kan nå at fylde bufferen op.

Men hvorfor i det hele taget bruge strukturer med begrænset størrelse' Det er ikke voldsomt svært i et programmeringssprog at sikre, at strukturer altid udvider sig efter behov, så programmøren hverken behøver at teste for overløb eller forudsige, hvad en passende størrelse for en buffer er. Mindre kan også gøre det: Enhver tilgang til en begrænset struktur laves først efter en (compilerindsat) test for, at tilgangen er indenfor de definerede grænser. Hvis dette ikke er tilfældet, kan en exception kastes eller programmet kan stoppes. Men det er vel trods alt bedre end et sikkerhedshul'

Så min tese er, at mange af de mest almindelige sikkerhedshuller kunne undgås med bedre biblioteksdesign og brug af sprog med bedre indbyggede garantier. Og det behøver ikke at koste alverden i køretidsoverhead – erfaringer har vist, at oversættere kan fjerne de fleste køretidscheck selv med ret simple analyser, og i andre tilfælde kan typekorrekte programmer helt undvære checkene.

Kommentarer (9)
sortSortér kommentarer
  • Ældste først
  • Nyeste først
  • Bedste først
#1 Klaus Agnoletti

Sjovt du bringer det op - godt at se at mine indlæg kan inspirere andre bloggere :)

Du har fuldstændig ret - en af de ting jeg tager op i næste del af indlægsrækken om sikker programmering er netop at det rigtige sprog er en af de faktorer der gør det opnåeligt med sikker kode (igen - uden at starte en religionskrig om programmeringssprog - det er en dead end :)

  • 0
  • 0
#2 Kasper Birch Olsen

LINQ er et fantastisk eksempel på hvordan sikkerhed og "let" programmering kan mødes. Ofte har SQL som simple strænge været den lette løsning også selv om mere avancerede løsninger fantes. LINQ er så let at gå til at der ikke er mange argumenter imod at bruge det. Forudsat at man koder C# naturligvis. Dejligt at sikkerhed og "brugervenlighed" kan gå hånd i hånd.

  • 0
  • 0
#3 Deleted User

"LINQ er så let at gå til at der ikke er mange argumenter imod at bruge det. Forudsat at man koder C# naturligvis."

Ja, det sidste er jo så en ulempe...

Vi kan undre os over, at udviklingsværktøjer, sprog, biblioteker osv. nærmest er "udviklet" til, at programmørerne falder i. Og dette bør måske give anledning til konspirationer.

I dag, er jeg kommet dertil, at jeg ikke tror på, at det findes noget "godt nok" som programmørerne må få viden om, eller adgang til. Når der udvikles noget, er det måske bedre end det eksisterende (eller ringere?). Men, ofte søges at opnå en udvikling der går i ring, så vi alligevel efter nogle gennerationer er retur og udviklingen holdes i skak. Lidt som Piet Heins tone, der altid går op, og alligevel vender vi tilbage ved udgangspunktet. Det er omtrent samme lyd, der høres fra mange tåbelige programmører. På et tidspunkt, vil programmørerne opdage, at C ikke er godt. Så udvikles C++. Der er nyt at sætte sig ind i, og det går lidt tid, inden de kan pege "eksakt" på fejlene, og forklare præcis hvor stor brøleren er. Man får dem "beskæftiget" en stykke tid. På et tidspunkt er de blevet fortrolige med C++, og nogle få har opdaget at C++ også har ulemper. Så kommer Java, for at tilfredsstille disse besværlige programmører. Atter dukker nye ulemper op. Ting som "tydeligt" ikke før var et problem, med mindre man går tilbage til computerens ungdom er retur. Min fornemmelse er, at man fortsætter med at trække programmørerne rundt i manegen, og at dette er hensigten. Såvel computerarkitekturer, operativsystemer, som programmeringssprog, er tydeligt udviklet til at kun give problemer. Og det nytter ikke, at bare holde på Linux. Et kritisk øje, vil fra starten have set, at det var udviklet med henblik på at give problemer. Ihvertfald forekom det tydeligt for mig, før jeg vidste noget om operativsystemer, og da jeg så lærte om de første, var jeg rystet. De var klart udviklet til, at være jokes, og enhver kunne se, at det skulle give problemer.

Meningen er simpelthen, at programmørerne skal arbejde hårdt, have så store problemer som muligt, at fejlfinding og besvær, skal absorbere alt deres tid, således de ikke tænker, og ikke opdager noget fornuftigt. I nogle tilfælde er nødvendigt at sætte dem til ligegyldigt arbejde, for at holde dem beskæftiget og bort fra den relevante tænkning. Derved kan en lille "elite" holde sig i front. De er ikke dygtige nok, til at klare sig på anden måde. Begyndte programmørerne at forstå, hvordan de holdes ved næsen, vil vi have et åbentlyst stort problem.

Det var så mit biddrag til konspirationen (hvis det altså er en konspiration.)

Under alle omstændigheder, er Torben Mogensen relevant. Hvis man får programmørerne til at overveje, ikke hvilke sprog eller metoder der er bedre, men hvordan det reelt skulle gøres, og her sammenligner med det eksisterende, så skulle de gerne opdage at noget er galt, og at de reelt kun trækkes rundt i manegen.

Det nye som kommer kunne være uden fejl. Men så vil programmørerne blive for dygtige. Så reelt, er kunsten at udvikle noget nyt, og evt. tage gamle problemer op på ny, for at indbygge dem i de nye tools. Ved at vælge den rette "modulation" af problemerne opstår en tydelig forbedring, trods det går ligeså meget ned. Det var idéen i Piet Heins lydeksempel, der skulle vække folk.

  • 0
  • 0
#4 Mikkel Høgh

En underfundig institution, DIKU. De har deres helt eget sprog :)

Nu arbejder jeg jo med PHP, så det der med buffer underruns og den slags er heldigvis ikke noget jeg behøver at bekymre mig om.

Mht. til SQL, så har jeg prøvet forskellige andre metoder, men jeg har fundet at lavpraktiske løsninger som f.eks. Drupals db_query-wrapper ( http://api.drupal.org/api/function/db_query/6 ) er langt mere effektive i forhold til at skulle lave en eller anden kompliceret datastruktur som computeren så oversætter til SQL for en.

Det er jo den samme gamle sang om "leaky abstractions" - men min holdning er altså at når man har med SQL-databaser at gøre, så er det mest effektive sprog at snakke altså SQL.

Den eneste grund der for mig at se er til at forlade SQL skulle være for at benytte et ORM, altså et stykke kode som tager indholdet af din database og mapper det til objekter, men stort set alle ORM'er har også muligheden af at droppe ned i rå SQL hvis de queries man ønkser er så komplekse at ORM'en ikke kan generere dem for en.

  • 0
  • 0
#5 Anders Reinhardt Hansen

"Den eneste grund der for mig at se er til at forlade SQL skulle være for at benytte et ORM"

Det er så bl.a. det man kan med LINQ, det er bare mere end det. Det er også en integration i sproget. Og i det at LINQ tjekkes på compile time, så er det potentielt hurtigere end indlejret SQL i din PHP kode! Du kan sikkert finde eksempler hvor indlejret SQL er hurtigere, men generelt vil det være hurtigere med LINQ. Man kan dog også kigge på Hibernate og nHibernate (.Net) som er Object relational Mapper.

En anden fordel ved ORM er også at korrektheden af din kode vil hæves da den er type stærk.

  • 0
  • 0
#7 Mikkel Høgh

Både Hibernate og LINQ er så vidt jeg kan forstå hhv. Java- og .NET specifikke. Der findes ganske vist PHPLinq, men der er stadig præ-beta.

En ting jeg ikke helt forstår er hvad forskellen på compiletime og runtime ifht. LINQ

De brugerdata der kommer ind og giver fejlmuligheder kommer vel først i runtime-delen alligevel - og syntaksfejl har man jo bedre måder at checke for, hvis jeg ikke tager meget fejl - når jeg sidder og skriver SQL plejer jeg bare at have en interaktiv SQL-prompt åben, så jeg kan checke at de resultater der kommer tilbage også er de ønskede - der er jo også semantiske fejl at tage sig i agt for.

  • 0
  • 0
#8 Anders Reinhardt Hansen

Hibernate findes og til .Net, men ja der er vist ikke noget PHP framework der kan de samme ting. Det i sig selv gør at jeg ikke bryder mig særlig meget om PHP. Man mangler også lidt backend kodning i php mener jeg men det bliver vist en helt anden diskussion. Men fordelen ved ORM er at frameworket sørger for at det er de rigtige data der bliver placeret i objekter.... Og at du kan autogenerere objekter fra databasen inden du bygger dit projekt. Da der ikke er noget compile time på php kan jeg godt forstå at du ikke forstår forskellen, men pointen er at din compiler giver dig fejl hvis du prøver at tilgå felter på et objekt der ikke indeholder de felter du tror det gør.

  • 0
  • 0
#9 Anonym

Hvis man designede med fejltolerance for øje ville fejlene i mindre grad føre til sammenbrud eller det som er værre.

Man kan måske sige omvendt - at antagelsen at sikkereshedsbrud kun skyldes programmeringsfejl formentlig er det største sikkerhedsproblem, fordi man dermed fokuserer forkert.

Bruce Schneier formuerede det meget godt noget i retning af "The question is not how it works, but how it fails"

Har du styr på fallback modellen, så er risikoen væsentligt formindsket. Det skal typisk ses på mange niveauer.

  • 0
  • 0
Log ind eller Opret konto for at kommentere