Impedire lo scripting tra siti (XSS) in ASP.NET Core

Di Rick Anderson

Scripting tra siti (XSS) è una vulnerabilità di sicurezza che consente a un cyberattacker di inserire script lato client (in genere JavaScript) in pagine Web. Quando altri utenti caricano pagine interessate, gli script di cyberattacker vengono eseguiti, consentendo al cyberattacker di rubare cookie e token di sessione, modificare il contenuto della pagina Web tramite la manipolazione DOM o reindirizzare il browser a un'altra pagina. Le vulnerabilità XSS si verificano in genere quando un'applicazione accetta l'input dell'utente e lo restituisce in una pagina senza convalidare, codificare o evitare l'escape.

Questo articolo si applica principalmente a ASP.NET Core MVC con visualizzazioni, Razor pagine e altre app che restituiscono CODICE HTML che potrebbero essere vulnerabili a XSS. Le API Web che restituiscono dati sotto forma di HTML, XML o JSON possono attivare attacchi XSS nelle app client se non sanificano correttamente l'input dell'utente, a seconda della quantità di attendibilità che l'app client inserisce nell'API. Ad esempio, se un'API accetta contenuto generato dall'utente e lo restituisce in una risposta HTML, un cyberattacker potrebbe inserire script dannosi nel contenuto eseguito quando viene eseguito il rendering della risposta nel browser dell'utente.

Per evitare attacchi XSS, le API Web devono implementare la convalida dell'input e la codifica di output. La convalida dell'input garantisce che l'input dell'utente soddisfi i criteri previsti e non includa codice dannoso. La codifica di output garantisce che tutti i dati restituiti dall'API siano correttamente sanificati in modo che non possano essere eseguiti come codice dal browser dell'utente. Per altre informazioni, vedere questo problema in GitHub.

Protezione dell'applicazione da XSS

A livello di base, XSS funziona ingannando l'applicazione nell'inserimento di un <script> tag nella pagina sottoposta a rendering o inserendo un On* evento in un elemento. Gli sviluppatori devono usare i passaggi di prevenzione seguenti per evitare di introdurre XSS nelle applicazioni:

  1. Non inserire mai dati non attendibili nell'input HTML, a meno che non si segua la rest procedura seguente. I dati non attendibili sono dati che possono essere controllati da un cyberattacker, ad esempio input di moduli HTML, stringhe di query, intestazioni HTTP o persino dati originati da un database, come cyberattacker possono essere in grado di accedere al breach database anche se non breach possono essere applicazioni.

  2. Prima di inserire dati non attendibili all'interno di un elemento HTML, assicurarsi che sia codificato in HTML. La codifica HTML accetta caratteri come < e li modifica in una forma sicura come <

  3. Prima di inserire dati non attendibili in un attributo HTML, assicurarsi che sia codificato in HTML. La codifica degli attributi HTML è un superset di codifica HTML e codifica caratteri aggiuntivi, ad esempio " e ".

  4. Prima di inserire dati non attendibili in JavaScript, inserire i dati in un elemento HTML il cui contenuto viene recuperato in fase di esecuzione. Se non è possibile, verificare che i dati siano codificati in JavaScript. La codifica JavaScript accetta caratteri pericolosi per JavaScript e li sostituisce con l'hex, ad esempio, < verrebbe codificato come \u003C.

  5. Prima di inserire dati non attendibili in una stringa di query URL, assicurarsi che sia codificato in URL.

Codifica HTML con Razor

Il Razor motore usato in MVC codifica automaticamente tutti gli output originati dalle variabili, a meno che non si lavori davvero duramente per impedirlo. Usa regole di codifica degli attributi HTML ogni volta che si usa la @ direttiva . Poiché la codifica degli attributi HTML è un superset di codifica HTML, ciò significa che non è necessario preoccuparsi se è necessario usare la codifica HTML o la codifica degli attributi HTML. È necessario assicurarsi di usare @ solo in un contesto HTML, non quando si tenta di inserire l'input non attendibile direttamente in JavaScript. Gli helper tag codificano anche l'input usato nei parametri dei tag.

Si prenda la visualizzazione seguente Razor :

@{
    var untrustedInput = "<\"123\">";
}

@untrustedInput

Questa vista restituisce il contenuto della variabile untrustedInput . Questa variabile include alcuni caratteri usati negli attacchi XSS, ovvero <, " e >. Esaminando l'origine, l'output sottoposto a rendering viene codificato come segue:

&lt;&quot;123&quot;&gt;

Avviso

ASP.NET Core MVC fornisce una HtmlString classe che non viene codificata automaticamente all'output. Questa operazione non deve mai essere usata in combinazione con l'input non attendibile, perché espone una vulnerabilità XSS.

Codifica JavaScript con Razor

In alcuni casi potrebbe essere necessario inserire un valore in JavaScript da elaborare nella visualizzazione. Per eseguire questa operazione è possibile procedere in due modi: Il modo più sicuro per inserire i valori consiste nell'inserire il valore in un attributo di dati di un tag e recuperarlo in JavaScript. Ad esempio:

@{
    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>

Il markup precedente genera il codice HTML seguente:

<div id="injectedData"
     data-untrustedinput="&lt;script&gt;alert(1)&lt;/script&gt;" />

<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>

Il codice precedente genera l'output seguente:

<script>alert(1)</script>
<script>alert(1)</script>
<script>alert(1)</script>

Avviso

Non concatenare l'input non attendibile in JavaScript per creare elementi DOM o usarlo document.write() in contenuto generato in modo dinamico.

Usare uno degli approcci seguenti per impedire che il codice venga esposto a XSS basato su DOM:

  • createElement() e assegnare valori di proprietà con metodi o proprietà appropriati, ad node.textContent= esempio o node.InnerText=.
  • document.CreateTextNode() e aggiungerlo nella posizione DOM appropriata.
  • element.SetAttribute()
  • element[attribute]=

Accesso ai codificatori nel codice

I codificatori HTML, JavaScript e URL sono disponibili per il codice in due modi:

  • Inserirli tramite l'inserimento delle dipendenze.
  • Usare i codificatori predefiniti contenuti nello spazio dei System.Text.Encodings.Web nomi .

Quando si usano i codificatori predefiniti, le personalizzazioni applicate agli intervalli di caratteri da considerare come sicure non avranno effetto. I codificatori predefiniti usano le regole di codifica più sicure possibili.

Per usare i codificatori configurabili tramite DI, i costruttori devono accettare un parametro HtmlEncoder, JavaScriptEncoder e UrlEncoder in base alle esigenze. Per esempio;

public class HomeController : Controller
{
    HtmlEncoder _htmlEncoder;
    JavaScriptEncoder _javaScriptEncoder;
    UrlEncoder _urlEncoder;

    public HomeController(HtmlEncoder htmlEncoder,
                          JavaScriptEncoder javascriptEncoder,
                          UrlEncoder urlEncoder)
    {
        _htmlEncoder = htmlEncoder;
        _javaScriptEncoder = javascriptEncoder;
        _urlEncoder = urlEncoder;
    }
}

Parametri URL di codifica

Se si vuole compilare una stringa di query URL con input non attendibile come valore, usare per UrlEncoder codificare il valore. ad esempio:

var example = "\"Quoted Value with spaces and &\"";
var encodedValue = _urlEncoder.Encode(example);

Dopo la codifica della variabile encodedValue contiene %22Quoted%20Value%20with%20spaces%20and%20%26%22. Spazi, virgolette, punteggiatura e altri caratteri non sicuri vengono codificati in percentuale nel valore esadecimale, ad esempio un carattere di spazio diventerà %20.

Avviso

Non usare l'input non attendibile come parte di un percorso URL. Passare sempre l'input non attendibile come valore della stringa di query.

Personalizzazione dei codificatori

Per impostazione predefinita, i codificatori usano un elenco sicuro limitato all'intervallo Unicode latino di base e codificano tutti i caratteri all'esterno di tale intervallo come equivalenti al codice carattere. Questo comportamento influisce anche sul Razor rendering di TagHelper e HtmlHelper perché usa i codificatori per restituire le stringhe.

Il motivo alla base di questo è proteggere da bug sconosciuti o futuri del browser (i bug del browser precedenti hanno troncato l'analisi in base all'elaborazione di caratteri non inglesi). Se il sito Web usa pesantemente caratteri non latini, ad esempio cinese, cirillico o altri, questo probabilmente non è il comportamento desiderato.

Gli elenchi sicuri del codificatore possono essere personalizzati per includere intervalli Unicode appropriati per l'app durante l'avvio, in Program.cs:

Ad esempio, usando la configurazione predefinita usando un Razor HtmlHelper simile al seguente:

<p>This link text is in Chinese: @Html.ActionLink("汉语/漢語", "Index")</p>

Il rendering del markup precedente viene eseguito con testo cinese codificato:

<p>This link text is in Chinese: <a href="/">&#x6C49;&#x8BED;/&#x6F22;&#x8A9E;</a></p>

Per ampliare i caratteri trattati come sicuri dal codificatore, inserire la riga seguente in Program.cs.:

builder.Services.AddSingleton<HtmlEncoder>(
     HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
                                               UnicodeRanges.CjkUnifiedIdeographs }));

È possibile personalizzare gli elenchi sicuri del codificatore in modo da includere intervalli Unicode appropriati per l'applicazione durante l'avvio, in ConfigureServices().

Ad esempio, usando la configurazione predefinita, è possibile usare un Razor HtmlHelper come in questo caso;

<p>This link text is in Chinese: @Html.ActionLink("汉语/漢語", "Index")</p>

Quando si visualizza l'origine della pagina Web, si noterà che è stato eseguito il rendering come indicato di seguito, con il testo cinese codificato;

<p>This link text is in Chinese: <a href="/">&#x6C49;&#x8BED;/&#x6F22;&#x8A9E;</a></p>

Per ampliare i caratteri trattati come sicuri dal codificatore, inserire la riga seguente nel ConfigureServices() metodo in startup.cs;

services.AddSingleton<HtmlEncoder>(
     HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
                                               UnicodeRanges.CjkUnifiedIdeographs }));

In questo esempio viene ampliato l'elenco sicuro in modo da includere gli intervalli Unicode CjkUnifiedIdeographs. L'output di cui è stato eseguito il rendering diventa ora

<p>This link text is in Chinese: <a href="/">汉语/漢語</a></p>

Gli intervalli di elenchi sicuri vengono specificati come grafici di codice Unicode, non come linguaggi. Lo standard Unicode include un elenco di grafici di codice che è possibile usare per trovare il grafico contenente i caratteri. Ogni codificatore, Html, JavaScript e URL deve essere configurato separatamente.

Nota

La personalizzazione dell'elenco sicuro influisce solo sui codificatori originati tramite inserimento di dipendenze. Se si accede direttamente a un codificatore tramite System.Text.Encodings.Web.*Encoder.Default l'impostazione predefinita, verrà usato solo l'elenco sicuro Di base latino.

Dove deve avvenire la codifica?

La procedura generale accettata è che la codifica avviene al punto di output e i valori codificati non devono mai essere archiviati in un database. La codifica al punto di output consente di modificare l'uso dei dati, ad esempio da HTML a un valore di stringa di query. Consente inoltre di eseguire facilmente ricerche nei dati senza dover codificare i valori prima della ricerca e consente di sfruttare eventuali modifiche o correzioni di bug apportate ai codificatori.

Convalida come tecnica di prevenzione XSS

La convalida può essere uno strumento utile per limitare gli attacchi XSS. Ad esempio, una stringa numerica contenente solo i caratteri 0-9 non attiverà un attacco XSS. La convalida diventa più complessa quando si accetta HTML nell'input dell'utente. L'analisi dell'input HTML è difficile, se non impossibile. Markdown, abbinato a un parser che esegue lo striping del codice HTML incorporato, è un'opzione più sicura per accettare input avanzato. Non fare mai affidamento solo sulla convalida. Codificare sempre l'input non attendibile prima dell'output, indipendentemente dalla convalida o dalla purificazione eseguita.