Förhindra XSS (Cross-Site Scripting) i ASP.NET Core
XSS (Cross-Site Scripting) är en säkerhetsrisk som gör det möjligt för en cyberattacker att placera skript på klientsidan (vanligtvis JavaScript) på webbsidor. När andra användare läser in berörda sidor körs cyberattackerns skript, vilket gör det möjligt för cyberattackeraren att stjäla cookies och sessionstoken, ändra innehållet på webbsidan via DOM-manipulation eller omdirigera webbläsaren till en annan sida. XSS-sårbarheter uppstår vanligtvis när ett program tar användarindata och matar ut det till en sida utan att validera, koda eller undvika det.
Den här artikeln gäller främst för ASP.NET Core MVC med vyer, Razor Pages och andra appar som returnerar HTML som kan vara sårbara för XSS. Webb-API:er som returnerar data i form av HTML, XML eller JSON kan utlösa XSS-attacker i sina klientappar om de inte korrekt sanerar användarindata, beroende på hur mycket förtroende klientappen har i API:et. Om ett API till exempel accepterar användargenererat innehåll och returnerar det i ett HTML-svar kan en cyberattacker mata in skadliga skript i det innehåll som körs när svaret återges i användarens webbläsare.
För att förhindra XSS-attacker bör webb-API:er implementera indataverifiering och utdatakodning. Indataverifiering säkerställer att användarindata uppfyller förväntade kriterier och inte innehåller skadlig kod. Utdatakodning säkerställer att alla data som returneras av API:et är korrekt sanerade så att de inte kan köras som kod av användarens webbläsare. Mer information finns i det här GitHub-ärendet.
Skydda ditt program mot XSS
På en grundläggande nivå fungerar XSS genom att lura ditt program att infoga en <script>
tagg på den renderade sidan eller genom att infoga en On*
händelse i ett element. Utvecklare bör använda följande förebyggande steg för att undvika att införa XSS i sina program:
- Placera aldrig ej betrodda data i HTML-indata, såvida du inte följer resten av stegen nedan. Ej betrodda data är alla data som kan styras av en cyberattacker, till exempel HTML-formulärindata, frågesträngar, HTTP-huvuden eller till och med data som kommer från en databas, eftersom en cyberattacker kan bryta mot din databas även om de inte kan bryta mot ditt program.
- Innan du placerar obetrodda data i ett HTML-element kontrollerar du att det är HTML-kodat. HTML-kodning tar tecken som < och ändrar dem till ett säkert formulär som <.
- Innan du placerar ej betrodda data i ett HTML-attribut kontrollerar du att de är HTML-kodade. HTML-attributkodning är en delmängd av HTML-kodning och kodar dubbla citattecken ("), enkla citattecken ('), et-tecken (&) och mindre än (<) tecken.
- Innan du ska placera ej betrodda data i JavaScript, ska du placera data i ett HTML-element vars innehåll du hämtar under körning. Om detta inte är möjligt kontrollerar du att data är JavaScript-kodade. JavaScript-kodning tar farliga tecken för JavaScript och ersätter dem med deras hex, till exempel < skulle kodas som
\u003C
. - Innan du lägger in ej betrodda data i en URL-frågesträng kontrollerar du att den är URL-kodad.
HTML-kodning med hjälp av Razor
Den Razor motorn som används i MVC kodar automatiskt alla utdata som kommer från variabler, såvida du inte arbetar hårt för att förhindra att den gör det. Den använder HTML-attributkodningsregler när du använder @-direktivet. Eftersom HTML-attributkodning är en superuppsättning HTML-kodning innebär det att du inte behöver bry dig om huruvida du ska använda HTML-kodning eller HTML-attributkodning. Du måste se till att du bara använder @ i en HTML-kontext, inte när du försöker infoga ej betrodda indata direkt i JavaScript. Tagghjälpare kodar också indata som du använder i taggparametrar.
Ta följande Razor vybild:
@{
var untrustedInput = "<\"123\">";
}
@untrustedInput
Den här vyn matar ut innehållet i variabeln untrustedInput. Den här variabeln innehåller vissa tecken som används i XSS-attacker, nämligen <, " och >. När du undersöker källan visas de renderade utdata som kodats som:
<"123">
Varning
ASP.NET Core MVC tillhandahåller en HtmlString
-klass som inte kodas automatiskt vid utdata. Detta bör aldrig användas i kombination med ej betrodda indata eftersom detta kommer att exponera en XSS-säkerhetsrisk.
JavaScript-kodning med hjälp av Razor
Det kan finnas tillfällen då du vill infoga ett värde i JavaScript för att bearbeta i vyn. Det finns två sätt att göra detta på. Det säkraste sättet att infoga värden är att placera värdet i ett dataattribut för en tagg och hämta det i JavaScript. Till exempel:
@{
var untrustedInput = "<script>alert(1)</script>";
}
<div id="injectedData"
data-untrustedinput="@untrustedInput" />
<div id="scriptedWrite" />
<div id="scriptedWrite-html5" />
<script>
var injectedData = document.getElementById("injectedData");
// All clients
var clientSideUntrustedInputOldStyle =
injectedData.getAttribute("data-untrustedinput");
// HTML 5 clients only
var clientSideUntrustedInputHtml5 =
injectedData.dataset.untrustedinput;
// Put the injected, untrusted data into the scriptedWrite div tag.
// Do NOT use document.write() on dynamically generated data as it
// can lead to XSS.
document.getElementById("scriptedWrite").innerText += clientSideUntrustedInputOldStyle;
// Or you can use createElement() to dynamically create document elements
// This time we're using textContent to ensure the data is properly encoded.
var x = document.createElement("div");
x.textContent = clientSideUntrustedInputHtml5;
document.body.appendChild(x);
// You can also use createTextNode on an element to ensure data is properly encoded.
var y = document.createElement("div");
y.appendChild(document.createTextNode(clientSideUntrustedInputHtml5));
document.body.appendChild(y);
</script>
Föregående markering genererar följande HTML:
<div id="injectedData"
data-untrustedinput="<script>alert(1)</script>" />
<div id="scriptedWrite" />
<div id="scriptedWrite-html5" />
<script>
var injectedData = document.getElementById("injectedData");
// All clients
var clientSideUntrustedInputOldStyle =
injectedData.getAttribute("data-untrustedinput");
// HTML 5 clients only
var clientSideUntrustedInputHtml5 =
injectedData.dataset.untrustedinput;
// Put the injected, untrusted data into the scriptedWrite div tag.
// Do NOT use document.write() on dynamically generated data as it can
// lead to XSS.
document.getElementById("scriptedWrite").innerText += clientSideUntrustedInputOldStyle;
// Or you can use createElement() to dynamically create document elements
// This time we're using textContent to ensure the data is properly encoded.
var x = document.createElement("div");
x.textContent = clientSideUntrustedInputHtml5;
document.body.appendChild(x);
// You can also use createTextNode on an element to ensure data is properly encoded.
var y = document.createElement("div");
y.appendChild(document.createTextNode(clientSideUntrustedInputHtml5));
document.body.appendChild(y);
</script>
Föregående kod genererar följande utdata:
<script>alert(1)</script>
<script>alert(1)</script>
<script>alert(1)</script>
Varning
Sammanfoga INTE obetrodd indata i JavaScript för att skapa DOM-element eller använda document.write()
på dynamiskt genererat innehåll.
Använd någon av följande metoder för att förhindra att kod exponeras för DOM-baserad XSS:
-
createElement()
och tilldela egenskapsvärden med lämpliga metoder eller egenskaper somnode.textContent=
ellernode.InnerText=
. -
document.CreateTextNode()
och lägg till den på lämplig DOM-plats. element.SetAttribute()
element[attribute]=
Åtkomst till kodare i kod
HTML-, JavaScript- och URL-kodarna är tillgängliga för din kod på två sätt:
- Inför dem via beroendeinjektion.
- Använd standardkodarna i
System.Text.Encodings.Web
-namnområdet.
När du använder standardkodarna börjar inga anpassningar som tillämpas på teckenintervall som ska behandlas som säkra att gälla. Standardkodarna använder de säkraste kodningsreglerna som är möjliga.
Om du vill använda de konfigurerbara kodarna via DI bör konstruktorerna ta en HtmlEncoder, JavaScriptEncoder och UrlEncoder parameter efter behov. Till exempel;
public class HomeController : Controller
{
HtmlEncoder _htmlEncoder;
JavaScriptEncoder _javaScriptEncoder;
UrlEncoder _urlEncoder;
public HomeController(HtmlEncoder htmlEncoder,
JavaScriptEncoder javascriptEncoder,
UrlEncoder urlEncoder)
{
_htmlEncoder = htmlEncoder;
_javaScriptEncoder = javascriptEncoder;
_urlEncoder = urlEncoder;
}
}
Kodning av URL-parametrar
Om du vill skapa en URL-frågesträng med ej betrodda indata som ett värde använder du UrlEncoder
för att koda värdet. Till exempel
var example = "\"Quoted Value with spaces and &\"";
var encodedValue = _urlEncoder.Encode(example);
Efter kodning innehåller variabeln encodedValue %22Quoted%20Value%20with%20spaces%20and%20%26%22
. Blanksteg, citattecken, skiljetecken och andra osäkra tecken är procentkodade till deras hexadecimala värde, till exempel blir ett blankstegstecken %20.
Varning
Använd inte ej betrodda indata som en del av en URL-sökväg. Ange alltid ej betrodda indata som ett värde i frågesträngen.
Anpassa kodarna
Som standard använder kodare en säker lista som är begränsad till Unicode-intervallet Basic Latin och kodar alla tecken utanför det intervallet som deras teckenkodsekvivalenter. Det här beteendet påverkar även Razor TagHelper- och HtmlHelper-återgivningen eftersom den använder kodarna för att mata ut dina strängar.
Resonemanget bakom detta är att skydda mot okända eller framtida webbläsarbuggar (tidigare webbläsarbuggar har orsakat problem med parsning baserat på hantering av icke-engelska tecken). Om din webbplats använder icke-latinska tecken, till exempel kinesiska, kyrilliska eller andra, är detta förmodligen inte det beteende du vill ha.
Kodarsäkra listor kan anpassas för att inkludera Unicode-intervall som är lämpliga för appen under starten, i Program.cs
:
Du kan till exempel använda standardkonfigurationen med hjälp av en Razor HtmlHelper som liknar följande:
<p>This link text is in Chinese: @Html.ActionLink("汉语/漢語", "Index")</p>
Föregående markering återges med kinesisk text kodad:
<p>This link text is in Chinese: <a href="/">汉语/漢語</a></p>
Om du vill bredda de tecken som behandlas som säkra av kodaren infogar du följande rad i Program.cs
.:
builder.Services.AddSingleton<HtmlEncoder>(
HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
UnicodeRanges.CjkUnifiedIdeographs }));
Du kan anpassa kodarens säkra listor så att de innehåller Unicode-intervall som är lämpliga för ditt program under starten, i ConfigureServices()
.
Om du till exempel använder standardkonfigurationen kan du använda en Razor HtmlHelper så här;
<p>This link text is in Chinese: @Html.ActionLink("汉语/漢語", "Index")</p>
När du visar källan till webbsidan ser du att den har renderats på följande sätt, med den kinesiska texten kodad.
<p>This link text is in Chinese: <a href="/">汉语/漢語</a></p>
Om du vill bredda de tecken som behandlas som säkra av kodaren infogar du följande rad i metoden ConfigureServices()
i startup.cs
;
services.AddSingleton<HtmlEncoder>(
HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
UnicodeRanges.CjkUnifiedIdeographs }));
I det här exemplet utökas listan med säkra värden till att omfatta Unicode Range CjkUnifiedIdeographs. De renderade utdata skulle nu bli
<p>This link text is in Chinese: <a href="/">汉语/漢語</a></p>
Säkra listintervall anges som Unicode-koddiagram, inte språk. Unicode Standard har en lista över koddiagram du kan använda för att hitta diagrammet som innehåller dina tecken. Varje kodare, Html, JavaScript och URL, måste konfigureras separat.
Anteckning
Anpassning av den säkra listan påverkar endast kodare som hämtas via DI. Om du har direkt åtkomst till en kodare via System.Text.Encodings.Web.*Encoder.Default
, används då endast standardlistan Basic Latin.
Var ska kodning ske?
Den allmänna godkända metoden är att kodning sker vid utdatapunkten och att kodade värden aldrig ska lagras i en databas. Med kodning vid utdatapunkten kan du ändra användningen av data, till exempel från HTML till ett frågesträngsvärde. Det gör också att du enkelt kan söka efter dina data utan att behöva koda värden innan du söker och gör att du kan dra nytta av ändringar eller felkorrigeringar som gjorts för kodare.
Validering som en XSS-förebyggande teknik
Validering kan vara ett användbart verktyg för att begränsa XSS-attacker. Till exempel utlöser inte en numerisk sträng som bara innehåller tecknen 0–9 en XSS-attack. Valideringen blir mer komplicerad när html accepteras i användarindata. Det är svårt att parsa HTML-indata, om inte omöjligt. Markdown, tillsammans med en parser som tar bort inbäddad HTML, är ett säkrare alternativ för att acceptera omfattande indata. Förlita dig aldrig på validering ensam. Koda alltid ej betrodda indata före utdata, oavsett vilken validering eller sanering som har utförts.
ASP.NET Core