PDA

View Full Version : [PHP+MySQL] Consiglio autenticazione utente


packllama
26-02-2009, 12:28
Ciao a tutti,
Sono alle prese con un login form in php. Gestisco utenti e password all'interno di un db e vorrei un vostro parere.

Per l'autenticazione ho ragionato così:

1. l'utente inserisce uid e pwd, e clicca su ok
2. lo script entra nel db dove sono le pwd ed i nomi utente ed estrapola la pwd reale corrispondente all'uid inserito.
3. lo script genera una tabella temporanea (se non esiste) e ne cancella il contenuto
4. lo script inserisce sulla prima riga le due password, quella inserita dall'utente e quella vera
5. confronta le due password.

Ora ho visto che il sistema è un tantino arzigogolato, più che altro mi preoccupa il fatto che se dovessero loggare svariati utenti sarebbe difficile cancellare il contenuto della tabella temporanea.

A questo proposito pensavo di creare un ID sessione univoco, o tramite qualche altro metodo, per cancellare solo ed esclusivamente quella riga (non mi fido di cancellare la riga corrispondente ad un singolo inserimento di password per ovvie ragioni di possibili "doppioni").

Più o meno comunque dovrà lavorare così.

Che ne pensate? Consigli, critiche, dritte..?

grazie!

ale09hh
26-02-2009, 12:42
Ciao io nel mio sito ho fatto così:

-l'utente inserisce uid e pwd
-script sql: $result=mysql_query("SELECT * FROM utenti WHERE username = '$user' AND password = '$pass'",$dbconnect);
-$rowCheck = mysql_num_rows($result);
-se le righe di ritorno sono 0 --->password sbagliata
-se è 1 --> password esatta --> creazione cookie

PS: io non sono un esperto quindi questo sistema sarà non sicuro o ce ne saranno di migliori.. ma funziona ;)

packllama
26-02-2009, 12:51
spero di non dire castronerie, ma il tuo script mi sembra vulnerabile ad un injection.

se tu nel campo password inserisci 'OR USER='nomeutente'
dovresti poter accedere lo stesso anche se sbagli password, perchè OR consente di restituire true anche se solo una delle due condizioni è soddisfatta.

In sostanza: preleva dalla tabella la riga che corrisponde al nome utente ed alla password (oppure al nome utente).


prova un po' vediamo che succede :D

ale09hh
26-02-2009, 12:55
No ora ho provato a mettere la password sbagliata e non riesco ad accedere...

Per quanto riguarda la sicurezza non lo so, te l'ho detto non sono un esperto, sono già contento che funziona :D

Big Bamboo
26-02-2009, 19:27
3. lo script genera una tabella temporanea (se non esiste) e ne cancella il contenuto
4. lo script inserisce sulla prima riga le due password, quella inserita dall'utente e quella vera
5. confronta le due password.


Non capisco perché tu faccia queste operazioni (che ritengo inutili)

Ti elenco una possibile soluzione

1 form con user e password
2 pagina che legge questi dati ed estrapola da database la password cercando tra gli username (che dovranno per forza di cose essere univoci)
3 confronta le 2 password ed autorizza se uguali

Di solito nel database si inserisce un hash della password e non la stessa in chiaro. Puoi usare md5 come algoritmo.

SerMagnus
26-02-2009, 19:32
concordo con quanto detto dagli altri, ti complichi la vita per nulla, una sola query per verificare l'utente e poi usa le sessioni invece del cookie per salvare lo stato dell'autorizzazione

vizzz
26-02-2009, 20:05
al posto di md5 meglio usare SHA1, se poi hai accesso fisico alla macchina (puoi installare pacchetti) esistono dei moduli PEAR che gestiscono l'autenticazione su database.

malocchio
26-02-2009, 23:26
spero di non dire castronerie, ma il tuo script mi sembra vulnerabile ad un injection.


Non capisco perché tu faccia queste operazioni (che ritengo inutili)

Ti elenco una possibile soluzione

1 form con user e password
2 pagina che legge questi dati ed estrapola da database la password cercando tra gli username (che dovranno per forza di cose essere univoci)
3 confronta le 2 password ed autorizza se uguali

Di solito nel database si inserisce un hash della password e non la stessa in chiaro. Puoi usare md5 come algoritmo.

Quoto a gran voce

packllama
27-02-2009, 08:44
Le faccio perchè non ci capisco niente, ecco perchè chiedo consiglio :help:


Avevo dei dubbi sulla sicurezza di uno script del tipo

"SELECT password FROM utenti WHERE user='$user_form' AND password='$pwd_form'"

perchè temo, come già citato, che sia vulnerabile.

Però se mi dite che è una complicazione inutile la mia soluzione (ci vogliono 4 secondi per ottenere risposta, lol, su un db di 3 password) allora proverò a criptare le password.

E comunque, come evitare che qualcuno inserisca ad esempio una condizione OR 1 - - all'interno del campo pwd?
Il mio grande cruccio é rendere sicuro il sistema di autenticazione..

Con la query che mi hai consigliato, se io metto nel campo password una condizione del tipo 'OR 1 - - ' ..dovrebbe sempre garantire l'accesso...?

Consigli bene accetti! e grazie ancora :)

packllama
27-02-2009, 16:45
Allora, questo è il codice risultante da vostri consigli. Mi fareste avere la vostra opinione in merito? grazie!

function password()

{
$link = mysqli_connect('server', 'user', 'password', 'nomedb');
if (!$link) {print "Errore durante la connessione al server"; print mysqli_error($link);exit();}

#Dichiaro le variabili locali
$uid_form=$_POST['uid'];
$pwd_form=$_POST['pwd'];

#aggiungo un backslash prima di ogni single quote per prevenire
#SQL injection, ma prima controllo se sono abilitati i magic quotes
if (!get_magic_quotes_gpc()){
$pwd_form=addslashes($pwd_form);}

#Faccio l'hash md5 della password inserita per renderla
#confrontabile con quella del database utenti
$pwd_hash=md5($pwd_form);

#Procedo con la select: seleziono la password coerentemente con i dati forniti

if ($query1=mysqli_query($link,"SELECT password FROM utenti WHERE utente='$uid' AND password='$pwd_hash'"))
{
$row=mysqli_fetch_row($query1);
$result=$row[0];

#Se il record risulta nullo, errore
if ($result==NULL) {print "Record inesistente";exit();}
}

if ($result==$pwd_hash){print "Password corretta. Benvenuto, ".$uid_form."\n";}
else {print "Password errata.\n";}

}

Big Bamboo
27-02-2009, 19:24
Allora, questo è il codice risultante da vostri consigli. Mi fareste avere la vostra opinione in merito? grazie!

function password()

{
$link = mysqli_connect('server', 'user', 'password', 'nomedb');
if (!$link) {print "Errore durante la connessione al server"; print mysqli_error($link);exit();}

#Dichiaro le variabili locali
$uid_form=$_POST['uid'];
$pwd_form=$_POST['pwd'];

#aggiungo un backslash prima di ogni single quote per prevenire
#SQL injection, ma prima controllo se sono abilitati i magic quotes
if (!get_magic_quotes_gpc()){
$pwd_form=addslashes($pwd_form);}

#Faccio l'hash md5 della password inserita per renderla
#confrontabile con quella del database utenti
$pwd_hash=md5($pwd_form);

#Procedo con la select: seleziono la password coerentemente con i dati forniti

if ($query1=mysqli_query($link,"SELECT password FROM utenti WHERE utente='$uid' AND password='$pwd_hash'"))
{
$row=mysqli_fetch_row($query1);
$result=$row[0];

#Se il record risulta nullo, errore
if ($result==NULL) {print "Record inesistente";exit();}
}

if ($result==$pwd_hash){print "Password corretta. Benvenuto, ".$uid_form."\n";}
else {print "Password errata.\n";}

}

io farei così (premetto che php lo conosco veramente poco)

$query1=mysqli_query($link,"SELECT password FROM utenti WHERE utente='$uid'");
$row=mysqli_fetch_row($query1);
$result=$row[0];
#Se il record risulta nullo, errore
if ($result==NULL) {print "Record inesistente";exit();}
if ($result==$pwd_hash){print "Password corretta. Benvenuto, ".$uid_form."\n";}
else {print "Password errata.\n";}

In pratica basta selezionare la riga con l'uid (e non anche con la password) e verificare se il valore restituito della password è uguale.

Contro SQL injection in altri linguaggi c'è la possibilità di parametrizzare la query. Credo che esista qualcosa di simile anche per php.

packllama
28-02-2009, 08:30
In effetti hai ragione, controllo due volte la password e non serve a niente.


Con la procedura da me usata, lo script non fa altro che prendere ogni stringa ed inserire un backslash per ogni apostrofo. Ciò interferisce con il database solo nel momento in cui l'utente malizioso invia al posto della password una clausola SQL e deve usare per forza gli apici (rendendo così vano il suo sforzo).

Sono molto curioso di saperne di più sulla parametrizzazione, puoi spiegarmene il principio?

Grazie.

Big Bamboo
01-03-2009, 20:26
I dati inseriti dall'utente vengono gestiti a livello database.
Spiegazione molto semplificata:
il web server invia al database la query da eseguire e dei segnaposto.
sarà poi compito del database riempire i segnaposto con i dati dell'utente, eseguendo solo una query con i dati corretti. se l'input fornito è sbagliato (tentata sql injection) la query fallisce

woomacoder
01-03-2009, 21:44
spero di non dire castronerie, ma il tuo script mi sembra vulnerabile ad un injection.

se tu nel campo password inserisci
dovresti poter accedere lo stesso anche se sbagli password, perchè OR consente di restituire true anche se solo una delle due condizioni è soddisfatta.

In sostanza: preleva dalla tabella la riga che corrisponde al nome utente ed alla password (oppure al nome utente).


prova un po' vediamo che succede :D

Solo a quella? :) direi ad un bel po di vulnerabilità.
Consiglio un LIMIT 1 sulla query di SELECT utente AND password, si migliorano le prestazioni di sql (una volta trovato sql si ferma, non va a cercare oltre)

malocchio
02-03-2009, 00:28
Per parametrizzare la query esistono i PREPARED STATEMENTs.

Sono delle query con "segnaposto" che vengono mandate al DBMS e lui le pre-compila. Poi si associano le variabili contententi i parametri (bind) e le variabili che conterranno le colonne del resultset restituito.

Allora vediamo se riesco a rispolverare del codice (copy&paste):

<?php
$mysqli = new mysqli( ... );

/* creo un oggetto mysqli_stmt */
$stmt = $mysqli->stmt_init();
if ($stmt->prepare("SELECT * FROM `users` WHERE `uid`=? AND `pwd_hash`=? LIMIT 1")) { //i punti di domanda sono segnaposto per i parametri

/* vaccio il bind delle variabili che contengono i parametri */
$stmt->bind_param("ss", $_POST['uid'], md5hash($_POST['pwd']));

/* eseguo la query includendo i parametri */
$stmt->execute();
$stmt->store_result();

if ($stmt->num_rows = 1) {
echo "benvenuto!";
} else {
echo "credenziali errate!";
}

/* close statement */
$stmt->close();
}

/* close connection */
$mysqli->close();
?>

La classe chiave è mysqli_stmt (http://it2.php.net/manual/en/class.mysqli-stmt.php).
La fase di bind dei risultati non ti serve perché devi solo controllare che il numero di righe restituite dalla query sia uguale a uno.
Ti conviene dare un'occhiata alla funzione bind_param: se i campi nel database sono stringhe, devi usare "s" nel primo parametro della funzione, "i" per gli interi, "d" per i double, "b" per blob. Un carattere per ogni variabile da bindare.
Ricapitolando le fasi principali sono:
- connettersi al database (new mysqli)
- inizializzare un oggetto mysqli_stmt (stmt_init)
- compilare la query con i segnaposto ? (prepare)
- fare il bind di eventuali parametri (bind_param)
- eseguire la query
- fare (eventualmente) il bind delle variabili alle colonne del resultset (bind_result)
- caricare i risultati (fetch riga per riga)
- eventualmente riutilizzare il prepared statement con altri parametri (è questo uno dei principali vantaggi, inoltre è leggermente più veloce di semplici query)
- chiudere il prepared statement (close)
- chiudere la connessione al dbms (close)

Spero di essere stato abbastanza chiaro. Comunque leggiti quelle 2 pagine di documentazione e in caso di dubbi chiedi pure.

~FullSyst3m~
02-03-2009, 02:09
Per parametrizzare la query esistono i PREPARED STATEMENTs.

Sono delle query con "segnaposto" che vengono mandate al DBMS e lui le pre-compila. Poi si associano le variabili contententi i parametri (bind) e le variabili che conterranno le colonne del resultset restituito.

Allora vediamo se riesco a rispolverare del codice (copy&paste):

<?php
$mysqli = new mysqli( ... );

/* creo un oggetto mysqli_stmt */
$stmt = $mysqli->stmt_init();
if ($stmt->prepare("SELECT * FROM `users` WHERE `uid`=? AND `pwd_hash`=? LIMIT 1")) { //i punti di domanda sono segnaposto per i parametri

/* vaccio il bind delle variabili che contengono i parametri */
$stmt->bind_param("ss", $_POST['uid'], md5hash($_POST['pwd']));

/* eseguo la query includendo i parametri */
$stmt->execute();
$stmt->store_result();

if ($stmt->num_rows = 1) {
echo "benvenuto!";
} else {
echo "credenziali errate!";
}

/* close statement */
$stmt->close();
}

/* close connection */
$mysqli->close();
?>

La classe chiave è mysqli_stmt (http://it2.php.net/manual/en/class.mysqli-stmt.php).
La fase di bind dei risultati non ti serve perché devi solo controllare che il numero di righe restituite dalla query sia uguale a uno.
Ti conviene dare un'occhiata alla funzione bind_param: se i campi nel database sono stringhe, devi usare "s" nel primo parametro della funzione, "i" per gli interi, "d" per i double, "b" per blob. Un carattere per ogni variabile da bindare.
Ricapitolando le fasi principali sono:
- connettersi al database (new mysqli)
- inizializzare un oggetto mysqli_stmt (stmt_init)
- compilare la query con i segnaposto ? (prepare)
- fare il bind di eventuali parametri (bind_param)
- eseguire la query
- fare (eventualmente) il bind delle variabili alle colonne del resultset (bind_result)
- caricare i risultati (fetch riga per riga)
- eventualmente riutilizzare il prepared statement con altri parametri (è questo uno dei principali vantaggi, inoltre è leggermente più veloce di semplici query)
- chiudere il prepared statement (close)
- chiudere la connessione al dbms (close)

Spero di essere stato abbastanza chiaro. Comunque leggiti quelle 2 pagine di documentazione e in caso di dubbi chiedi pure.

La mia domanda non è molto pertinente con il thread, però vorrei sapere una buona guida dove poter studiare SQL e perchè no, anche PHP.
Ovviamente che spieghino da zero.

packllama
02-03-2009, 08:45
Per parametrizzare la query esistono i PREPARED STATEMENTs.

Sono delle query con "segnaposto" che vengono mandate al DBMS e lui le pre-compila. Poi si associano le variabili contententi i parametri (bind) e le variabili che conterranno le colonne del resultset restituito.



Ok, ero scettico sui segnaposto perchè dalle mie poche informazioni erano legati esclusivamente a PEAR al quale non voglio assolutamente essere legato (per questioni di portabilità di tutto il sistema). Tant'è vero che avrete notato che PEAR poteva essere usato anche per l'interazione materiale con MySQL, ma non l'ho fatto.

Diciamo che a volte i magic quotes traggono in inganno perchè non si sa se ci siano o meno, quindi bisogna trovare un sistema indipendente da loro (anche perchè se devi mandare in print una stringa quotata vengono fuori quegli antipatici backslash!) e il mio era semplicemente un surrogato dei magic quotes.


Bene, studio i vostri codici e poi preparo un altro ciocchetto da mettere al fuoco qui sul forum... grazie!



Per ~FullSyst3m~ : ci dovrebbe essere una discussione molto molto recente a proposito di ciò che chiedi.

Per woomacoder: ottimo il suggerimento, lo provo anche sul mio script (oddio, non devo gestire trilioni di record, però fa sempre comodo!) grazie!

~FullSyst3m~
02-03-2009, 09:54
Ok, ero scettico sui segnaposto perchè dalle mie poche informazioni erano legati esclusivamente a PEAR al quale non voglio assolutamente essere legato (per questioni di portabilità di tutto il sistema). Tant'è vero che avrete notato che PEAR poteva essere usato anche per l'interazione materiale con MySQL, ma non l'ho fatto.

Diciamo che a volte i magic quotes traggono in inganno perchè non si sa se ci siano o meno, quindi bisogna trovare un sistema indipendente da loro (anche perchè se devi mandare in print una stringa quotata vengono fuori quegli antipatici backslash!) e il mio era semplicemente un surrogato dei magic quotes.


Bene, studio i vostri codici e poi preparo un altro ciocchetto da mettere al fuoco qui sul forum... grazie!



Per ~FullSyst3m~ : ci dovrebbe essere una discussione molto molto recente a proposito di ciò che chiedi.

Per woomacoder: ottimo il suggerimento, lo provo anche sul mio script (oddio, non devo gestire trilioni di record, però fa sempre comodo!) grazie!

Quale è la discussione?

packllama
02-03-2009, 10:13
Quale è la discussione?

http://www.hwupgrade.it/forum/showthread.php?t=1934486

~FullSyst3m~
02-03-2009, 10:24
http://www.hwupgrade.it/forum/showthread.php?t=1934486

Cercavo anche quelli per SQL. Ne conosci?

CozzaAmara
02-03-2009, 12:11
Cercavo anche quelli per SQL. Ne conosci?


Qui una guida base per SQL. (http://database.html.it/guide/leggi/40/guida-linguaggio-sql/)

~FullSyst3m~
02-03-2009, 12:20
Qui una guida base per SQL. (http://database.html.it/guide/leggi/40/guida-linguaggio-sql/)

Già letta, ma è fatta abbastanza male. Almeno per me.

packllama
03-03-2009, 10:42
Sono delle query con "segnaposto" che vengono mandate al DBMS e lui le pre-compila.

Vorrei capire meglio quali siano i vantaggi dal punto di vista della sicurezza.

Ho esaminato la documentazione ed il tuo esempio (fondamentale!) ed ho fatto l'ipotesi che i tipi di dati dichiarabili sui segnaposto non permettano l'inserimento di caratteri speciali, ma penso di sbagliarmi.

Altra idea: ho letto che in SQL ci sono i cosiddetti caratteri jolly che permettono di eseguire query a partire da dati incompleti. Questi caratteri sono % e _ .

Ho letto oltretutto che la parametrizzazione della query non modifica il loro comportamento.
Se invece mi affidassi alla funzione addslashes() per inserire un backslash prima di ogni single quote ed alla funzione strtr() per eliminare i caratteri jolly e sostituirli con altri backslash?

SerMagnus
03-03-2009, 15:01
ma dico io, invece di complicarsi la vita, usa la funzione mysql_real_escape_string
veloce e indolore, ma sopratutto dormi sonni tranquilli.

non credo sia il caso di scomodare sql con query parametrizate

packllama
03-03-2009, 16:57
d'accordo con te, non trovi inoltre che dovrei usare strtr() per aggiungere un backslash ai caratteri jolly?

SerMagnus
03-03-2009, 18:06
sinceramente non sono d'accorto i caratteri jolly di per se non dovrebbero creare particolari problemi di sicurezza, inoltre nn credo che varrebbe come escape del carattere.

magari usi una regex a monte

inoltre con strtr sprechi memoria, basterebbe un str_replace


ps: si è perso parte del post durante l'utimo edit

packllama
03-03-2009, 18:11
aahh le regex:mc:

scherzi a parte, è una ottima alternativa ma per pigrizia non mi ci sono cimentato in questo caso. Però le ho usate con ottimi risultati per controllare la validità dei numeri di targa inseriti però :fagiano:

Tornando ai caratteri jolly, ad esempio inserire %xyz potrebbe creare problemi se bisogna aggiornare alcuni dati nelle tabelle, consentendo la modifica di tutti gli x record aventi nel corpo la stringa xyz


Ti invito a darmi la tua opinione sul topic che ho appena aperto qui (http://www.hwupgrade.it/forum/showthread.php?t=1939482).

SerMagnus
03-03-2009, 18:14
guarda nn riesco a seguirti, perchè dovrebbe crearti problemi.
imho una procedura di login di per se non dovrebbe fare update significativi, nè tantomento utilizzare caratteri jolly

packllama
03-03-2009, 18:16
ovviamente no, una query per un login non implica alcun aggiornamento, io parlavo così in via teorica, sarebbe meglio evitare l'inserimento casuale di caratteri jolly?

SerMagnus
03-03-2009, 18:19
imho no anzi per password complesse è una manna un carattere in più.

tutto sta da come strutturi la query. inoltre proprio per evitare problemi "strani" qualche post fà ti era stato detto di verificare che il risultato sia sempre e solo unico in casodi login esatto

packllama
03-03-2009, 18:26
Tutto ciò va ben oltre le mie esigenze, non devo gestire pwd complesse :)

Per il resto, si ho subito implementato il LIMIT 1 che giustamente fa il suo lavoro.


Ri-grazie