Prima streven. Testcases zijn heel belangrijk maar meestal gericht op functionaliteit.
Afgezien van het vinden en oplossen van fouten is het ook van belang om tooling te gebruiken die je helpt om fouten te voorkomen. En dan vooral de helaas erg hardnekkige top van de OWASP lijst.
Dat kan door linters en static analyzers, maar ook door talen en libraries zo te ontwikkelen of aan te passen, dat het lastiger is om fouten te maken.
PHP heeft eeuwen geleden bijvoorbeeld magic_quotes_gpc aangepast. Daardoor is het duidelijk waar de verantwoordelijkheid ligt voor SQL escaping. Afgezien van dat het een oplossing was op de verkeerde plek met bijwerkingen die niemand wilde en waar je omheen moest werken.
Bovendien bieden alle niet archaïsche SQL drivers voor zo goed als elke taal goede mogelijkheden om SQL escaping automatisch te laten doen. Prepared statements is de velige methode en daarom voor bijna alle situaties de juiste. Mogelijk nog sneller ook als het als prepared statement naar de db-server gaat. Het voorkomt fouten.
Wat mij betreft zou het nog wel strenger mogen. Ik denk dat je in Rust bijvoorbeeld prima een compile-time statische SQL prepared query/statement API kan definiëren. Je kan dan voor de uitzonderingen methods definiëren die je met unsafe markeert. Dat forceert elke SQL aanroep met dynamisch aan elkaar geplakte SQL code in een unsafe block. Unsafe code kan je verbieden in een module en anders is het hopelijk een hint dat mensen na moeten denken wat ze doen en of ze niet beter de velige API kunnen gebruiken.
Goede documentatie wijst ook op het juiste gebruik van API's en geeft geen voorbeeldcode waarin gapende securityexploits voorkomen. Er zijn teveel ontwikkelaars die voorbeeldcode voor het gemak kopiëren en alleen masseren tot het werkt, zonder verder na te denken.
Memory safe talen zijn ook een manier om systemen veiliger te maken. Hetzij door gevaarlijk geheugengebruik te markeren, zoals in Rust met als bedoeling zo min mogelijk niet automatisch te controleren code, hetzij door de hele taal memory safe te houden, zoals bij veel andere talen voor toepassingen die minder tijdkritiek zijn. Die laatste talen zijn vaak nog eenvoudiger in gebruik ook. Zelfs voor C of C++ heb ik gelezen over een voorstel om, nu voor de meeste constructies veilige alternatieven bestaan, de onveilige constructies via compiler flags te verbergen. Dat kon natuurlijk al via linters en static code analyzers. Dat scheelt echt in aantallen ernstige geheugengerelateerde bugs, zoals het uitvoeren van code op afstand en het onbedoeld lezen van geheugen met onvoorspelbare data. Het op die manier willen voorkomen van bugs maakt je niet een mindere programmeur.
Compile-time type-safe talen valt hier wat mij betreft ook onder, al vinden veel mensen dat vreselijk. Je kan er bijvoorbeeld data mee markeren nadat die gevalideerd is. Andere code kan dan eisen dat alleen het gevalideerde type acceptabel is als parameter. Dat gebruik lijkt een beetje op tainted data in Perl.
Genoeg mogelijkheden tot verbetering en veel daarvan wordt al toegepast.
Bestaande code heeft daar het meeste aan als je het per module of library aan kan passen en testen. Herschrijven van code geeft veel risico op andere bugs, al zijn dat dan als het goed is minder securitybugs. Mogelijk introduceer je zelfs nieuwe logische securitybugs.
Die bestaande code is waar onder andere Microsoft moeite mee zal hebben. Neem je het risico om code aan te passen die al decennia goed genoeg draait, waarvan de ontwikkelaar en documentatie mogelijk niet beschikbaar is en waarvan het onduidelijk is hoeveel bugs er nog in zitten met welke impact?
[Reactie gewijzigd door wooha op 12 september 2024 23:03]