tisdag 23 februari 2010

Se upp med genererad toString()

För ett par dagar sen råkade jag och en kollega ut för en lite skrämmande sak. Vi satt och arbetade med loggningen i en webbapplikation.

I och med det så lät vi utvecklingsmiljön (IDEA IntelliJ) generera ett antal toString()-metoder åt oss för att öka tydligheten i loggmeddelandena. IntelliJ gör oftast riktigt snygga såna metoder. Men plötsligt upptäcker vi att lösenord skrivs ner i loggen för nyregistrerade användare! Förvisso bara i testmiljön men nog för att man ska få hjärtat i halsgropen.

Boven i dramat var en av våra genererade toString(). Ptja, hur skulle IntelliJ kunna veta att just password-attributet inte skulle med i utdata?

public class UserPutData {
private final String userName;
private final String password;
private final String email;
private final String firstname;
private final String surname;
private final String postcode;

...

@Override
public String toString() {
return "UserPutData{" +
"userName='" + userName + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
", firstName='" + firstname + '\'' +
", surname='" + surname + '\'' +
", postcode='" + postcode + '\'' +
'}';
}
}

Ooups!

5 kommentarer:

Anonym sa...

"The Real WTF" är att lösenord överhuvudtaget fanns i klartext i er applikation! Det finns i princip ingen giltig anledning till sådan hantering. Eller?

John Wilander sa...

Hej Olle!

För det första handlade det här om "nyregistrerade användare", dvs där vi tar emot det lösenord som den nya användaren valt.

För det andra så tar man i princip alltid emot lösenord i klartext i serverkoden, både vid nyregistrering och inloggning. Det viktiga är att lösenordet inte transporteras i klartext (därför SSL) eller lagras i klartext (därför saltad hash).

Alternativet är saltad hash på klienten vilket är en intressant teknik (sk Digest Authentication). Det stöds dock inte av alla komponenter och är tämligen ovanligt i praktiken.

Lösenordsinloggning som sådant är ett annat, större problem. Där har vi folks val av lösenord och återanvändning av lösenord som stora utmaningar. Engångslösenord (OTP) är en av de få tekniker som kan lösa problemet utan att det hela blir för komplext.

Anonym sa...

Jag håller då inte med, man bör aldrig ens temporärlagra ett klartextlösen vilket uppenbarlgen skett här. Objektet ifråga skulle kunna ha gjort hasningen i sin konstruktor eller en setter, då hade man iaf. inte fått ut klartext i loggen. En bra illustration av problemet med att lagra klartextlösen var det iaf, tack för den!

Vad gäller lösenordsinloggning i övrigt argumenterar jag inte emot, men vill gärna nämna Stanfords SRP och liknande algoritmer som löser flera problem.
Det finns också friare varianter såsom http://www.toolcrypt.org/papers/spapwke/index.html.

John Wilander sa...

Intressanta åsikter, Olle. Ursäkta att det tagit mig sån tid att svara bara :).

Din input kopplar till det jag och min kollega Dan Bergh-Johnsson har skrivit om Domain-Driven Security, dvs att skapa korrekta värdeobjekt så tidigt som möjligt för att få indatavalidering på rätt ställe.

Lösenord hanteras som klartextsträngar av ganska mycket kod eftersom det på invägen har gått igenom webbservern som hanterar http, en eventuell WAF på webbservern eller i applikationslagret, ett MVC-ramverk med interceptors, kanske något XML/JSON-ramverk och så vidare. Så när strängen väl kommit till vår egen kod så har den förekommit som klartextsträng i ganska mycket annan, lokal kod. Man får förstås se upp med loggning även i de lagren. Det är inte ett försvar för dålig lösenordshantering men det ger en mer komplett bild av hur lösenorden hanteras.

Databasen hashar = bekvämt och robust
Frågan är då var man ska hasha? Här håller jag principiellt med dig, man bör hasha (och salta) så tidigt som möjligt. Vårt PutData-objekt skulle då kunna göra det i konstruktorn. Men många databaser erbjuder sitt eget skydd av lösenord. Ett bekvämt och hyfsat robust sätt för utvecklarna att komma bort från klartextlösenord i databasen. Att få ordning på kryptografiskt bra hashning i egen kod är inte alltid så enkelt. Det motsäger i viss mån principen om att undvika hemmakok när det gäller kryptografi. T ex så gör många misstag vid saltning (dålig pseudoslump, enkel strängkonkatenering osv). Dessutom är hashning notoriskt svårt pga alla encoding-problem (ASCII, UTF-8, ISO-8859-x, URL, HTML, BASE64, hex ...).

Om olika system ska kunna logga in användaren och dessa system är en mix av .NET/JavaEE/PHP och Win/Unix så kommer man med största säkerhet få problem med att skapa ekvivalenta hashar i alla systemen. Det går förstås att lösa men är jobbigare än att låta den centrala databasen sköta det.

Migrera konton till starkare skydd = jobbigt
Insikten om problemen med lösenord i webbappar har kommit smygande. För 3-4 år sen var det inte många utanför säkerhetsvärlden som hört om stora SQL injection-hack. Nu är det allmänt känt. Men att migrera till säkrare lösningar blir plötsligt jobbigt när man väl har börjat hasha lösenorden. Då har man plötsligt en användardatabas som bara kan migreras av användarna själva, dvs man applicerar den nya, säkrare hashningen varje gång en användare med gammalt konto loggar in. Någonstans längs vägen får man terminera de gamla konton som inte migrerats men fram tills dess så får man ha båda inloggningarna igång. Inte oöverkomligt men jobbigt.

Hybrid?
En intressant och bra hybrid vore att använda enklare, osaltad hashning i applikationslagret och sen lägga på bra salt och en mix av hashning i persistenslagret. Vad tror du om en sån lösning?

Och vad är dina erfarenheter/åsikter om det ovanstående?


PS. Som jag skrev så var loggningsproblemet något vi upptäckte i testsystemet ca 10 minuter efter att koden skrivits (genererats) så det var aldrig något om drabbade riktiga användare. DS.

Anonym sa...

Som sagt går jag inte in i diskution om alternativ till klartextlösen annat än att säga att det i webbvärlden saknas vettiga alternativ. En dröm vore ju om ID-delegationsteknik kunde lösa det i framtiden...

Gällande sparade lösenordshashar håller jag med om att man inte bör hitta på något själv. Jag brukar rekommendera att man använder bcrypt (http://www.openwall.com/crypt/) eller om man inte har tillgång till blowfish-primitiver att följa rekommendationerna i rfc2898 kapitel 4. Hash-builtins i databasmotorer är i princip aldrig bra alternativ, dessutom är det ju inte särskilt portabelt om man vill byta backend.

Idén med två separata lösenordslagringar förstod jag inte riktigt vilket problem det skulle lösa.

Boka after-work den 28:de så kan vi fortsätta samtalet AFK! (mer info kommer snart)