Security Professionals - ipfw add deny all from eindgebruikers to any

Schoolopdracht over veilig programmeren

16-06-2014, 01:40 door mrvrf, 1 reacties
Hallo allemaal,

Voor een schoolopdracht heb ik een artikel moeten schrijven en moet dit ook publiceren. Dat doe ik via deze forumpost. Alvast bedankt voor het lezen en feedback is zeer welkom! Ik dek mij zelf meteen al in door te zeggen dat ik allesbehalve een professional ben, en dit artikel omvat wat ik nu weet.

------------------------------------------------------------------------------------------
Programmeerparadigma's en security

Veel discussie is gaande omtrent de algehele beveiliging van vele computersystemen, zowel in 'software ontwikkelaars-land' als in de media. De explosieve groei van gebruik van computersystemen in zowel het dagelijks leven als in het bedrijfsleven heeft er voor gezorgd dat tegenwoordig veel informatie in computersystemen te vinden is.

In de media gaat het er vaak over hoe gebruikers zichzelf kunnen beschermen tegen zogenaamde phishing mails en vreemde linkjes. Hoewel dit veel voorkomt is hier weinig aan te doen, want deze methode speelt voornamelijk in op de soms onwetendheid en naïviteit van de gebruiker. Deze vorm van beveiliging valt totaal buiten de scope van dit artikel.

Tussen software ontwikkelaars woedt vaak de discussie hoe men het veiligst kan programmeren. Veel beveilingslekken zijn namelijk vaak programmeerfouten, of onvoorziene situaties waarin een applicatie of webservice gebruikt of misbruikt kan worden. Programmeerfouten zijn niet te voorkomen; programmeurs blijven mensen. Ook onvoorziene situaties blijven niet tegen te houden. Maar het is wel mogelijk om het risico om dit soort incidenten te verkleinen.

Veelal wordt er gebruik gemaakt van code reviews en geautomatiseerde test tools om er zo zeker mogelijk van te zijn dat de kans op lekken in de software zo klein mogelijk is. Code reviews zijn in het leven geroepen omdat meerdere mensen meer kunnen zien dan één. Daarbij spot een niet vermoeid oog een fout eerder dan de programmeur die al uren bezig is om een bepaalde feature aan de praat te krijgen. Geautomatiseerd testen kan ook helpen om lekken te ontdekken. Nou moet niet gedacht worden dat een applicatie zichzelf automatisch kan testen. Bij geautomatiseerd testen worden er losse tests geschreven door de programmeur zelf om te checken of de applicatie nog steeds hetzelfde doet dat het zou moeten doen. Maar uiteraard is dit ook geen perfecte oplossing; deze tests moeten nog steeds gemaakt worden door mensen. En naar fouten in tests kunnen onvoorziene situaties niet getest worden als er geen test voor is verzonnen.

Er kan veel fout gaan in een programma, zeker als een programmeur nog onervaren is kan er soms zeer complexe code geschreven worden waarbij moeilijk te zien is of er iets fout gaat of niet. Nog erger is als een programma alles lijkt te doen wat het moet doen, maar stiekem, onopgemerkt, negatieve bijwerkingen heeft. Eigenlijk gaan alle discussies over hoe je bepaalde problemen het best kan oplossen in een programmeertaal. Maar eigenlijk gaan al deze oplossingen over problemen in een imperatieve programmeertaal.

Programmeerparadigma's

Een programmeertaal is altijd gebaseerd op één of meerdere programmeerparadigma's. Een programmeerparadigma is een verzameling ideeën en theorieën over hoe je in een programmeertaal een probleem zou kunnen, of moeten, oplossen. Het wordt vaak ook wel beschreven als de stijl waarin je de programmeertaal moet gebruiken. Er zijn zes verschillende programmmeerparadigma's: imperatief, declaratief, functioneel, object-georiënteerd, logisch en symbolisch4. Van vele van deze programmeerparadigma's zijn er populaire programmeertalen ontstaan: C en BASIC (imperatief), Java en C# (object-georiënteerd), Haskell en Lisp (functioneel) en nog vele andere. Python bijvoorbeeld heeft meerdere paradigma's: imperatief, object-georiënteerd en functioneel. De drie zonet genoemde paradigma's zijn de populairste. Echter, functionele programmeertalen blijven nog erg achter qua populariteit: de meeste (web)applicaties en systemen worden in imperatieve of object-georiënteerde programmeertalen geschreven.

Daar is opzich niets mis mee, zei het niet dat imperatieve en object-georiënteerde programmeertalen een stuk gevoeliger blijken voor bugs dan functionele programmeertalen. Dit komt omdat functionele programmeertalen een stuk stricter zijn en meer denkwerk vereisen om hetzelfde probleem te verhelpen dan in imperatieve programmeertalen. Dit zou misschien ook één van de redenen kunnen zijn dat functionele programmeertalen achterblijven in populariteit: deze talen zijn een stuk moeilijker meester te maken en hebben een steilere leercurve. Daar komt nog eens bij dat de functionele programmeerparadigma zo een totaal ander inzicht geeft in programmeren dat het nog lastiger is om te 'switchen' tussen paradigma's als je al ver gevorderd ben in imperatief of object-georiënteerd programmeren. De programmeurs' denkwijze moet immers drastisch veranderen, iets dat ontzettend lastig kan zijn.

Zoals gezegd zijn er meerdere programmeerparadigma's dan alleen imperatief, object-georiënteerd en functioneel, maar dit zijn wel de drie meest populaire paradigma's. Gezien object-georiënteerde talen voortborduren op imperatieve talen, zal nu alleen het verschil worden uitgelegd tussen imperatief en functioneel programmeren.

Imperatief vs. Functioneel programmeren

Dit artikel gaat uit van geen voorkennis van de lezer, dus zal er op een fundamenteel niveau beide paradigma's uitgelegd en vergeleken worden.

Bij het ontwikkelen van programma's kom je vaak (wiskundige) problemen tegen die opgelost moeten worden met behulp van programmeercode. Afhankelijk in welke stijl en in welke taal je programmeerd zijn er verschillende oplossing mogelijk. Imperatieve programmeertalen kenmerken zich door de huidige 'staat' van het programma constant aan te passen3. Door het aanpassen van de 'staat' maakt het programma beslissingen en doet het wat het (hopelijk) zou moeten doen. Door de staat van het programma aan te passen worden er commando's gebruikt. Een voorbeeld hiervan is het commando print in Python. Door te typen “print 'Hello World!' ” zal dit programma de commando print uitvoeren. Het commando print houdt in dat de gegeven zin wordt getoond op het scherm. Elke programmeertaal heeft een aantal ingebouwde commando's maar de programmeur kan altijd nog eigen commando's bedenken en maken. Vaak zijn deze commando's dan weer opgebouwd uit de bestaande ingebouwde commando's.

Om de code overzichtelijk te houden is het mogelijk om een aantal commando's op een aparte plek te zetten zodat het duidelijk is dat deze commando's bij elkaar horen. Zoiets wordt vaak een subroutine of een functie genoemd. Dit heeft echter weinig met een functie te maken zoals die in functionele programmeertalen voorkomen.

Bij een pure functionele programmeertaal, zoals bij Haskell, is de uitkomst van het programma de uitkomst van een functie. Het is dus niet zo dat een andere programmeerparadigma 'niet-functioneel' is in de zin dat het niet werkt. Het is meer dat functionele programmeertalen puur uit functies bestaan, en niks anders. Een functionele programmeertaal veranderd dan ook niet van staat: het is constant bezig met functies uit voeren en meer niet. Er wordt ook wel gezegd dat imperatieve talen zeggen hoe bepaalde taken moeten worden uitgevoerd en functionele programmeertalen zeggen wat er moet worden uitgevoerd.

Nog een ander verschil is dat in functionele programmeertalen de 'functie' eigenlijk een andere soort functie is dan in imperatieve talen. In functionele programmeertalen is de functie het best te vergelijken met een wiskunde functie: in de wiskunde heeft een functie een bepaalde input (bijvoorbeeld x) en een bepaalde output (bijvoorbeeld y). In de wiskunde heeft de functie f(x) altijd dezelfde uitkomst bij eenzelfde x. Het feit dat in de wiskunde en in functionele programmeertalen een functie altijd hetzelfde teruggeeft wordt ook wel aangeduid als dat deze functies geen 'side-effects', of bijwerkingen, hebben. Naast de overeenkomst met functies uit de wiskunde, zijn ook variabelen anders dan in imperatieve talen. In functionele programmeertalen zijn ook variabelen gelijk aan hun wiskundige tegenhanger: ze zijn onveranderlijk, hebben een bepaalde type en kunnen alleen binnen functies bestaan.

Hoewel ze dus dezelfde naam dragen, zijn variabelen in een functionele programmeertaal tamelijk anders dan variabelen in imperatieve programmeertalen. In imperatieve programmeertalen zijn variabelen eerder voor te stellen als plekjes in het geheugen waar data in gestopt kan worden. Deze ruimte in het geheugen kan je dus ook weer overschrijven als de programmeur dat wilt: deze variabelen zijn dus niet onveranderlijk. Daarbij bestaan er in imperatieve programmeertalen ook globale variabelen: variabelen die blijven bestaan buiten een functie zodat deze bijvoorbeeld gedurende de gehele tijd dat het programma draait te benaderen zijn. Zoals gezegd bestaan variabelen in functionele programmeertalen alleen binnen in functies; nog een verschil tussen beide paradigma's.

De afwezigheid van globale variabelen en dus ook de afwezigheid van de staat van het programma zorgt ervoor dat functionele programmeertalen een ander voordeel hebben: de code, en daarmee de functies in de code, is puur. Zoals eerder al is gezegd geven functies altijd dezelfde output terug bij dezelfde input. Een kleine nuance is wel nodig; dit geldt namelijk alleen voor pure functies.

Echter, zonder niet-pure functies kunnen sommige problemen niet worden opgelost in functionele programmeertalen, en dat zijn problemen die met I/O hebben te maken. I/O staat voor Input/Output en gaat over communicatie tussen het programma en bestanden op een computer. Deze bestanden kunnen zowel lokaal als op het internet te vinden zijn. Deze niet-pure functies kunnen wel side-effects hebben, en dat komt puur omdat de programmeur niet tijdens het ontwikkelen kan weten wat er in een bestand staat van te voren. Gezien I/O toch vaak gedaan moet worden in applicaties moeten dit soort acties gedaan worden in niet-pure functies.

Is dit slecht? Nee; zeker niet. De voor- en nadelen worden in het volgende hoofdstuk besproken.

Als laatste zal er een voorbeeld getoond worden waarin hetzelfde probleem wordt opgelost in zowel een imperatieve programmeertaal als een functionele programmeertaal. Er zal respectievelijk Python en Haskell gebruikt worden.

Het probleem dat opgelost moet worden is het tonen van de Fibonacci rij tot een gegeven index. Dus stel de gebruiker geeft fib 10 op, dan worden de eerste 10 Fibonacci getallen getoond op het scherm.

Implementatie in Python

def fib(index):
if index <= 0:
return 0
elif index == 1:
print 0
return 0
counter = 0
i = 0
print str(i) + ',',
i2 = i
i = 1
while counter != index – 1:
print str(i) + ',',
i3 = i
i = i + i2
i2 = i3
counter += 1

Implementatie in Haskell

fib i :: Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

Naast het feit dat Haskell ogenschijnlijk veel minder regels code nodig heeft wordt het nu het verschil in programmeerstijl ook duidelijker. In Python (imperatief) worden er commando's gegeven ('geef een waarde aan i', 'tel iets bij elkaar op', etc.). De programmeur verteld de computer hoe de rij van Fibonnaci berekend moet worden. Dit in tegenstelling tot het voorbeeld in Haskell: de functie fib heeft drie regels:
1. als de input 0 is, is de output 0;
2. als de input 1 is, is de output 1;
3. voor alle andere gevallen: de output is fib(n-1) + fib(n-2).

Met deze drie simpele regels is hetzelfde probleem opgelost.

Veilig(er) programmeren

Nu het duidelijker is wat het verschil is tussen imperatieve en functionele programmeertalen, kan er begonnen worden over de beveiligingsverschillen tussen beide soorten. Het moge duidelijk zijn: of men nou imperatief of functioneel programmeert, men maakt nog steeds fouten en de keuze in paradigma gaat daar niets aan veranderen. Wat men wel in handen heeft is het verkleinen van het risico op fouten. Het is logisch dat hoe meer regels code er wordt geschreven hoe groter de kans is op bugs. Er zijn immers meer plekken waar ze op kunnen treden. Uit de code voorbeelden van het vorige hoofdstuk is al op te maken dat er een veel kleinere kans is op bugs in het voorbeeld in Haskell dan in Python. Respectievelijk 4 regels code tegenover 17 regels code. Geen garantie dat de ene taal veiliger is dan de ander, maar Haskell laat hier wel zien dat de kans op bug aanzienlijk kleiner lijkt te zijn.

Zoals eerder is besproken heeft Haskell pure en niet-pure functies. Ook dit is een voordeel in het veilig programmeren. Haskell dwingt namelijk af om I/O en niet-I/O gerelateerde zaken op te splitsen in verschillende functies. Dit heeft als voordeel dat bugs makkelijker gelokaliseerd kunnen worden. De pure functies kunnen namelijk in automatische tests worden gestopt; als deze tests positief draaien weet je al dat de fout in niet-pure functies zit en hiermee kan je al veel situaties mee wegstrepen. Naast het oplossen van een bug is het namelijk vaak moeilijker om de bug te vinden, dus met dit soort opsplitsingen kan dit vergemakkelijkt worden.

Deze twee voordelen van functionele programmeertalen kunnen bijdragen aan de veiligheid van je systeem. De kans lijkt groter dat de applicatie doet wat het moet doen. Er moet echter niet vergeten worden dat functionele programmeertalen vaak moeilijker te begrijpen zijn en de programmeur dus vaardiger moet zijn om deze talen te kunnen gebruiken. Wellicht is de kans op bugs groter bij functionele programmeertalen als deze wordt gebruikt door een minder vaardige programmeur.

We kunnen hieruit concluderen dat het, zoals altijd, van meerder factoren afhankelijk is hoe veilig een applicatie in elkaar is gezet. Functionele programmeertalen zijn minder populair, maar blijken wel minder gevoelig te zijn voor bugs als deze wordt gebruikt door vaardige programmeurs. Het is niet voor niets dat imperatieve programmeertalen populairder zijn; ze zijn makkelijke te begrijpen en te gebruiken. Maar wellicht is een goed idee als iedere programmeur zich eens goed verdiept in een functionele programmeertaal; het zal veel inzichten geven omtrent veilig programmeren. En daarbij hebben veel imperatieve talen, zoals Python, steeds meer functionele invloeden gekregen zodat er op sommige gebieden ook functioneel geprogrammeerd kan worden.

Bronvermelding
1. http://www.techrepublic.com/blog/it-security/functional-programming-techniques-can-improve-software-security/
2. https://en.wikipedia.org/wiki/Functional_programming
3. https://en.wikipedia.org/wiki/Imperative_programming
4. https://en.wikipedia.org/wiki/Programming_paradigm
5. http://stackoverflow.com/questions/498234/vulnerability-in-the-functional-programming-paradigm
6. http://book.realworldhaskell.org/read/functional-programming.html
Reacties (1)
16-06-2014, 08:59 door Anoniem
Mijn eerste indruk is dat je al een eind op weg bent. Wellicht dat je nog wat nuttige onderwerpen toe kunt voegen om de meest voorkomende kwetsbaarheden te benoemen. De OWASP top 10 geeft je een indruk van hetgeen dagelijkse ellende veroorzaakt. Zo heeft ook het NCSC een handleiding gemaakt om webapplicaties beter te maken.
Doe er je voordeel mee!
Reageren

Deze posting is gelocked. Reageren is niet meer mogelijk.