venerdì 12 settembre 2014

Risolvere la tirannia di HTTP 403 risposte per l'esplorazione delle directory in ASP.NET

L'utente non può saperlo, ma una risposta HTTP 403 quando si naviga ad una directory vuota è un serio rischio per la sicurezza.

Quello che il ?! Vuoi dire che se vado al mio sito che ha una cartella "scripts" dove ho messo tutta la mia JavaScript e io sono l'esplorazione delle directory disattivato (come giustamente dovrebbe) ed il server restituisce un "Forbidden" 403 (che giustamente dovrebbe), I 'sto mettendo le mie cose di internet a rischio di essere pwned ?!

Sì, perché rivela la presenza di una cartella denominata "script", che è una directory comune.

Beh, certo c'è una cartella sanguinosa denominata "script", tutta la mia sorgente HTML che è possibile vedere i riferimenti esso! Potrei chiamarlo "i-love-drunken-elefanti" e si poteva ancora vedere quindi qual è il punto ?!

Ma sarebbe comunque restituire un 403 che confermerebbe l'esistenza della risorsa e rappresentare un rischio directory enumerazione.

Ma si può scoprire la presenza di directory in ogni caso! Ok, in apps moderni come ASP.NET MVC potrebbero in realtà essere percorsi che non si traducono in attraverso percorsi fisici, ma ancora, questo è solo di essere pedante!

Il tuo sito non può andare dal vivo fino a risolvere il problema.

Uh, vorrei solo risolvere che per te ...

Alle prese con il problema di fondo
Questa è una di quelle cose che a torto oa ragione, che ho visto che spuntano da varie squadre di sicurezza e scanner automatici negli ultimi tempi. Si può obiettare che tutto quello che volete (e la gravità di essa è contestata), ma il fatto che alza la sua 'dibattito testa e le cause è sufficiente per risolvere proprio il dannato e da fare con esso. Oh - e per inciso, mi sono imbattuto un Netsparker sopra sono stato pwned? (HIBP) di recente e questo è stato uno dei risultati così yeah, mi colpisce troppo (anche se ho il lusso di scegliere di ignorarlo se mi piace!)

Netsparker segnalare una "risorsa Forbidden" su una cartella con la navigazione diretory disabilitato

Lascia che ti mostri il motivo per cui questo accade: la fonte di ogni pagina ho un tag <script> come questo:

< lo script src = "/ scripts / pwned? v = 2VUGdHR_7X4UImu2rpAgoquGkcvoIBUlzD35P5Y-dgo1"> </ script di >
Questo è in realtà utilizzando bundling e minification ASP.NET per combinare più script in uno e poi schiacciare tutto il codice JavaScript, ma cosa vuol dire è che sta implicando c'è un sentiero che è semplicemente "/ scripts". Se colpiamo percorso che otterremo il seguente:

Il / scripts percorso di ritorno 403

Sì, ho errori personalizzati configurati per l'applicazione, ma non prendo l'403,14 restituito quando l'utente non è autorizzato a sfogliare una directory senza pagina di default presente. Aspetta - qual è il bit .14? Questo è il codice di sub-stato che IIS restituisce per questo particolare sapore di un errore di "proibito". Non si vede il codice di stato secondario riflette esternamente nella risposta, ma esiste all'interno del processo di restituzione.

Troverete lo stesso errore viene restituito per una serie di altri percorsi che sono facilmente individuabili compreso / contenuto, / contenuti / immagini / fonts. Ogni volta che si dispone di una directory, si corre il rischio di un 403,14 da restituire e la gente di sicurezza sempre arrogante.

Un modo per affrontare questo è quello di creare un ingresso URL Rewrite regola tale che ogni richiesta di una cartella vuota conosciuta semplicemente viene espulso al tuo errore predefinito personalizzato o generico pagina 404. Che funziona, ma non è scalabile come si deve farlo per ogni singola cartella compreso quando si aggiungono più tardi su - il quale sarete inevitabilmente dimenticare.

No, qualcosa di più sostenibile si impone.

Facendo la risposta 403 (e perché non si può)
Il mio pensiero originale era questo: io creo una regola di riscrittura URL che cattura l'uscita 403 risposta e semplicemente riscrive a un 302 e aggiunge un'intestazione posizione o in altre parole, dice il browser per reindirizzare fuori alla mia solita pagina 404. Nonostante quanto brillante ho pensato che questa soluzione era, semplicemente non sarebbe giocare a palla. La risposta non veniva catturato dalla regola e il codice di stato non veniva riscritto. In preda alla disperazione, mi rivolsi a Stack Overflow e ha spiegato che io non posso cambiare IIS codice di risposta con URL Rewrite regola di uscita . Ho anche mandato collega MVP e URL Rewrite guru Scott Forsyth .

Potete leggere la risposta di Scott su Stack Overflow, ma insomma, che proprio non andava a lavorare. URL Rewrite non può riscrivere i codici di risposta in modo da qualcos'altro è stato chiamato per. Ci sono probabilmente modi di affrontare questo con un modulo HTTP o da qualche parte all'interno del ciclo di vita della risposta, ma non è una soluzione sola configurazione e ho davvero voluto mantenere questo vincolato a qualcosa che può essere fatto senza ricompilare, semplicemente perché questo è un importante vantaggio per un sacco di gente, soprattutto quando hanno già ottenuto siti web in esecuzione dal vivo e ottengono un risultato come questo.

Ma la soluzione ha cominciato a svilupparsi in risposta di Scott e tutto si riduce a come gli errori vengono gestiti all'interno di system.webServer.

Definizione di errori personalizzati in system.webServer (e come è solo una soluzione parziale)
Ecco la risposta di Stack Overflow che sulla superficie di esso, fa buon senso:

< httpErrors ErrorMode = " Personalizzato " >
  < errore statusCode = " 403 " subStatusCode = " 14 " percorso = " / Error / PageNotFound " responseMode = " ExecuteURL " />
</ httpErrors >
Vediamo ora come che guarda su HIBP:

Una pagina "Pagina non trovata" dopo aver configurato un errore HTTP in system.webServer

Ok, se realmente stiamo ottenendo da qualche parte - un pò. Stiamo vedendo "Pagina non trovata", e la richiesta è restituzione di un HTTP 200 che soddisferà il "Noi non vogliono vedere un 403" richieste, ma non è compatibile con un normale 404 sul sito. Per esempio:

Un vero e proprio 404 mostra un reindirizzamento 302

Si potrebbe sostenere che, mentre sì, non c'è più un 403 e che la casella di particolare può essere spuntata, il fatto che l'errore di esplorazione directory restituisce la "Pagina non trovata" pagina sul URL originale con un HTTP 200, mentre la genuina 404 reindirizza a / Errore / PageNotFound rivela effettivamente la stessa cosa di prima - che c'è una cartella fisica denominata "script".

Possiamo cominciare a risolvere il problema abbastanza facilmente modificando l'attributo "responseMode" a "Redirect" invece di "ExecuteUrl". Ecco cosa succede ora:

Reindirizzamento piuttosto che riscrivere l'errore system.webServer

Ah, così va meglio - specie di. Vedere come ora abbiamo la stessa risposta 302 seguito da un reindirizzamento a / Error / PageNotFound - questo è il bello. La cattiva bit è che abbiamo ancora una differenza fondamentale in quanto non c'è stringa di query "aspxerrorpath" quando il reindirizzamento avviene system.webServer. Non è possibile aggiungere neanche, non per la configurazione e né è possibile rimuoverlo dal errore personalizzato che gestisce la genuina 404, almeno non senza aggiustamenti stesso.

Tempo per ottenere creativo.

Rimozione della stringa di query aspxerrorpath
In realtà c'è una soluzione molto semplice per rimuovere la stringa di query dalla configurazione errori personalizzati ed è questo:

< customErrors mode = " On " defaultRedirect = " ~ / Error " >
  < errore statusCode = " 404 " redirect = " / Error / PageNotFound? foo = bar " />
</ customErrors >
Che poi significa che il percorso / foo da prima si dà questo:

Appening una stringa di query per non più alla pagina di errore CUSTM passa il valore aspxerror

Che potrebbe sembrare un po 'irregolare (ed è), ma ora si può andare e applicare la stessa stringa di query per il redirect in system.webServer e montone castrato è un vero e proprio 404 o si tratta di un realtà un 403,14, si otterrà lo stesso irregolare stringa di query - lavoro fatto! Non importa ciò che la stringa di query è chiamato ("foo" è puramente casuale) e in effetti non avrete nemmeno bisogno di una coppia nome valore, si può semplicemente aggiungere il punto interrogativo e che da sola è sufficiente.

Il problema, però, è che non mi piace stringhe di query screwy che appaiono nella risposta di no (apparente) buona ragione quindi abbiamo bisogno di un po 'di più a smanettare ancora ...

Riscrivere la posizione redirect
Il problema ora è che voglio prendere / Error / PageNotFound? Foo = bar e di sbarazzarsi della stringa di query. Nel caso in cui la semantica di redirect non sono del tutto familiari, quando un risposte del server web con un 302 come nella schermata afferrare sopra, è anche un colpo di testa "location" che assomiglia a questo:

Località: / Error / PageNotFound foo = bar?
Questo indica al browser di emettere una successiva richiesta di questo percorso, che è il motivo per poi vedere la seconda richiesta di cui sopra. Questo è ciò che dobbiamo cambiare e per fortuna si tratta di una soluzione semplice con URL Rewrite. Sembra proprio come questo:

< outboundRules >
  < regola nome = " Modifica intestazione posizione " patternSyntax = " ExactMatch " >
    < partita serverVariable = " RESPONSE_location " modello = " / Error / PageNotFound? foo = bar " />
    < azione di tipo = " Rewrite " value = " / Error / PageNotFound " />
  </ regola >
</ outboundRules >
Roba abbastanza semplice - prendere la "location" intestazione di risposta quando si sta cercando di reindirizzare il browser al nostro percorso di stringa di query irregolare e poi basta spogliarla fuori. Proviamo ora:

La stringa di query "? Foo = bar" ora rimosso dalla risposta

Ehi, questo sembra a posto, ora è esattamente lo stesso del comportamento quando si carica il percorso script! Bastava un errore personalizzato in system.webServer poi una stringa di query irregolare in errore system.web personalizzato per la risposta 404 e una regola di URL Rewrite per modificare il percorso. Come semplice è che ... ?!

Ok, devo essere un po 'faceto, perché dovrebbe essere più facile di quello, ma non è. Il lato positivo, però, ecco quello che abbiamo ora:

Non è più un 403 restituito durante la navigazione di una directory senza pagina di default quando l'esplorazione delle directory è disabilitata C'è.
La risposta da un vero 404 su un percorso inesistente è identica a navigare un percorso fisico senza doc predefinito.
E 'tutto fatto da solo la configurazione, nessun codice.
Quindi è perfetto allora? Beh un pò, tutto tranne che per una piccola cosa ...

Aggira reindirizzamenti di cortesia
Se sei aquila-eyed abbastanza, avrete notato una leggera differenza nel modo in cui ho chiesto foo e script in precedenza con il primo è denominato "/ foo" e quest'ultimo "/ scripts /". Quella barra finale fa una grande differenza, perché ecco cosa succede quando non esiste:

Manca slash causando un HTTP 301 poi un 302

Che follia è questa ?! A 301 "permanente" Redirect seguito da un "temporaneo" 302 redirect e infine la pagina "PageNotFound" - ciò che dà? E 'una cortesia reindirizzamento , cioè IIS è in realtà alla ricerca di un file di denominato "script" e quando non riesce a trovarlo, si rende conto che è c'è una directory con il nome e sputa indietro un 301 dicendo al browser molto esplicitamente che questo è davvero un cartella in virtù della barra finale appena aggiunto. Naturalmente dice anche il signor Hacker la stessa cosa, così mentre il 403 è andato ed i sentieri e le stringhe di query sono tutti buoni, che il reindirizzamento in più dà al gioco via.

Per essere onesti, come circa ora sarete senza dubbio ottenere un segno di spunta dalla gente di sicurezza in quanto non vi è più una 403 Se questo è il vostro obiettivo, allora si può fermare qui, infatti si potrebbe avere arrestato subito dopo il system.webServer voce di errore personalizzato, ma secondo me, che tipo di prova a metà mandato a quel paese. Se ho intenzione di fare questo, ho intenzione di farlo correttamente dannazione!

Sono andato avanti e indietro un po 'con Scott su questo fino a quando siamo venuti a un'implementazione che assomiglia a questo:

< system.webServer >
  < defaultDocument abilitato = " true " />
</ system.webServer >
Questo è abbastanza auto-esplicativo - disabilitare la funzionalità di documento predefinito. Ciò significa allora che il DefaultDocumentModule (che è di destra, senza spazi, è una cosa) non causa più la 301 per il percorso con la barra finale, al fine di implicare la richiesta è di una cartella che dovrebbe poi servire il documento predefinito. Significa che la richiesta appare come segue:

A 302 dal percorso script senza slash alla pagina di errore

Significa anche ciascuno dei seguenti scenari risponde in modo identico:

GET https://haveibeenpwned.com/PathDoesntExist
HTTP 302 -> / Error / PageNotFound

GET https://haveibeenpwned.com/PathDoesntExistWithTrailingSlash/
HTTP 302 -> / Error / PageNotFound

GET https://haveibeenpwned.com/scripts
HTTP 302 -> / Error / PageNotFound

GET https://haveibeenpwned.com/scripts/
HTTP 302 -> / Error / PageNotFound
Hallelujah - funziona!

Fatta eccezione per i tempi non è così. In realtà sempre funziona su HIBP, ma che è perché è un app MVC che implementa il routing per mappare un URL come https://haveibeenpwned.com/About al "Chi" controllore. Se fosse un app Web Forms e dipendeva da un file Default.aspx nella "About" directory allora avrei un problema del tutto nuovo.

Tuttavia, ci sono più strade per affrontare questo. Uno è quello di consentire solo i documenti predefiniti per una whitelist di percorsi. Ok, non molto scalabile ma certamente fattibile.

Un altro è quello di utilizzare URL Rewrite per reindirizzare le query ai percorsi specifici che si basano su un documento predefinito per l'URL che contiene esplicitamente il nome del file in esso, ad esempio "/foo/default.aspx". Ancora una volta, non reale scalabile e anche non reale bella.

Una soluzione migliore sarebbe quella di utilizzare la riscrittura URL o in altre parole, impostare la modalità di risposta system.webServer a "ExecuteURL" e gli errori personalizzati reindirizzano alla modalità "ResponseRewrite". Questo sarà quindi mostrare la pagina di errore sul URL richiesto senza alcun reindirizzamento di sorta. Se siete preoccupati per le implicazioni SEO di un HTTP 200 che mostra una pagina di errore, si può sempre cambiare il codice di risposta nella pagina di errore in sé.

Anche in questo caso, è più aggiustamenti, ma che l'ultima opzione è probabilmente la scelta migliore se hai una dipendenza sulla presenza di documenti predefiniti in cartelle.

Riassunto
Se io sono onesto, tutto questo è un sacco di gingillarsi per molto poco beneficio fine. Persone caritatevoli Meno di me lo chiamerei "teatro di sicurezza" e nello spettro di rischi potenzialmente sfruttabili, questo è il modo, in fondo in fondo.

Ma niente di tutto questo cambia il fatto che strumenti di sicurezza e squadre ritengono che questo sia un rischio e solleva una bandiera ed è necessario per risolvere il problema. Che in ultima analisi, può essere fissato molto facilmente (ma solo perché ora avete la strada tracciata per voi!) e tramite la configurazione solo è un buon motivo sufficiente per farlo e andare avanti.

Oh, e nel caso in cui si dispone di un mezzo più efficiente per farlo sia con configurazione o codice, lasciate un commento con alcuni consigli. Questo è il genere di cosa che la gente inevitabilmente Google la loro strada nel più facile e siamo in grado di rendere la vita su coloro che seguono, il migliore.

Giusto per rendere più reale facile, ecco tutte le config in un colpo solo:

< system.web >
  < customErrors >
    < errore statusCode = " 404 " redirect = " / Error / PageNotFound? foo = bar " />
  </ customErrors >
</ system.web >
< system.webServer >
  < riscrittura >
    < outboundRules >
      < regola nome = " Modifica intestazione posizione " patternSyntax = " ExactMatch " >
        < partita serverVariable = " RESPONSE_location " modello = " / Error / PageNotFound? foo = bar " />
        < azione di tipo = " Rewrite " value = " / Error / PageNotFound " />
      </ regola >
    </ outboundRules >
  </ riscrittura >
  < httpErrors ErrorMode = " Personalizzato " >
    < errore statusCode = " 403 " subStatusCode = " 14 " percorso = " / Error / PageNotFound " responseMode = " Redirect " />
  </ httpErrors >
  < defaultDocument abilitato = " true " />
</ system.webServer >
Non, non ti senti più sicuro adesso? :)

Nessun commento:

Posta un commento