Alkampfer's Place

Il blog di Gian Maria Ricci
posts - 659, comments - 871, trackbacks - 80

My Links

News

Gian Maria Ricci Mvp Logo CCSVI in Multiple Sclerosis

English Blog

Tag Cloud

Article Categories

Archives

Post Categories

Image Galleries

I miei siti

Siti utili

Validare sempre gli input dell’utente

Nel precedente post ho mostrato come cifrare la querystring per rendere sicuro il passaggio di parametri. Chiaramente le best practice prevedono che OGNI parametro passato dall'utente debba essere validato, sia quelli in querystring che quelli in Post. Nella rivista MSDN di marzo Dino Esposito mostra una possibile tecnica decisamente valida, io vorrei postarne una alternativa. Prima di tutto definisco una interfaccia per un generico validatore:

public interface IParamValidator {
   
Boolean Validate(String value);
}

Dato che i parametri provengono da form o da querystring il loro valore verrà passato come stringa, per cui un validatore per me è un oggetto che accetta una stringa e mi dice se è valida o meno. A questo punto creo un oggetto che invece mantiene un set di regole.

public class PageInputRules {
 
   
public static PageInputRules AllowAll = new PageInputRules(true);
 
   
private Dictionary<StringIParamValidator> mRules
      = 
new Dictionary<stringIParamValidator>();
 
   
public Dictionary<StringIParamValidator> Rules {
      
get { return mRules; }
      
set { mRules = value; }
   }
 
   
private readonly Boolean mPermitUnspecifiedParameter;
   
public PageInputRules() : this(false) { }
 
   
public PageInputRules(Boolean permitUnspecifiedParameter) {
      mPermitUnspecifiedParameter = permitUnspecifiedParameter;
   }
 
   
public void AddRule(String parameterName, IParamValidator validator) {
      mRules.Add(parameterName, validator);
   }
 
   
/// <summary>
   
/// Validate all querystring and form parameter
   
/// </summary>
   
/// <param name="querystring"></param>
   
/// <param name="form"></param>
   
/// <returns></returns>
   
public Boolean Validate(NameValueCollection querystring, NameValueCollection form) {
      
if (!ValidateNameValueCollection(querystring)) return false;
      
if (!ValidateNameValueCollection(form)) return false;
      
return true;
   }
 
   
private bool ValidateNameValueCollection(NameValueCollection querystring) {
      
foreach (String key in querystring.Keys) {
         
if (!key.StartsWith("__")) {
            
if (mRules.ContainsKey(key)) {
               
if (!mRules[key].Validate(querystring[key]))
                  
return false;
            }
            
else {
               
//I have no rule for this parameter
               
if (!mPermitUnspecifiedParameter) return false;
            }
         }
      }
      
return true;
   }
}

La classe tiene un dictionary di coppie (nomeparametro,validatore), permette di specificare se vogliamo o meno controllare la presenza di parametri non specificati e presenta una normale funzione Validate che accetta la querystring ed i parametri di post e li sottopone uno ad uno a validazione. Il costruttore di questo oggetto accetta un boolean che indica se ammettere parametri senza regola, per una difesa completa ogni parametro non espliticamente permesso dovrebbe generare un errore per questa ragione bisognerebbe sempre usare il costruttore di default. Naturalmente tra le variabili di post c'è il __VIEWSTATE per questa ragione il validatore ignora ogni variabile di post che inizia con il doppio underscore. Ora bisogna creare qualche classe concreta per i validatori, la prima è una basata su regular expression.

public class RegexInputValidator : IParamValidator {
   
private Regex mRegex;
 
   
public RegexInputValidator(String regularExpression) {
      mRegex = 
new Regex(regularExpression);
   }
 
   #region IParamValidator Members
 
   
public bool Validate(string value) {
      
return mRegex.IsMatch(value);
   }
 
   #endregion
}

Veramente banale, a questo punto io ho:

  • l'interfaccia,
  • un validatore corretto,
  • un oggetto che incorpora le regole per tutti i parametri,

mi rimane solamente da capire come e dove utilizzarli. Una prima tecnica è quella di permettere ad ogni pagina di specificare il set di parametri accettabili, per far questo si crea una pagina base da cui tutte le pagine erediteranno (tecnica abbastanza utile in generale anche per altre cose) e si inserisce il seguente codice.

protected override void OnInit(EventArgs e) {
   
base.OnInit(e);
   
PageInputRules rules = GetPageInputRules();
   
if (!rules.Validate(Request.QueryString, Request.Form))
      
throw new SecurityException();
}
 
protected virtual PageInputRules GetPageInputRules() {
   
return PageInputRules.AllowAll; 
}

Le pagine possono ignorare il sistema di validazione perché nella pagina base la GetPageInputRules torna un set di regole statico che permette ogni tipologia di valore, ma se una pagina vuole ad esempio ammettere un solo parametro chiamato mailaddr e verificare che sia un indirizzo mail ben formato può fare questo

public partial class Validator : BasePage {
 
   
protected override FileDownload.Validation.PageInputRules GetPageInputRules() {
      
PageInputRules rules = new PageInputRules();
      rules.AddRule(
"mailaddr"new RegexInputValidator(@"[\w-]+@([\w-]+\.)+[\w-]+"));
      
return rules;
   }

LA pagina eredita da quella base e fa override della GetPAgeInputRules restituendo un set di regole che ammette il solo parametro mailaddr. Per migliorare le prestazioni sarebbe meglio cachare questo set di regole invece di ricrearle ad ogni chiamata, la modifica è lasciata al lettore :D. Provate ora a chiamare la pagina tentando di passare strani parametri nella querystring e vedrete che la pagina accetta solamente un parametro chiamato mailaddr che soddisfi la regular expression specificata.

Questa tecnica è semplice, ma potente perché è possibile creare i propri IParamValidator senza nessun limite. Se io avessi ad esempio un singolo sito che gestisce il business di più clienti, vorrei ad esempio che quando viene specificato in post un id di un prodotto ci sia un controllo per verificare se quell'id è veramente di quel cliente o di un altro. Per questa ragione posso farmi un validatore particolare che effettua una query al database per controllare se effettivamente l'id di un prodotto è del cliente corrente (il cliente lo si mette ad esempio in sessione). Con il pattern Composite è facile creare un AndParamValidator oppure un OrParamValidator che permettono di combinare tra di loro più validatori per fare regole complesse. In sostanza in questo modo ci si può sbizzarrire creando ogni sorta di validatore e blindare i parametri in ingresso. Naturalmente molto può essere fatto anche con i validatori standard di Asp.Net che effettuano la validazione anche lato server, ma ad esempio non sono validi per la querystring e preferisco lasciarli solamente per validazioni non di sicurezza.

A chi non piace l'intrusione di questa tecnica nelle pagine (è necessario infatti fare una pagina base da cui tutte le pagine erediatno e poi è necessario fare override di un metodo) nei prossimi giorni mostrerò come utilizzare un motore di Inversione di Controllo (castle) per fare un modulo che effettua questa validazione in modo completamente trasparente, prendendo la definizione delle regole di validazione direttamente da un file xml di configurazione.

Alk.

Print | posted on martedì 9 ottobre 2007 11:55 | Filed Under [ Security ]

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET