ga('send', 'pageview');
Categories
Teknik

Vanliga programmeringsmisstag som rubbar säkerheten

Utvecklar du som om du sitter i ett säkerhetskritiskt system? Då här du säkert insett att det kostar betydligt mindre att ta höjd för säkerhetsfrågor i samband med utvecklingen, än om du gör det senare. Att rätta till ett system i efterhand kräver oftast betydande insatser för att inte tala om vad en lyckad attack kan kosta i form av såväl ekonomisk skada som goodwill och arbetsinsats.

Utvecklar du som om du sitter i ett säkerhetskritiskt system? Då här du säkert insett att det kostar betydligt mindre att ta höjd för säkerhetsfrågor i samband med utvecklingen, än om du gör det senare. Att rätta till ett system i efterhand kräver oftast betydande insatser för att inte tala om vad en lyckad attack kan kosta i form av såväl ekonomisk skada som goodwill och arbetsinsats. 

 

Har du läst blogginlägget om injektionsattacker och sanering av data så har du förhoppningsvis redan anammat ett par viktiga säkerhetsprinciper och också börjat sova lugnare på nätterna. Eller? Dessvärre finns många fler saker att tänka på. Det finns några potentiellt allvarliga programmeringsmisstag som jag ser med alltför hög frekvens. Med Java i åtanke när jag beskrivit fallgroparna, men de finns på ett eller annat sätt även i andra programspråk.

 

Glöm inte kvar en oanvänd main()-metod

Alltför ofta har jag sprungit på kod där det finns en main()-metod i var och varannan klass. Många gånger är det något som användes för att köra klassen under utveckling och med största sannolikhet är det ingen som testar, underhåller eller använder denna metod längre. I praktiken är det alltså en otestad bakdörr in i systemet som de allra flesta inte ens har en aning om att den existerar. Rent generellt är min rekommendation att alltid ta bort main-metoder som inte används.

 

Hårdkoda inte känslig information

Hårdkodad känslig information är något annat som man till och från ser i kod. Det kan vara allt från backendserverns ip-adress till användarnamn och lösenord till databasen. Det som många inte verkar förstå, eller om det bara “glömts bort”, är att det räcker att köra “javap -c” på en klass för att dessa hårdkodade värden ska vara fullt läsbara i klartext. Alltså, hårdkoda aldrig, aldrig, aldrig, känslig information i klasser. Läs hellre upp dessa värden från en propertyfil.

 

Använd inte strängar för att läsa/skriva känslig information

Lagra inte information som lösenord i strängar (String). Detta eftersom strängar i Java sparas i en “String pool” och därmed kan exponeras även efter det att strängen inte längre används. Strängen blir därmed under en längre tid läsbar för alla som har en möjlighet att exempelvis ta ut en minnesdump från systemet. En bättre lösning är att istället använda en char-array då dessa i de allra flesta fall lever betydligt kortare i minnet.

 

Returnera en kopia av förändringsbara objektreferenser

Något som ofta ses är att getters returnerar en referens till förändringsbara objekt. Detta innebär att en ändring av denna variabel riskerar att slå brett på andra objekt som använder samma referens.

public class NoGoodCode {



private HashMap<String, String> hash = new HashMap<>();



private NoGoodCode() {

hash.put("Sensitive", "Sensitive data");

}



public HashMap<String, String> getValues() {

return hash;

}



}

Ovanstående kod kan vara förkastlig ur ett säkerhetsperspektiv, detta eftersom det går att förändra tillståendet på exempelvis följande sätt:

public static void main(String[] args) {

NoGoodCode noGoodCode = new NoGoodCode();

HashMap<String, String> hashMap1 = noGoodCode.getValues();

System.out.println("hashMap1: " + hashMap1.get("Sensitive"));

HashMap<String, String> hashMap2 = noGoodCode.getValues();

System.out.println("hashMap2: " + hashMap2.get("Sensitive"));

hashMap1.put("Sensitive", "new data");

System.out.println("hashMap1: " + hashMap1.get("Sensitive"));

System.out.println("hashMap2: " + hashMap2.get("Sensitive"));



}

Det “hashMap1.put(“Sensitive”, “new data”)“ gör är att inte bara ändra tillståndet på sig själv utan indirekt även på värdet i hashMap2 eftersom denna pekar på samma bakomliggande objekt. Konsolutskriften av ovanstående program blir därmed:

hashMap1: Sensitive data

hashMap2: Sensitive data

hashMap1: new data

hashMap2: new data

Ovanstående kan många gånger vara oavsiktligt men det kan även utnyttjas av illvilliga personer för att medvetet sätta ett objekt i ett felaktigt tillstånd. Ur ett säkerhetsperspektiv är det därför mycket bättre att returnera en kopia av variabeln. I exemplet ovan borde getValues()-funktionen exempelvis se ut på följande sätt:

public HashMap<String, String> getValues() {

return (HashMap<String, String>) hashMap.clone();

}



Därmed kommer programmet också att leverera som förväntat:



hashMap1: Sensitive data

hashMap2: Sensitive data

hashMap1: new data

hashMap2: Sensitive data

 

Använd inte överridbara funktioner i konstruktorn eller i clone()

Att använda clone( ) för oss osökt in på nästa problem, nämligen att anropa metoder som kan överridas i en clone-metod eller från en konstruktor. Problemet med detta är att det därmed går att göra en subklass som sedan överrider den önskade metoden och på så sätt påverka beteendet för konstruktorn eller klon-metoden. Risken finns därmed att ett objekt aldrig blir initierat korrekt alternativt inte har det beteendet som det var tänkt.

public class CloneTest implements Cloneable {

public HttpCookie cookie;



CloneTest(HttpCookie cookie) {

this.cookie = cookie;

cookie.setSecure(true);

}



public Object clone() throws CloneNotSupportedException {

final CloneTest clone = (CloneTest) super.clone();

clone.doStuff();

return clone;

}



public void doStuff() {

cookie.setSecure(true);

}



public static void main(String[] args) throws CloneNotSupportedException {

HttpCookie cookie = new HttpCookie("cookie", "cookieValue");

HttpCookie cookie2 = new HttpCookie("cookie2", "cookieValue");



CloneTest superCloneTest = new CloneTest(cookie);

CloneTest superCloned = (CloneTest) superCloneTest.clone();

System.out.println(superCloned.cookie.getSecure());



CloneTest subCloneTest = new SubClone(cookie2);

CloneTest subCloned = (CloneTest) subCloneTest.clone();

System.out.println(subCloned.cookie.getSecure());

}



}



class SubClone extends CloneTest {



SubClone(HttpCookie cookie) {

super(cookie);

}



@Override

public void doStuff() {

cookie.setSecure(false);

}



}

I ovanstånde exempel så överrider alltså SubClone-klassen doStuff() och ändrar setSecure-parametern på ett sätt som med största sannolikhet inte var avsikten när CloneTest-klassen skapades. Körs main-metoden blir också utskriften true för “superCloned” men false för “subCloned”.

Lösningen är att göra metoden, i ovanstånde exempel doStuff(), final. På så vis kommer det inte gå att överrida metoden.

 

Avslutning

Beroende på vilken typ av applikation det gäller så är fallgroparna mer eller mindre allvarliga. De exempel jag tagit upp är bara toppen på isberget när det gäller att skriva säker kod. Lyckligtvis finns det en uppsjö av bra information i såväl böcker som på webben att fördjupa sig i. Två bra ställen att börja på är www.securecoding.cert.org och www.owasp.org.

 

Min erfarenhet är också att det går att komma relativt långt genom att alltid hålla säkerhetsperspektivet i bakhuvudet och reflektera en extra gång kring frågor som om det går att göra saker på ett säkrare sätt eller om det där känsliga datat verkligen måste exponeras.

 

Missa inte senaste blogginläggen – Ta del av vårt nyhetsbrev!

By Tobias Modig

Leave a Reply

Your email address will not be published. Required fields are marked *