Jeg sidder pt. og forsøger, at sikre et site mod XSS og CSRF og
i den forbindelse er jeg pt. kommet frem til at flg. som minimum
bør gøres.
Sikring mod XSS
Her skal man sikre sig at klienten ikke kan injicere skadelig
indhold på sitet og dette indbærer, at man konstant (nærmest
paranoidt) sørger for at encode sit output. Der er flere
forskellige scenarier man skal forholde sig til og nogle af disse
er
- data der genereres ind i
HTML
- data der genereres ind i
HTML-tag atributter
- data der genereres ind i
JavaScript
Hver af disse skal håndteres forskelligt og flg. eksempler
forsøger at give et bud på, hvordan dette kan gøres. Model
indeholder i disse eksempler de tekster der skal genereres ind i
siden i et objekt der hedder Translations. Dette er for at
signalere, at teksterne kan være variable og derfor ikke kan
garanteres værende passende indhold i den givne kontekst.
Ad 1: Dette sker som udgangspunkt, når man bruger @ til at
generere output, dvs. at
<strong>@Model.Translations.ImportantText</strong>
som udgangspunkt er HtmlEncoded, fordi @ sørger for dette i MVC
3.
Ad 2: Her skal man gøre sig lidt ekstra umage, men
der findes også en helper i frameworket til dette, så data kan
indsættes i en atribut således
<a href="/"
title="@Html.AttributeEncode(Model.Translations.Home)">
@Model.Translations.Home</a>
Ad 3: Denne løsning ligner lidt
atribut-encoding, men formatet er endnu længere
<script>
var tekst =
"@Html.Raw(Ajax.JavaScriptStringEncode(
Model.Translations.AlertText))";
alert(tekst);
</script>
Med dette på plads skulle der være taget hånd om de fleste
scenarier mht. encoding. Det skal naturligvis gøres for ALT input
der kommer fra en utroværdig kilde - endda også databasen, da den
kan gemme på XSS, med mindre man har sørget for at lukke for alt
indkommende skadelig indhold på alle kanaler (POSTS, COOKIES,
HEADERS osv.).
Håndtering af CSRF
MVC 3 har som standard en atribut til controller actions, som
tjekker for om en given forespørgsel indeholder et unikt token og
hvis det ikke gør, så fejler forespørgslen helt.
Feltet hedder __RequestVerificationToken og ligger typisk i den
formular der sendes med en POST-forespørgsel. Dette felt kan, af
MVC frameworket, også nemt genereres og indsættes i formularer,
hvor dette måtte være nødvendigt. Dette gøres således
@using(Html.BeginForm())
{
@Html.AntiForgeryToken()
<!-- Resten af din fomular her... -->
}
Således er der, i formularen og i en cookie,
indsat et unikt token, som serveren kan tjekke på når formularen
POSTes tilbage til serveren. Controlleren til formularen kunne så
dekoreres således
[ValidateAntiForgeryToken]
public ActionResult Contact(ContactViewModel model)
{
// do the contactstuff here...
return View();
}
Således skulle denne formular være sikret mod at
contact-formularen kan sendes fra andre sites end dit eget. Hvis
man vil krydre tingene endnu mere, er der også mulighed for at
tilføje "salt" til sit token. Dette gøres således begge steder (i
formularen og i
ValidateAntiForgeryToken-atributten ifm. controller action).
Håndtering af CSRF i et AJAX-scenarium
Hvis man laver meget AJAX på en side, kan det være besværligt at
håndtere CSRF, men laver man POST-request flere gange i løbet af
sidens levetid, kan dette løses med lidt scripting og én
omnipresent formular på siden, der indeholder AntiForgeryToken (som
illustreret nedenfor).
@using(Html.BeginForm("", "", FormMethod.Post,
new { id = "frmARFTokenForm" }))
{
@Html.AntiForgeryToken()
}
I scriptet kan man så aflæse
denne og plastre den ind i sin AJAX-POST-forespørgsel. Dette kunne
se således ud (her antager jeg at jQuery er inkluderet i
siden)
<script>
$(function() {
var tokenForm = "#frmARFTokenForm",
tokenField = "input[name=__RequestVerificationToken]";
$("#sendData").click(function() {
var contactForm = $("#frmContact");
var arfToken = $(tokenForm + " " + tokenField).val();
$.ajax({
type: "POST",
url: "/home/contact",
data: {
text: $("textarea[name=comment]", contactForm).val(),
__RequestVerificationToken: arfToken
},
success: function(data) {
// update the page...
}
});
});
});
</script>
Dette betyder at det er samme token der sendes med alle
formularer indtil siden opdateres i browseren, hvorefter
frmARFTokenForm opdateres med et nyt token, som så benyttes af de
forskellige scripts.
Der er naturligvis basis for en centralisering af denne feature
(f.eks. en rutine til at hente ARFToken og en til at (ind)sætte
ARFToken-feltet i en given formular), men princippet er i det
mindste skitseret...