lunedì 14 ottobre 2024

Parallel Linq "troppo" veloce ?!?

 PLINQ (parallel linq)  permette la gestione di operazioni su insiemi di oggetti su più core  con un notevole vantaggio nei tempi di esecuzione , qui un introduzione alla tecnologia PLinq , ho fatto qualche prova ed i risultati portano a credere che il test stesso andrebbe condotto in altro modo , ho caricato 1000000 di record nella tabella Products e poi ho fatto una select sulle righe prima con Linq "semplice" e poi con ParallelLinq .La macchina utilizzata ha un processore i7-11370H , 4 cpu cores ed 8 "cpu logiche" ,disco ssd .

Ho  aggiunto 1 milione di record nella tabella Products di un db Northwind , ho creato un progetto Asp .net Mvc , ed ho utilizzato la possibilità "Database First" (con il comando Scaffold-DbContext) per creare nel progetto Asp .net la struttura di oggetti che rispecchi quella tabellare di Northwind per poter utilizzare Linq e ParallelLinq .Segue lo snippet per caricare la tabella :

public class Util
{        
    public Util() { }

    public void CaricaDB() {
        SqlConnection sqlConnection = null;
        string str = "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=Northwind;"
             + "Integrated Security=SSPI";
        sqlConnection = new SqlConnection(str);
        sqlConnection.Open();
        int k= 1000000;
        int j= 2000000;
        while(k<j)
        {
            string sql = "INSERT INTO Products (ProductName) values ('ProductName" + k.ToString() + "'" + ")";
            SqlCommand cmd = new SqlCommand(sql, sqlConnection);
            cmd.ExecuteNonQuery();                
            k++;
        }
        sqlConnection.Close();
    }    
}

lo snippet può essere chiamato in un app console :

internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("inizio caricamento");
        Util u=new Util();
        u.CaricaDB();
        Console.WriteLine("fine caricamento");
    }
    
}

a questo punto è possibile provare le query sulle collezioni di oggetti , creiamo un controller Products ed aggiungiamo un metodo (un action) di test ,di nome Test ( :-) ) 

public async Task<IActionResult> Test()
{
    DateTime dt1 = DateTime.Now;
    //var list = from p in _context.Products.AsParallel().WithDegreeOfParallelism(2) where p.ProductName.Contains("%0%") select p;
    var list = from p in _context.Products.AsParallel() where p.ProductName.Contains("%0%") select p;
    //var list = from p in _context.Products where p.ProductName.Contains("%0%") select p;
    DateTime dt2 = DateTime.Now;
    TimeSpan span = dt2.Subtract(dt1);
    ViewData["Milliseconds"] = span.TotalMilliseconds;
    return View("Test");
}

segue la view Test.cshtml :

@{
    ViewData["Title"] = "Test";
}
<h1>Test</h1>
Milliseconds: @ViewData["Milliseconds"];


 
i due Datetime.Now racchiudono le chiamate che caricano i dati , la prima query permette di specificare il numero di processori utilizzati passando il numero stesso al metodo WithDegreeOfParallelism , in questo caso 2 .
Ho testato le altre due modalità di usare linq e plinq ,
la seconda è from p in _context.Products.AsParallel() where p.ProductName.Contains("%0%") select p;
in questo modo PLinq cerca di usare tutti i core della macchina , 

la terza "query" utilizza Linq (non parallel) :
from p in _context.Products where p.ProductName.Contains("%0%") select p;

ho eseguito 10 volte la seconda query (Plinq) in visual studio eseguendo il progetto (non in debug) ed ho ottenuto :

0,8038
1,2417
1,7415
0,653
0,4739
0,5141
1,0046
0,6074
0,5183
0,9088

i risultati sono in millisecondi




ho eseguito 10 volte la seconda query (Linq) in visual studio eseguendo il progetto (non in debug) ed ho ottenuto :

836,0055 
978,9648
450,3928
977,5208 
435,4028
516,4756
535,6739
855,7413
648,2701
488,9144


Plinq sembrerebbe centinaia di volte più veloce , evidentemente qualcosa non và nei test... , se avete suggerimenti , critiche ,segnalazione di errori, potete scrivere a gianmarco.castagna@gmail.com


domenica 29 settembre 2024

Crittografia e WCF per passare una password ( od una qualsiasi altra stringa (xml,json, etc.etc.) ) da un applicazione ad un' altra in ,relativa, sicurezza

 Il codice che segue è da considerarsi in alpha e da non utilizzare in un ambiente di produzione , qui potete trovare il  "progetto" https://sourceforge.net/projects/sourcecodesafer/

L'ipotetico scenario è quello in cui vogliamo lasciar testare la nostra applicazione .Net Windows Forms ma vogliamo evitare che sia possibile decompilarla in fase di test ,

per ottenere questo risultato crittografiamo sul nostro pc l'eseguibile da proteggere con una password (chiamiamola P , si tratta di una password simmetrica(https://it.wikipedia.org/wiki/Crittografia_simmetrica) ) ,quando ci spostiamo sulla macchina di test decrittografiamo l'eseguibile con la stessa password e lo carichiamo ed eseguiamo grazie alla reflection ,

sulla macchina di test portiamo quindi l'eseguibile crittografato , se  ci venisse carpita la password P sulla macchina di test si potrebbe decrittografare l'eseguibile e risalire al codice sorgente ,per evitare che ciò accada dobbiamo evitare che possibili keylogger intercettino lo stream della tastiera o/e che uno screen recorder registri la nostra attività sulla tastiera virtuale dell'applicazione , per risolvere questa situazione potremmo utilizzare una seconda applicazione che in remoto fornisca la password P tramite un servizio WCF ( questa è ovviamente solo una delle soluzioni possibili ). Chiamando questo servizio forniamo una password asimmetrica pubblica ( https://it.wikipedia.org/wiki/Crittografia_asimmetrica ) , quest'ultima password viene utilizzata per crittografare la password P , l'array della password così crittografata viene restituito al chiamante il quale ha la chiave privata e può risalire alla password P in chiaro .

Questo "progetto" è costituito da 3 sotto-progetti :

1) un eseguibile Ethical_Hacking.exe che è l'applicazione che vogliamo non sia decompilata in fase di test

2) un applicazione WindowsFormsProtector che si occupa di :

   a) crittografare Ethical_Hacking.exe

   b) decrittografare Ethical_Hacking.exe

   c) creare una coppia di chiavi asimmetriche e chiamare il servizio WCF di  PasswordSupplier passando la chiave pubblica ed ottenendo da PasswordSupplier  la password P crittografata e decrittografarla con la chiave privata quando è ritornata dal metodo GetPassword della classe Service di PasswordSupplier      

   d) caricare in memoria ed eseguire Ethical_Hacking.exe partendo da un array di bytes ( ovvero Ethical_Hacking.exe decrittografato )

 3)  l'applicazione PasswordSupplier che si occupa di :

   a) scegliere l'indirizzo a cui sarà reso disponibile il servizio WCF

   b) consentire di inserire la password ,dovrà essere la stessa inserita al punto 2)a)

   c) rendere disponibile (Start) il servizio all'indirizzo scelto in 3)a)

   d) fermare (Stop) il servizio avviato in 3)c)

   e) fornire l'implementazione di GetPassword  prevista dall'interfaccia IService


Segue il dettaglio dei singoli punti :

1) Ethical_Hacking.exe è un eseguibile Windows Forms si tratta di un form che ne chiama un altro,la sua funzione è solo quella di essere un eseguibile .net chiamabile tramite reflection 

2) vediamo il codice relativo al punto 2)a)

    private void button_encrypt_Click(object sender, EventArgs e)
        {
            try { 
            byte[] encrypted = null;
            string file = this.textBox1.Text;
            string pwd = this.textBox2.Text;
            if (!file.Equals(""))
            {
                    if (pwd.Length > 7)
                    {
                        FileStream fs = new FileStream(this.textBox1.Text, FileMode.Open);
                        BinaryReader br = new BinaryReader(fs);
                        byte[] bin = br.ReadBytes(Convert.ToInt32(fs.Length));
                        fs.Close();
                        br.Close();
                        string EncryptionKey = pwd;
                        byte[] clearBytes = bin;
                        Aes encryptor = Aes.Create();
                        Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[]
                        { 0x20,0x6e, 0x20, 0x61, 0x6e, 0x20, 0x4d, 0x65,0x20, 0x61, 0x64, 0x76,0x65});
                        encryptor.Key = pdb.GetBytes(32);
                        encryptor.IV = pdb.GetBytes(16);
                        MemoryStream ms = new MemoryStream();
                        CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(),
                              CryptoStreamMode.Write);
                        cs.Write(clearBytes, 0, clearBytes.Length);
                        cs.Close();
                        encrypted = ms.ToArray();
                        FileStream fs1 = new FileStream(this.folderName + "\\encryptedExe.encrypt", FileMode.Create);
                        BinaryWriter br1 = new BinaryWriter(fs1);
                        br1.Write(encrypted);
                        fs1.Close();
                        br1.Close();
                    } else
                    {
                        MessageBox.Show("inserire una password di almeno 8 caratteri");
                    }       
            }          
            else {
                    MessageBox.Show("inserire il nome di un file");      
                }
            } catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

       }        


-segue il codice relativo al punto 2)b) 

                string EncryptionKey = this.textBox2.Text;
                string dir = Directory.GetCurrentDirectory();
                FileStream fs = new FileStream(this.EncryptedFile, FileMode.Open);               
                BinaryReader br = new BinaryReader(fs);
                byte[] bin = br.ReadBytes(Convert.ToInt32(fs.Length));
                byte[] exe = null;
                fs.Close();
                br.Close();
                using (Aes encryptor = Aes.Create())
                {
                    Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[]
                         {0x20, 0x6e, 0x20, 0x61, 0x6e, 0x20, 0x4d, 0x65,0x20, 0x61, 0x64, 0x76,0x65 });
                    encryptor.Key = pdb.GetBytes(32);
                    encryptor.IV = pdb.GetBytes(16);
                    using (MemoryStream ms = new MemoryStream())
                    {
                        using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(),
                                                   CryptoStreamMode.Write))
                        {
                            cs.Write(bin, 0, bin.Length);
                            cs.Close();
                        }
                        exe = ms.ToArray();
                    }
                }
    

l'array exe consiste dell'eseguibile decrittografato 

-passiamo al punto 2)c)

    private void GetPasswordFromRemoteAddress(object sender, EventArgs e)
        {
            try
            {
                /*https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsacryptoserviceprovider?view=net-7.0*/
                RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
                string publicKey = rsa.ToXmlString(false); // false to get the public key   
                string address = this.textBox5.Text;                
                ChannelFactory<IService> cf = new ChannelFactory<IService>(new WebHttpBinding(), address);
                //WebHttpBehavior abilita il modello di programmazione Web per un servizio Windows Communication Foundation (WCF).
                cf.Endpoint.Behaviors.Add(new WebHttpBehavior());
                //crea il canale di comunicazione
                IService channel = cf.CreateChannel();
                //viene invocato il metodo GetPassword sul servizio il cui indirizzo è address                
                byte[] encryptedData = channel.GetPassword(publicKey);
                //viene valorizzato decryptedData con l'array di bytes ritornati dal metodo RSADecrypt
                byte[] decryptedData = RSADecrypt(encryptedData, rsa.ExportParameters(true), true);
                UnicodeEncoding convertToString = new UnicodeEncoding();
                string str = "";
                str = convertToString.GetString(decryptedData);
                this.textBox2.Text = str;                              
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }
        }

      segue il metodo RSADecrypt che utilizza la chiave privata:

      static public byte[] RSADecrypt(byte[] DataToDecrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
        {
            try
            {
                byte[] decryptedData;
                //Create a new instance of RSACryptoServiceProvider.
                using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
                {
                    //Import the RSA Key information. This needs
                    //to include the private key information.
                    RSA.ImportParameters(RSAKeyInfo);
                    //Decrypt the passed byte array and specify OAEP padding.  
                    //OAEP padding is only available on Microsoft Windows XP or
                    //later.  
                    decryptedData = RSA.Decrypt(DataToDecrypt, DoOAEPPadding);
                }
                return decryptedData;
            }
            //Catch and display a CryptographicException  
            //using a message box
            catch (CryptographicException e)
            {                
                MessageBox.Show(e.Message.ToString());
                return null;
            }
        }

-punto 2)d)

private void button_decrypt_and_run_Click(object sender, EventArgs e)
        {
            try
            {
                string EncryptionKey = this.textBox2.Text;
                string dir = Directory.GetCurrentDirectory();
                FileStream fs = new FileStream(this.EncryptedFile, FileMode.Open);               
                BinaryReader br = new BinaryReader(fs);
                byte[] bin = br.ReadBytes(Convert.ToInt32(fs.Length));
                byte[] exe = null;
                fs.Close();
                br.Close();
                using (Aes encryptor = Aes.Create())
                {
                    Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[]
                         {0x20, 0x6e, 0x20, 0x61, 0x6e, 0x20, 0x4d, 0x65,0x20, 0x61, 0x64, 0x76,0x65 });
                    encryptor.Key = pdb.GetBytes(32);
                    encryptor.IV = pdb.GetBytes(16);
                    using (MemoryStream ms = new MemoryStream())
                    {
                        using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(),
                                                   CryptoStreamMode.Write))
                        {
                            cs.Write(bin, 0, bin.Length);
                            cs.Close();
                        }
                        exe = ms.ToArray();
                    }
                }
               //https://learn.microsoft.com/en-us/dotnet/api/system.reflection.assembly.load?view=net-8.0
                Assembly assembly1 = Assembly.Load(exe);
                var programType1 = assembly1.GetTypes().FirstOrDefault(c => c.Name == "Program");
                MethodInfo method1=programType1.GetMethod("Start", BindingFlags.Public | BindingFlags.Static);
                method1.Invoke(null, new object[] { });                
            }
            catch(Exception ex)
            {
                string msg= " controllare anche che la password per la decriptazione sia corretta";
                MessageBox.Show(ex.Message.ToString() + " " + msg);                
            }
        }

per utilizzare il codice soprastante bisogna modificare il codice di Ethical_Hacking.exe aggiungendo il metodo Start :     
    
static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            //Start();
        }
                
        public static void Start()  
        {
            Form1 f = new Form1();
            f.ShowDialog();           
        }
    }
    
il metodo chiama il primo Form dell'applicazione (Form1) .

3)  
  a) si tratta di un textbox dove possiamo inserire un indirizzo http a cui sarà possibile trovare il servizio WCF 
 b)  come sopra si tratta di un textbox in cui inserire la password che abbiamo utilizzato al punto 2)a) 
 c) al click del button1 (Start) eseguiamo il gestore dell'evento click
         private void button1_Click(object sender, EventArgs e)
        {
                try
                {
                  string uri = this.textBox1.Text;                    
                  string s = "";
                  s = this.textBox2.Text;                    
                  this.password = s;
                  //WebServiceHost è una classe derivata da ServiceHost che integra il modello di programmazione REST
                  //di Windows Communication Foundation (WCF).
                  //https://learn.microsoft.com/it-it/dotnet/api/system.servicemodel.web.webservicehost?view=netframework-4.8
                  host = new WebServiceHost(typeof(Service), new Uri(uri));                       
                  host.Open();               
                } catch(Exception ex)
                {
                   MessageBox.Show(ex.Message.ToString());
                }
        } 

d) al click del button2 (Stop) 
         private void button2_Click(object sender, EventArgs e)
        {
            try { 
                host.Close();
            }catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

e) segue la classe service che implementa IService 
public class Service : IService
    {        
        public byte[] GetPassword(string publicKey)
        {
             try {                
                byte[] encryptedData = null;
                Form1 f = (Form1)Application.OpenForms["Form1"];
                string text = f.password;
                UnicodeEncoding byteConverter = new UnicodeEncoding();
                byte[] dataToEncrypt = byteConverter.GetBytes(text);

                //load the encryptedData variable with the return of the RSACryptoServiceProvider encrypt method
                using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
                {
                    // load public key from publicKey parameter ,publicKey is a valid xml    
                    rsa.FromXmlString(publicKey);
                    // Encrypt the data and store it in the encyptedData bytes array                       
                    encryptedData = rsa.Encrypt(dataToEncrypt, true);
                }
                return encryptedData;
            } catch(Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            } 
            return null;
        }       
    }
 
sia WindowsFormsProtector che PasswordSupplier possiedono ed utilizzano la seguente interfaccia :

//https://learn.microsoft.com/en-us/dotnet/framework/wcf/designing-service-contracts
    //https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.servicecontractattribute?view=dotnet-plat-ext-7.0
    [ServiceContract]
    public interface IService
    {
        //https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.operationcontractattribute?view=dotnet-plat-ext-7.0
        [OperationContract]
        //https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.web.webgetattribute?view=netframework-4.8.1
        [WebGet]
        byte[] GetPassword(string s);       

    }

Per suggerimenti ,critiche o segnalazione errori potete inviare un email a gianmarco.castagna@gmail.com

domenica 15 settembre 2024

Scegliere password complesse ma facili da "indovinare" ? Come evitarlo..

 La lunghezza di una password non ne implica sempre la sicurezza , la password "Passw0rd!" è composta da 9 caratteri  di cui uno maiuscolo ,un numero ed un carattere speciale quindi formalmente è una password forte, in realtà non è così nella pratica se le credenziali sono sottoposte ad un attacco a dizionario usando le password più comuni ( https://it.wikipedia.org/wiki/Attacco_a_dizionario )  . Probabilmente un dizionario usato nell'attacco potrebbe contenere anche la password di cui sopra perchè è molto comune , per mitigare questo tipo di problemi possiamo utilizzare delle liste che contengono le password più utilizzate per verificare in fase di registrazione dell'utente che la password scelta dall'utente non sia tra queste . A quest'indirizzo   https://github.com/danielmiessler/SecLists/tree/master/Passwords/Common-Credentials potete trovare delle liste di password comuni (1000000 di password e c'è anche 'Passw0rd!' , :-) ) .

Ovviamente un attacco dizionario non è comunque facile da portare a segno se abbiamo configurato la nostra applicazione adeguatamente, ad esempio in asp.net core possiamo settare così la nostra applicazione :

builder.Services.AddDefaultIdentity<IdentityUser>
    (options =>
    {
        options.SignIn.RequireConfirmedAccount = true;
        options.Password.RequireDigit = true;
        options.Password.RequiredLength = 8;
        options.Password.RequireNonAlphanumeric = true;
        options.Password.RequireUppercase = true;
        options.Password.RequireLowercase = true;
        options.Lockout.MaxFailedAccessAttempts = 3;
        options.Lockout.AllowedForNewUsers = true;
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
    });

impostare il lockout con la valorizzazione dei tentativi falliti dopo i quali l'account è locked ed il periodo per cui esso sarà lockout è quindi relativamente semplice .

E' disponibile qui  https://info.microsoft.com/rs/157-GQE-382/images/EN-CNTNT-eBook-MicrosoftPasswordGuidance.pdf  un documento di Microsoft  che  è  una "Microsoft Password Guidance" , in questo paper è presente anche un link al sito di "Schneier on Security" ( https://www.schneier.com/ ) ,in particolare al post https://www.schneier.com/blog/archives/2014/03/choosing_secure_1.html , chi fosse interessato a tematiche di sicurezza e crittografia potrà trovare interessante la newsletter mensile del sito di Schneier

Per verificare se la password inserita dall'utente appartiene a quelle da ritenere troppo comuni per prima cosa carichiamo le "password comuni" in una tabella 'Passwords' creata nel db "GestioneUtenti" ,un pò di "pseudocodice" :

 private void button1_Click(object sender, EventArgs e)
        {
            IEnumerable<string> lines = File.ReadLines(@"C:\Users\gianm\Desktop\10-million-password-list-top-1000000.txt");
            long l = lines.LongCount<string>(); //999998

            SqlConnection conn = new SqlConnection("Data Source=(localdb)\\mssqllocaldb;Initial Catalog=GestioneUtenti;Integrated Security=True");
            conn.Open();

            foreach (string line in lines) {
                SqlParameter par = new SqlParameter("@password", System.Data.SqlDbType.Text);
                par.Value = line;

                SqlCommand cmd = new SqlCommand("insert into Passwords(password) values (" + par + ")",conn);
                cmd.Parameters.Add(par);
                cmd.ExecuteNonQuery();
                par = null;
                cmd = null;
            }
            conn.Close();
        }

il codice impiegherà da qualche decina di secondi ad alcuni minuti per caricare le passwords nella tabella 'Passwords' 

segue un metodo per verificare se la password inserita dall'utente esiste nella tabella Passwords:

 private bool CheckPasswordExists(string s)
        {
            bool b = false;
            SqlConnection conn = new SqlConnection("Data Source=(localdb)\\mssqllocaldb;Initial Catalog=GestioneUtenti;Integrated Security=True");
            conn.Open();
            SqlParameter par = new SqlParameter("@password", System.Data.SqlDbType.Text);
            par.Value = s;
            SqlCommand cmd = new SqlCommand();
            cmd.Parameters.Add(par);
            cmd.Connection = conn;
            cmd.CommandType = System.Data.CommandType.Text;
            cmd.CommandText = "select * from Passwords where Password like " + par;
            SqlDataReader reader = cmd.ExecuteReader();
            bool read = reader.Read();
            b = read;
            reader.Close();
            conn.Close();       
            return b;
        }

se il metodo torna true significa che la password esiste nella lista delle password comuni e quindi bisogna indicare all'utente di scegliere un' altra password ,
se il metodo torna false la password non è tra quelle della nostra lista e possiamo lasciar procedere l'utente nella registrazione .

Ora vedremo come utilizzare ulteriori funzionalità proprie di Asp .Net Core per verificare che la password immessa dall'utente non sia troppo comune :

nel file Program.cs ,di un progetto AspNetCore, possiamo aggiungere alla configurazione un AddPasswordValidator con cui costruire una nostra logica di validazione della password ( https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.identitybuilder.addpasswordvalidator?view=aspnetcore-7.0 ) :

builder.Services.AddDefaultIdentity<IdentityUser>
            (options =>
            {
                options.SignIn.RequireConfirmedAccount = true;
                options.Password.RequireDigit = true;
                options.Password.RequiredLength = 8;
                options.Password.RequireNonAlphanumeric = true;
                options.Password.RequireUppercase = true;
                options.Password.RequireLowercase = true;
                options.Lockout.MaxFailedAccessAttempts = 3;
                options.Lockout.AllowedForNewUsers = true;
                options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
            }) .AddPasswordValidator<TestPasswordValidator<IdentityUser>>()               
               .AddEntityFrameworkStores<AppDbContext>();

creiamo una classe TestPasswordValidator :

public class TestPasswordValidator<TUser> : IPasswordValidator<TUser> where TUser : class
    {
        
public TestPasswordValidator()
{
}
    public Task<IdentityResult> ValidateAsync(UserManager<TUser> manager, TUser user, string password)
    {
        SearchIntoDb s = new SearchIntoDb();
        bool res = s.CheckPasswordExists(password);        
        IdentityResult result;
        if (res)
        {
            result = IdentityResult.Failed(new IdentityError { Description = "Password troppo facile da indovinare" });
        } else {
            result = IdentityResult.Success;
        }
        s = null;
        return Task.FromResult(result);
    }
}

la classe deve implementare l'interfaccia IPasswordValidatorhttps://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.ipasswordvalidator-1?view=aspnetcore-7.0 ) e quindi essa deve avere un' implementazione del metodo ValidateAsync previsto dall'interfaccia , sarà questo metodo a ritornare un IdentityResult . Creando l'IdentityError possiamo valorizzare la Description dell'errore , nel caso la password non sia presente nell'elenco la variabile "res" varrà false ed il metodo ritornerà un IdentityResult.Success.

Il metodo ValidateAsync utilizza una classe SearchIntoDb ed un metodo CheckPasswordExists (già riportato sopra ) per effettuare la select nel db al fine di verificare se la password appartiene alla tabella Passwords oppure no.

public class SearchIntoDb
{

    private string connectionString = null;    
    public SearchIntoDb()
    {
        var config = new ConfigurationBuilder()
                .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
                .AddJsonFile("appsettings.json").Build();
        string t=config.GetSection("ConnectionStrings").GetSection("UtentiConnection").Value;
        connectionString = t;
    }
   
    public bool CheckPasswordExists(string password)
    {
        bool t = false;        
        SqlConnection conn = new SqlConnection(connectionString);
        conn.Open();
        SqlParameter par = new SqlParameter("@password", System.Data.SqlDbType.Text);
        par.Value = password;
        SqlCommand cmd = new SqlCommand();
        cmd.Parameters.Add(par);
        cmd.Connection = conn;
        cmd.CommandType = System.Data.CommandType.Text;
        cmd.CommandText = "select * from Passwords where Password like " + par;
        SqlDataReader reader = cmd.ExecuteReader();
        bool b = reader.Read();
        t = b;
        reader.Close();
        conn.Close();       
        return t;
    }

}

proviamo il codice in un applicazione asp .net core , inseriamo la famigerata password "Passw0rd!" 

                              

ed otteniamo quanto ci aspettavamo :



    


Il post ha solo scopo di esempio e la lista di password utilizzata non rappresenta un elenco esaustivo delle password "comuni" .


martedì 28 maggio 2024

Versione 2.8.1 alpha di KeyloggerJumper , aggiunta funzionalità per valorizzare l'ultima casella di testo selezionata e semplificata l'interfaccia

Versione 2.8.1 alpha( https://en.wikipedia.org/wiki/Software_release_life_cycle ) , ora l'ultima casella di testo selezionata può essere valorizzata con il testo scritto con la tastiera virtuale , la funzione è richiamata dal bottone su sfondo blu , il semplicissimo codice utilizzato è il seguente :

private async void button6_ClickAsync(object sender, EventArgs e) {
    string res = this.textBoxValore.Text;
    await webView21.CoreWebView2.ExecuteScriptAsync("document.activeElement.value='" + res + "';");
}

in pratica il value dell'active element viene valorizzato con il valore del testo del textBoxValore , quest'ultimo può (dovrebbe) essere valorizzato con la tastiera virtuale

per ingrandire l'immagine cliccarci sopra


qui i file sorgenti ed eseguibile https://sourceforge.net/projects/keyloggerjumper/files/  e qua la descrizione del programma , 
per suggerimenti o segnalazione di errori o malfunzionamenti potete inviare un' email a gianmarco.castagna@gmail.com

sabato 3 febbraio 2024

Compilare un form di inserimento dati programmaticamente da un altra applicazione usando .Net P/Invoke con la dll user32

Questo post è solo un ""PoC"" (Proof of Concept) :

potrebbe presentarsi in uno scenario ipotetico la necessità di caricare dei dati in un applicazione windows di cui non abbiamo i sorgenti ne accesso al database ne funzioni di importazione dati,una soluzione potrebbe essere utilizzare l'API user32 per caricare programmaticamente i dati (che arrivano da un file excel,xml,json,testo,db,web service, etc.etc.) tramite i form dell'applicazione. Per trovare gli indirizzi dei textbox che vogliamo compilare possiamo utilizzare Winspector  :

Una volta trovata la text box da compilare possiamo valorizzarla da un altra applicazione:

        using System;
using System.Runtime.InteropServices;

        namespace TestWindow
       {
            public partial class Form1 : Form
           {

              [DllImport("user32.dll", CharSet = CharSet.Unicode)]
              private static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);

              [DllImport("user32.dll")]
              static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

              const int WM_SETTEXT = 0X000C;              

              private void SendMessageToTextBox(IntPtr ptr,string testo)
             {
                    SendMessage(ptr, WM_SETTEXT, 0, testo);
             }

              private void Form1_Load(object sender, EventArgs e)
             {
                   this.CompileForm();
             }            

             private void CompileForm()
             {
                 IntPtr p1 = new IntPtr(0x0020059A);
                 this.SendMessageToTextBox(p1, "textboxuno");
             } 
         }
      }

     


Modificando il metodo CompileForm compiliamo le due textbox:

private void CompileForm()
        {
            IntPtr p1 = new IntPtr(0x0020059A);
            this.SendMessageToTextBox(p1, "textboxuno");
            IntPtr p2 = new IntPtr(0x0043060E);
            this.SendMessageToTextBox(p2, "textboxdue");           
        }

ed otteniamo :


a scopo dimostrativo dell'effettiva chiamata dell'evento click dell'altra applicazione aggiungiamo del codice nel gestore dell'evento click del bottone "button1" :

         private void button1_Click(object sender, EventArgs e)
        {
            string s = this.textBox1.Text;
            string w = this.textBox2.Text;
            this.textBox3.Text = this.textBox3.Text + s + Environment.NewLine;
            this.textBox3.Text=this.textBox3.Text + w + Environment.NewLine;           
        }


aggiungiamo al metodo CompileForm dell'applicazione "chiamante" il SendMessage all'evento click :

        private void CompileForm()
        {
            IntPtr p1 = new IntPtr(0x0020059A);
            this.SendMessageToTextBox(p1, "textboxuno");
            IntPtr p2 = new IntPtr(0x0043060E);
            this.SendMessageToTextBox(p2, "textboxdue");                       
            IntPtr p3 = new IntPtr(0x003F079E);
            const int BM_CLICK = 0x00F5;
            SendMessage(p3, BM_CLICK,IntPtr.Zero,IntPtr.Zero);
        }

chiamiamo il metodo CompileForm ed otteniamo :
   





sabato 11 marzo 2023

KeyloggerJumper un software opensource per mitigare il rischio che dati sensibili ,quali nome utente e password , vengano catturati da un keylogger basico

 KeyLoggerJumper è un software in release 2.3 alpha  , (https://en.wikipedia.org/wiki/Software_release_life_cycle) sotto la licenza New BSD.

Questo software permette di compilare dei campi di un applicazione web senza passare dallo stream della tastiera , per fare questo usa una tastiera virtuale costruita in windows forms che passa i valori al componente WebView2 che si occupa della navigazione. 

KeyLoggerJumper per il suo funzionamento necessita che sulla macchina dove andrà in esecuzione sia presente il "WebView2 Runtime" , segue il link per scaricare il run time  https://developer.microsoft.com/it-it/microsoft-edge/webview2/#download-section , si tratta di un software di Microsoft completamente gratuito .
In KeyLoggerJumper inseriamo l'indirizzo di un applicazione Asp .Net Core di prova nel textbox nell'area verde ,  premiamo il bottone "Navigate to"



selezioniamo un textbox e mentre abbiamo il focus al suo interno scegliamo Esamina (potete usare un altro browser per trovare l'id dell'elemento che vogliamo valorizzare , basta puntare alla stessa pagina :-) , personalmente come possibili alternative consiglio Chrome e Firefox Developer Edition, nel primo potete selezionare l'elemento cliccando il tasto destro del mouse e selezionando "Ispeziona" ,stessa cosa per Firefox e selezionate Inspect)


 troviamo un campo input con il valore di "id" ad "Input_Email" , valorizziamo con "Input_Email" il textbox a destra della label "Id dell'elemento da valorizzare:"



clicchiamo sul bottone Tastiera si aprirà un form di nome tastiera



 che possiamo utilizzare per scrivere il valore del campo Input_Email ,quando abbiamo scritto il valore da assegnare possiamo cliccare su Assegna

a questo punto il textbox "Valore da assegnare" avrà il valore digitato in tastiera ,clicchiamo sul bottone "Assegna Valore" 

ora il valore è caricato nell'input box email         


ripetiamo il processo per la password:

troviamo l'id del campo password 


clicchiamo su tastiera e scriviamo la password , clicchiamo su assegna:



clicchiamo su "Assegna valore" e valorizziamo il campo password


il procedimento è identico per valorizzare il campo "Input_ConfirmPassword" .

Il punto ""saliente"" del programma è il metodo

private async void AssegnaValore(object sender, EventArgs e)
        {
            try
            {
                string value = this.textBoxValore.Text;
                string t = this.textBoxId.Text;
                string test = await webView21.CoreWebView2.ExecuteScriptAsync("document.getElementById('" + t + "').value='" + value + "';");
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

ovvero sfruttiamo la possibilità di eseguire  uno script javascript con il metodo ExecuteScriptAsync in un' istanza di WebView2 , il javascript assegna la proprietà value di un elemento del document che è identificato (l'element) in modo univoco dal metodo getElementById ,
qui trovate il progetto https://sourceforge.net/projects/keyloggerjumper/files scritto in C# usando VisualStudio 2022 Community  .

Nota: per ingrandire le immagini potete cliccarci sopra :-)

sabato 25 febbraio 2023

Creare un progetto asp .net core che usi Northwind ed a cui venga aggiunto Identity in un momento successivo

 Creiamo un progetto App Web Asp .Net Core MVC ,una volta creato il progetto scrivete nella “Console di Gestione Pacchetti ":

Install-Package Microsoft.EntityFrameworkCore.Tools

vi consiglio di usare il seguente comando per visualizzare le funzionalità proprie del package appena installato :

per saperne di più digitate nella console di gestione pacchetti "Get-Help about_EntityFrameworkCore" e premete invio :-) ,

considerato che nell'esempio useremo Sql Server con lo Scaffold DB Context dobbiamo installare il pacchetto Microsoft.EntityFrameworkCore.SqlServer ed a tal fine eseguiamo il seguente comando

Install-Package Microsoft.EntityFrameworkCore.SqlServer

in seguito eseguite il comando Scaffold-DbContext per creare il dbcontext che verrà utilizzato dall'applicazione , questo modo di creare il dbcontext è detto “Database First” ovvero si parte dal db esistente per creare le entità dell'applicazione,

supponiamo di utilizzare il database Northwind https://github.com/Microsoft/sql-server-samples/tree/master/samples/databases/northwind-pubs ,apritelo con Sql Management Studio e cliccate su Execute ,ora dovremmo avere il database Northwind popolato.

A questo punto possiamo eseguire con successo il comando :

Scaffold-DbContext "Data Source=(localdb)\mssqllocaldb;Initial Catalog=Northwind;Integrated Security=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -ContextDir Context -Context NorthwindContext

Come risultato avremo una directory Context dentro la quale troveremo il file NorthwindContext.cs ,esso contiene la “mappatura” del database fisico nelle entità,avremo inoltre una nuova directory Models che contiene le classi che rappresentano le tabelle del db 

, in models troviamo le classi relative alle tabelle del db , qui sotto la lista non completa :


Per vedere l'utilizzo pratico di quanto appena fatto andiamo sulla dir controller ,tasto destro-> aggiungi-> controller:

clicchiamo su Aggiungi



scegliamo ,ad esempio Customer come classe e NorthwindContext come contesto ,clicchiamo su Aggiungi ,eseguita la procedura di Scaffolding da parte di Visual Studio andiamo a vedere la directory Controllers


 
all'interno della cartella troviamo un nuovo controller riferito alla gestione dei Customers ,prima di vedere il controller in azione spostiamo la nostra attenzione al file Program.cs e troviamo


  questo metodo definisce le regole di route ,se non settiamo nessun percorso (oltre al nome del server) verranno usati i default ,ovvero verrà selezionato il controller Home e l'action Index del controller Home ,se nel path di route abbiamo solo Customers (il nome del controller) verrà chiamata l'azione di default ovvero Index


se andiamo sotto la directory Views/Home/ troveremo il file Index.cshtml che contiene il messaggio di Welcome.

Nel file Program dobbiamo aggiungere il dbcontext :

builder.Services.AddDbContext<NorthwindContext>(options =>options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

il valore DefaultConnection è ricavato dalla sezione ConnectionStrings valore della chiave “DefaultConnection” del file appsettings.json che vedete qua sotto:

{

"Logging": {

"LogLevel": {

    "Default": "Information",

    "Microsoft.AspNetCore": "Warning"

            }

         },

"ConnectionStrings": {

    "DefaultConnection": "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=Northwind;Integrated Security=True"

},

"AllowedHosts": "*"

}

Mandiamo in esecuzione il programma cliccando su Esegui o premendo F5 , proviamo ora a chiamare il CustomersController aggiungendo alla barra dell'indirizzo “Customers” , così :

https://localhost:7074/Customers

ed otteniamo:



in pratica lo scaffolding crea per noi le funzioni base per interagire con le entità che possono esser create oppure modificate oppure andare a vederne il dettaglio oppure cancellate , il routing ha aggiunto Index nel comporre l'indirizzo da chiamare perchè è il suo valore predefinito nel MapControllerRoute .

Osserviamo le action dentro il controller :

l'action Create senza parametri è chiamata da un anchor tag helper dentro la pagina Index.cshtml sotto Views/Customers , il codice è <a asp-action="Create">Create New</a> , l'attributo asp-controller è stato omesso poiché se non viene specificato nessun controller è chiamato il controller di default che chiama la vista corrente,avremmo potuto scrivere <a asp-controller="Customers" asp-action="Create">Create New</a> ed ottenuto lo stesso risultato.La prima create si occupa quindi di reinviarci alla view dove inseriremo i dati del nuovo customer , la seconda create raccoglie i dati inseriti e crea un nuovo customer sul db. Il primo Create riceve una richiesta get da un link , il secondo riceve una richiesta di tipo post da un form ,il method non è specificato poiché di default vale “post” : <form asp-action="Create"> è quindi equivalente ad <form asp-action="Create" method="post">

Aggiungiamo Identity al progetto :

se partiamo con un progetto nuovo in fase di creazione del progetto con Visual Studio possiamo scegliere “Account Individuali” oppure da riga di comando “dotnet new mvc –auth Individual --use-local-db” per aggiungere Identity.Per aggiungere Identity in un progetto esistente dobbiamo invece seguire una procedura:

1) installiamo i seguenti due package:

        Install-Package Microsoft.AspNetCore.Identity.EntityframeworkCore

        Install-Package Microsoft.AspNetCore.Identity.UI

2) dobbiamo far derivare NorthwindContext da IdentityDbContext ovvero:

    public class NorthwindContext : IdentityDbContext {

                    ...….......

                   ..............

     }

3) configuriamo i servizi di Identity aggiungendoli ai services della classe Program:

builder.Services.AddDefaultIdentity<IdentityUser>()   .AddEntityFrameworkStores<NorthwindContext >();

in questo modo NorthwindContext erediterà il mapping specifico di Identity e sarà possibile gestire i dati relativi agli utenti utilizzando le tabelle a questo preposte,

per utilizzare il mapping aggiungiamo la chiamata al metodo base.OnModelCreating (builder) all'interno del metodo OnModelCreating (che potete trovare nella classe NorthwindContext) ,in pratica :

protected override void OnModelCreating(ModelBuilder modelBuilder)

{   base.OnModelCreating(modelBuilder);

     //qui segue il nostro codice di mapping

}

è chiaro quindi che viene chiamato il metodo OnModelCreating della classe IdentityDbContext passandogli il modelBuilder ,essendo aspnetcore opensource possiamo andare a vedere dove Identity definisce la struttura delle identità che lo vanno a definire: 

https://github.com/dotnet/aspnetcore/blob/main/src/Identity/EntityFrameworkCore/src/IdentityUserContext.cs

4) per continuare la nostra procedura dobbiamo aggiungere due middleware : 

   
   app.UseAuthentication();
   app.UseAuthorization();

i middleware devono essere aggiunti nel punto “giusto” ,al contrario dei services , un punto adatto potrebbe essere dopo il routing :

app.UseRouting();

app.UseAuthentication();

app.UseAuthorization();

5)poichè la UI di Identity usa le razor pages dobbiamo abilitare le razor pages aggiungendo il servizio RazorPages nel file Program così :

builder.Services.AddRazorPages()

ed il middleware MapRazorPages nel seguente modo prima di app.Run():

app.MapRazorPages();

app.Run();

6) il modello concettuale ora contiene anche le identità di Identity , per allinearlo con il database dobbiamo creare una migration usando il comando Add-Migration scegliendo un nome significativo per la migration ad esempio Add-Migration Identity , eseguiamo il comando a cui facciamo seguire il comando “Update-Database” che porta le modifiche sul database fisico

7) aggiungiamo la partial view _LoginPartial.cshtml ,dobbiamo chiamare questa LoginPartial nel file _Layout.cshtml

 <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">

     <ul class="navbar-nav flex-grow-1">

    <li class="nav-item"><a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-      action="Index">Home</a></li>

     <li class="nav-item">
          <a class="nav-link text-dark" asp-area="" asp-controller="Home" 
            asp-action="Privacy">Privacy</a>
      </li>
    </ul>

        <partial name="_LoginPartial" />

 </div>

Vediamo cosa contiene la LoginPartial fornita da Identity :


@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager

<ul class="navbar-nav">

@if (SignInManager.IsSignedIn(User))

{

    <li class="nav-item">
    <a id="manage" class="nav-link text-dark" asp-area="Identity" asp-     page="/Account/Manage/Index" title="Manage">Hello @UserManager.GetUserName(User)!</a>
    </li><li class="nav-item">

     <form id="logoutForm" class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })"><button id="logout" type="submit" class="nav-link btn btn-link text-dark border-0">Logout</button></form>

    </li>

else {


    <li class="nav-item">
    <a class="nav-link text-dark" id="register" asp-area="Identity" asp-                page="/Account/Register">Register</a>
     </li>
     <li class="nav-item">
    <a class="nav-link text-dark" id="login" asp-area="Identity" asp-            page="/Account/Login">Login</a>
     </li>
}
</ul>

grazie all'if che utilizza il valore booleano ritornato dal metodo IsSignedIn(User) la partial view fornisce due coppie di link ,nel caso l'utente sia loggato oppure no ,nel primo caso il primo link è alla pagina di gestione dell'account ovvero “/Account/Manage/Index” ,il secondo è un button link dentro un form che punta ad “/Account/Logout” ,viene passato anche un returnUrl alla Index della Home . Nel caso l'utente non sia loggato la partial view restituisce due altri link , il primo invia alla pagina di registrazione “/Account/Register” il secondo manda alla pagina di Login “/Account/Login”.

Parallel Linq "troppo" veloce ?!?

 PLINQ (parallel linq)  permette la gestione di operazioni su insiemi di oggetti su più core  con un notevole vantaggio nei tempi di esecuzi...