"Non esiste una patch per la stupidità" http://www.sqlsecurity.com ASP.NET Security Raffaele Rialdi MVP.NET [email protected] http://mvp.support.microsoft.com MVP Profile: http://snipurl.com/7vyf Agenda Spoofing / Tampering Spying Sql/script Injection D.O.S. Autenticazione ("chi sei?") • • • • Anonymous Basic Windows Certificate • • • • None Windows Forms Passport Autorizzazione ("cosa posso fare?") IIS • NTFS Access Control List (ACL) Asp.net .aspx, .asmx, .asax, .ascx, .soap, .rem, ... Web Application NTFS LDAP • UrlAuthorizationModule • FileAuthorizationModule • Imperative • Declarative SQL Meccanismi di Autenticazione IIS Anonima Basic Digest Certificate Windows Asp.net Passport Forms IIS mappa l'utente su IUSR_nomemacchina Asp.net lo vede come "" Utente non viene riconosciuto anche se è in Lan Usano lo store delle credenziali di Windows Richiedono una CAL per ogni utente La password non viaggia sulla rete Credenziali in chiaro (necessita SSL) Windows Authentication step-by-step 1. Web.config di default è pronto: <authentication mode="Windows" /> 2. Impostare le autorizzazioni <authorization> <deny users="?" /> <allow users="*" /> </authorization> 3. ? utente anonimo * tutti gli utenti Disabilitare l'autenticazione anonima in IIS ... (prossime slide) 4. L'utente autenticato è: (stringa vuota se anonimo) HttpContext.Current.User.Identity.Name 5. L'utente usato dal worker process è: System.Security.Principal.WindowsIdentity.GetCurrent().Name Internet Information Server IIS5 (Windows 2000 / XP Pro) Internet Information Server IIS6 (Windows 2003) IIS6 Application pool Impersonation (solo con Windows Authentication) Il token di security a livello di thread viene sostituito con quello dell'utente autenticato. Il token di processo rimane invariato. Se l'utente è anonimo, viene impersonato IUSR_NomePc Sintassi (web.config): <identity impersonate=true /> IIS5 non può eseguire più worker process sotto identità diverse Soluzione: impersonation di un utente specifico <identity impersonate=true user="xxx" password="zzz" /> I problemi architetturali di Impersonation Molti vogliono usare la security di Sql server Se il db è in rete, impersonation non funziona ma ci vuole invece delegation Si perde il controllo centralizzato della security (accedere a Ntfs, Ldap, risorse in rete, DB) La security 'per righe' fatta con sql server è un incubo I problemi tecnologici di Impersonation Il token dell'utente non può essere usato per accedere a risorse remote (per es. la webapp non può usarlo per accedere un db in rete) La soluzione viene con Delegation che è di default disabilitata (proprio perchè è pericolosa!) Impersonation implica contesti diversi per ciascun utente. Questo significa niente connection pooling Protezione limitata. Un eventuale buffer overrun può usare sia il token di thread (impersonato) che quello di processo (worker process) usando RevertToSelf. Se chiamo un componente COM che sta in un apartment diverso, COM non userà il token di impersonazione ma quello di processo Forms Authentication step-by-step 1. 2. Abilitare l'autenticazione anonima in IIS Impostare l'autenticazione e i suoi parametri <authentication mode="Forms"> <forms name="myCookieName" loginUrl="~/Login.aspx" /> </authentication> 3. Impostare le autorizzazioni <authorization> <deny users="?" /> <allow users="*" /> </authorization> 4. ? utente anonimo * tutti gli utenti Creare la pagina di login controllare l'utente e autorizzarlo if(UserDB.Check(txtUsername.Text, txtPassword.Text)) { FormsAuthentication.RedirectFromLoginPage( txtUsername.Text, ckRemember.Checked); } Forms Authentication gestire i ruoli 1. Gestire l'evento Application_AuthenticateRequest protected void Application_AuthenticateRequest(Object sender, EventArgs e) { UserDB.AssignRoles(); } 2. Impostare le autorizzazioni per singole parti del sito <location path="Backoffice.aspx"> <system.web> <authorization> <deny users="?" /> <allow roles="admins" /> <deny users="*" /> </authorization> </system.web> </location> L'ordine di valutazione delle autorizzazioni è dal primo verso l'ultimo. Il primo 'match' vince. Forms Authentication Gestione utenti e ruoli Si costruisce una piccola classe: public class UserDB { public static bool CheckUser(string Username, string Password) { return (Username == Password);// Solo per la demo!!! ;-) } public static void AssignRoles() { IPrincipal CurrentUser = HttpContext.Current.User; if(CurrentUser != null && CurrentUser.Identity.IsAuthenticated && CurrentUser.Identity.AuthenticationType == "Forms") { string User = CurrentUser.Identity.Name; string [] roles = GetRolesForUser(User); CurrentUser = new System.Security.Principal.GenericPrincipal (CurrentUser.Identity, roles); } } private static string[] GetRolesForUser(string User) { string[] roles = new string[2]; roles[0] = "Administrators"; roles[1] = "Users"; return roles; // Solo per la demo! } } Principal e Identity La sicurezza basata sui ruoli secondo il framework IIdentity rappresenta l'identità di un utente WindowsIdentity, FormsIdentity, PassportIdentity, GenericIdentity AuthenticationType String. "Windows", "Forms", "Passport", ... IsAuthenticated Bool. Indica se l'utente è autenticato String. Nome dell'utente Name IPrincipal contiene l'Identity e i ruoli WindowsPrincipal, GenericPrincipal Identity IIdentity. IsInRole Bool. Indica se l'utente appartiene ad un certo ruolo (gruppo) Forms Authentication Gestione utenti Gli utenti si possono anche gestire nel web.config ma è sconsigliato: <authentication mode="Forms"> <forms name="myCookieName" loginUrl="~/Login.aspx"> <credentials passwordFormat = "SHA1" <user name="UserName1" password="SHA1EncryptedPassword1"/> </credentials> </forms> </authentication> Esempio Forms Authentication Forms Authentication Tip Diciamo di avere due Web Application ... Prendiamo in considerazione: 1. Nome del cookie della Forms authentication 2. Path del cookie 3. Il tag <machineKey ... /> nel web.config (vedi http://support.microsoft.com?id=312906) Se sono identici, l'utente potrà navigare da una all'altra senza doversi ri-autenticare Se almeno uno di questi è diverso, sarà necessario ri-autenticarsi Autenticazione mista Windows / Forms Il problema: In Windows Authentication, l'header HTTP "LOGON_USER" contiene il nome utente Se IIS è configurato come anonimo, NON viene passato il nome utente anche se siamo loggati sul dominio ... Ma la Forms authentication richiede che IIS sia configurato come anonimo (altrimenti compare la dialog di autenticazione) Autenticazione mista Windows / Forms La soluzione: Due pagine di Login: Forms e Windows Web.config configurato per la Forms Autorizzazione a tutti per la pagina di Login Windows IIS – WebApp: abilitare accesso anonimo IIS – LoginWin.aspx: togliere accesso anonimo LoginWin.aspx: Crea il ticket della Forms authenticaion a partire dalle credenziali Windows <location path="LoginWin.aspx"> <system.web> <authorization> <allow users="*" /> </authorization> </system.web> </location> Esempio Autenticazione mista Forms Authentication con LDAP LDAP è un protocollo per dialogare con Active Directory Posso chiedere con LDAP: di verificare le credenziali di un utente su AD di darmi l'elenco dei gruppi a cui appartiene quell'utente Il codice per fare queste due cose è qui: http://support.microsoft.com/?id=326340 Metodo 1: Public Function IsAuthenticated(ByVal domain As String, ByVal username As String, ByVal pwd As String) As Boolean Metodo 2: Public Function GetGroups() As String Un ottimo motivo per usarla è nelle WebApp con autenticazione mista Windows + Forms Dove siamo? Autenticazione IIS Basic, Win, ... Autorizzazione IIS NTFS Controllo Imperativo Controllo dichiarativo Asp.net Passport, Form Asp.net <authorization ... /> Asp.net PrincipalPermission, etc. Raffaele Pagina si/no Codice si/no Sicurezza imperativa e dichiarativa Gli attrezzi del mestiere: IPrincipal.IsInRole() Imperativa (bool) if(User.IsInRole("Admins")) { ... } PrincipalPermission.Demand() Imperativa (SecurityException) PrincipalPermission perm = new PrincipalPermission(null, "Admins"); perm.Demand(); PrincipalPermissionAttribute Dichiarativa (SecurityException) PrincipalPermissionAttribute [PrincipalPermission(SecurityAction.Demand, Role="Admins")] public void MyAdminMethod() {...} Esempio SecureHandler Role Based Authorization Mai dare informazioni preziose default: <customErrors mode="RemoteOnly" /> Qualsiasi informazione sugli errori può essere sfruttata da un hacker. Gli errori custom (che nascondono i dettagliati): mode="Off" mostrati a nessuno mode="On" mostrati a tutti mode="RemoteOnly" solo in remoto Questo meccanismo è poco elastico Possiamo usare un HttpModule per migliorare la situazione .... CustomErrorHandler (esempio) 1. Web.Config: <customErrors mode="On" defaultRedirect="~/HttpErrors.aspx" /> .... <httpModules> <add type="CustomErrorHandler.RafErrorModule, CustomErrorHandler" name="RafErrorModule"/> </httpModules> 2. Due pagine di gestione errore: SoftError.aspx (per utenti) e HardError.aspx (per admin) 3. Il Module redirige gli errori a seconda del ruolo dell'utente 4. Il Module gestisce gli errori Http e le Exception ... vediamo il codice ... Esempio CustomErrorHandler SQL Injection Un pirata può devastare il db ... string strSql = "Select * from authors where au_lname like '" + TextBox1.Text + "'"; SqlCommand cmd = new SqlCommand(strSql, Cnn); SqlDataReader dr = cmd.ExecuteReader(); Select * from authors where au_lname like ' ' Prima query ; drop authors -- ' Seconda query Scartato SQL Injection ... usare i parameters!!! ... I Parameters incrementano anche le performance: non c'è conversione da string a tipo sul db la query rimane compilata e preparata sul db server string strSql = "Select * from authors where au_lname like @au_lname"; SqlCommand cmd = new SqlCommand(strSql, Cnn); cmd.Parameters.Add("@au_lname", SqlDbType.VarChar,40); SqlDataReader dr = cmd.ExecuteReader(); exec sp_executesql N'Select * from authors where au_lname like @au_lname', N'@au_lname varchar(40)', @au_lname = ' ' ' ; drop authors - - ' apice raddoppiato da ADO.NET Gli apici non sono l'unico problema: select * from titles where royalty = 0 ; drop authors XSS: Cross Site Scripting From: Hacker To: Raffaele Subject: Free gift Click here to win 1. Normale navigazione 2. Email con link contenente un attacco XSS 3. Il link effetua una GET sul sito della banca con la QueryString 4. La banca (non protetta da XSS) restituisce nella pagina html lo script inviato 5. Lo script viene eseguito dal browser e le informazioni riservate arrivano al pirata Contromisura di asp.net 1.1 <%@ Page validateRequest="true" %> (true by default) Riconosce un eventuale input malizioso dell'utente e lancia l'eccezione HttpRequestValidationException Se però voglio accettare una stringa html/script dall'utente? Opzione 1: validateRequest = false (vale per tutta la pagina) e validarla. Opzione 2: criptare sul client, decrittarla sul server e validarla. 1. 2. 3. Sul client (durante la onsubmit) si cripta il contenuto con encode di javascript il contenuto criptato si mette dentro un <input type=hidden> sul server si usa HttpUtility.UrlDecode per decodificare la stringa La validazione ... si può usare Server.HtmlEncode per farlo apparire sulla pagina si può fare il parsing per eliminare i tag pericolosi <Input type=hidden /> Spesso viene usato un campo hidden per conservare i dati tra un postback e l'altro La modifica (tampering) dei campi hidden è banale e, se non controllata adeguatamente, può comportare un duro attacco. Soluzione: Criptarli prima di mandarli al client Decrittarli dentro un try/catch quando tornano al server Protezione del Viewstate Il Viewstate di default è un campo <input type=hidden /> Il Viewstate contiene lo stato dei controlli sul lato server Se non è criptato, è facilmente visibile: http://www.pluralsight.com/toolcontent/ViewStateDecoder11.zip Soluzioni: 1. Criptarlo: <%@ Page enableViewStateMac=“true” /> Mac = machine authentication check 2. La chiave e il metodo di encryption sono specificati nel tag <machineKey> del machine.config (autogenerazione) Per crearne e specificarne di nuovi nel web.config: Q312906 Salvarlo sul server: http://www.aspalliance.com/articleViewer.aspx?aId=72&vId=&pId= http://msdn.microsoft.com/msdnmag/issues/03/02/CuttingEdge/default.aspx Proteggere le risorse Molti file non devono poter essere scaricati via http dall'utente Nel machine.config Asp.net protegge di default alcuni tipi di file dal download (.cs, .config, ...) Soluzione 1: custodirlo fuori dalla cartella virtuale Soluzione 2: proteggere il file via NTFS (se si usa impersonation.) Soluzione 3: proteggere con asp.net Associare i file da proteggere in IIS all'Isapi di Asp.net Proteggere (ad esempio) i file mdb nel web.config: <httpHandlers> <add verb="*" path="*.mdb" type="System.Web.HttpForbiddenHandler" /> </httpHandlers> Mappare una estensione in IIS Affinchè asp.net (e quindi handlers e moduli) abbiano il controllo di un tipo di file (.jpg nell'esempio) è necessario configurare IIS Path dell'isapi di asp.net (copiarla da quella di .aspx) Q&A ....