domenica 6 ottobre 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 :
   





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...