posts - 644, comments - 2003, trackbacks - 137

My Links

News

Raffaele Rialdi website

Su questo sito si trovano i miei articoli, esempi, snippet, tools, etc.

Archives

Post Categories

Image Galleries

Blogs

Links

Linq Injection – be careful!

Durante la mia sessione di ieri su WIF (Windows Identity Foundation) ai CommunityDays.it che si stanno concludendo questa sera, ho mostrato la Claim Based Authentication in azione.

La versatilità di un claim sta nel fatto che possiede un valore arbitrario e non un valore booleano come per i ruoli. Questa versatilità implica una discrezionalità nel modo in cui si vuole utilizzare il valore. Un Claim può rappresentare, per esempio, il valore dell’età oppure l’affidabilità di una persona o ancora delle coordinate GPS.

La discrezionalità di cui parlavo è la funzione utilizzata per valutare questi criteri, cioè il filtro. I criterio per valutare la valutazione dell’età, l’affidabilità e la locazione geografica sono naturalmente molto differenti e application-specific.

Il “valutatore” nella Claim Based Authentication è una nostra classe che deriva da ClaimsAuthorizationManager. I criteri di valutazione possono essere “cablati” nel codice oppure caricati da un elemento di configurazione chiamato “policy”.

In un esempio ho mostrato quanto sia versatile scrivere una lambda testuale dentro l’elemento policy, lambda che poi viene compilata al volo e mantenuta in una cache locale per essere eseguita ogni volta che è necessario valutare un Claim.

Il codice che compila una lambda “on the fly” è il seguente:

   1: public sealed class LambdaCompiler<T>
   2: {
   3:     private Dictionary<string, T> _expressionCache;
   4:     private List<string> _compilerOutputs;
   5:  
   6:     public IList<string> LastCompilerOutputs
   7:     {
   8:         get { return _compilerOutputs; }
   9:     }
  10:  
  11:     public LambdaCompiler()
  12:     {
  13:         _expressionCache = new Dictionary<string, T>();
  14:     }
  15:  
  16:     private string GetTemplateClass(string expression)
  17:     {
  18:         if (!expression.EndsWith(";"))
  19:             expression += ";";
  20:  
  21:         StringBuilder sb = new StringBuilder();
  22:         sb.AppendLine("class ExpressionContainer {");
  23:         sb.AppendLine("    public " + GetNameFromType(typeof(T)) + " Expression {");
  24:         sb.AppendLine("    get { return " + expression + " } } }");
  25:         return sb.ToString();
  26:     }
  27:  
  28:     private string GetNameFromType(Type t)
  29:     {
  30:         if (t.IsGenericType)
  31:         {
  32:             string name = t.Name;
  33:             string displayName = t.Namespace + "." + name.Substring(0, name.IndexOf('`'));
  34:             string paramList = string.Join(",", (from a in t.GetGenericArguments() select a.FullName).ToArray());
  35:             return string.Format("{0}<{1}>", displayName, paramList);
  36:         }
  37:         return t.FullName;
  38:     }
  39:  
  40:     private IEnumerable<string> GetInMemoryAssemblies()
  41:     {
  42:         List<string> assemblies = new List<string>();
  43:         foreach (var alreadyInMemory in AppDomain.CurrentDomain.GetAssemblies())
  44:         {
  45:             try
  46:             {
  47:                 assemblies.Add(alreadyInMemory.Location);
  48:             }
  49:             catch (Exception) { }
  50:         }
  51:         return assemblies;
  52:     }
  53:  
  54:     public T GetLambda(string lambdaText)
  55:     {
  56:         T lambda;
  57:         if (_expressionCache.TryGetValue(lambdaText, out lambda))
  58:             return lambda;
  59:  
  60:         try
  61:         {
  62:             Assembly compiledAssembly;
  63:             string source = GetTemplateClass(lambdaText);
  64:             using (CSharpCodeProvider provider = new CSharpCodeProvider())
  65:             {
  66:                 var assemblies = GetInMemoryAssemblies();
  67:                 var parameters = new CompilerParameters(assemblies.ToArray());
  68:                 parameters.GenerateExecutable = false;
  69:                 parameters.GenerateInMemory = true;
  70:  
  71:                 CompilerResults results = provider.CompileAssemblyFromSource(parameters, source);
  72:  
  73:                 if (results.Errors.HasErrors)
  74:                 {
  75:                     _compilerOutputs = results.Errors.OfType<CompilerError>().Select(s => s.ErrorText).ToList();
  76:                     return default(T);
  77:                 }
  78:  
  79:                 compiledAssembly = results.CompiledAssembly;
  80:                 _compilerOutputs = (from string s in results.Output select s).ToList();
  81:             }
  82:  
  83:             object o = compiledAssembly.CreateInstance("ExpressionContainer");
  84:             var expression = (T)o.GetType().GetProperty("Expression").GetValue(o, null);
  85:             if (expression == null)
  86:             {
  87:                 _compilerOutputs.Add("Error: result is null instead of a type " + GetNameFromType(typeof(T)));
  88:                 return default(T);
  89:             }
  90:  
  91:             _expressionCache.Add(lambdaText, expression);
  92:             return expression;
  93:         }
  94:         catch (Exception err)
  95:         {
  96:             _compilerOutputs = new List<string>() { err.ToString() };
  97:             return default(T);
  98:         }
  99:     }
 100: }

Per utilizzare questo codice, vediamo un esempio triviale. Diciamo di volere scrivere in modo testuale una funzione che torni true solo se il valore intero in input è maggiore di 10. Nel caso dei Claim si utilizzerebbe invece una Func<IClaimsPrincipal, bool>.

   1: var filters = new LambdaCompiler<Func<int,bool>>();
   2: var lambda = filters.GetLambda("c => c > 10;");
   3: Console.WriteLine(lambda(7));
   4: Console.WriteLine(lambda(15));

 

Sembra una soluzione riutilizzabile in una moltitudine di scenari e invece, se utilizzata male, può creare la peggiore delle vulnerabilità, peggio ancora della SQL Injection.

Siamo partiti dalla Claim Based Authentication, con il tag policy che contiene i criteri per esprimere il modo in cui valutare un Claim. Per la cronaca WIF ci lascia totale discrezionalità nel gestire il formato e il contenuto delle policy. In alcuni esempi di WIF le policy sono costruite con un expression tree partendo da una sorta di serializzazione con <and> e <or> di condizioni. Io invece ho mostrato la compilazione al volo di una lambda … essenzialmente la stessa cosa.

Guardiamo tutto questo dal punto di vista della sicurezza:

  1. Sappiamo da sempre che mettere una password in un web.config è sbagliato. Il tag policy andrebbe perciò protetto con DPAPI al pari di una password, considerato che è il criterio per le autorizzazioni dell’applicazione
  2. Usando lambda compilate o comunque expression tree serializzati in qualsivoglia modo, bisogna stare attenti a validare sempre ogni parametro che dovesse essere iniettato dentro la stringa di testo.

Il secondo punto l’ho chiamato Linq Injection. Pensate a cosa potrebbe significare accettare ad una pagina web un parametro utente, concatenarlo nella lambda, compilarlo ed eseguirlo. Potenzialmente potrei fare qualsiasi diabolica cosa mi venisse in mente.

Per questo motivo queste soluzioni apparentemente brillanti e “generiche” sono dei boomerang clamorosi e il loro beneficio è totalmente offuscato dal potenziale rischio nel loro utilizzo.

Prevenire è molto meglio che curare …

Print | posted on venerdì 17 dicembre 2010 18:05 |

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET