Sprawdzenie poprawności nicku przy rejestracji

0

Cześć, mam takie pytanko, na co najlepiej zamienić ctype_alnum jeżeli chcę dalej zabezpieczyć bazę przed wstrzykiwaniem mysql'a, a jednocześnie chciałbym umożliwić użytkownikom wprowadzanie polskich znaków (czyli chciałbym dodać utf-8) oraz znaki specjalnie (na pewno _ i -)

1

Najlepiej wyeliminować znak pojedynczego cudzysłowu lub zamienić na dwa takie znaki, żeby SZBD nie zinterpretował tego jako początek lub koniec napisu (brak tej eliminacji umożliwia wstrzyknięcie). Innym sposobem jest użycie parametrów w zapytaniu.

https://www.php.net/manual/en/security.database.sql-injection.php
https://pl.wikipedia.org/wiki/SQL_injection

2

Najlepiej to po prostu stosować PDO i prepared statements, do tego wcześniej np. przez preg_replace dokonać przefiltrowania nazwy użytkownika. Przełącznik u powoduje, że będzie brane pod uwage UTF-8 i w nazwie użytkownika będą mogły być polskie znaki diakrytyczne.

Tutaj zostawiasz tylko litery, liczby i _ + -

$username = preg_replace('/[^\w\d_\-]/imsu', '', $username);
0

A dodanie po prostu mysqli_real_escape_string() będzie tak samo działać i nie powstaną, żadne dziury?

$nickname_post = $_POST['nickname'];
$nickname = mysqli_real_escape_string($polaczenieMySql, $nickname_post);
if ((strlen($nickname)<3) || (strlen($nickname)>50))
{
  $OK=false;
  $_SESSION['nick_error']="Nick musi posiadać od 3 do 50 znaków!";
}

// if (ctype_alnum($nickname)==false)
// {
// 	$OK=false;
// 	$_SESSION['nick_error']="Nick może składać się tylko z liter i cyfr (bez polskich znaków)";
// }

I czy coś więcej dodać do filtracji

if (strpos($nickname, "~") || strpos($nickname, "`") || strpos($nickname, "!") || strpos($nickname, "@") || strpos($nickname, "$") || strpos($nickname, "%") || strpos($nickname, "^") || strpos($nickname, "&") || strpos($nickname, "(") || strpos($nickname, ")") || strpos($nickname, "<") || strpos($nickname, ">") || strpos($nickname, "?"))
{
  $OK=false;
  $_SESSION['nick_error']="Nick nie może posiadać: ~`!@$%^&()<>?";
  }
2

Dowolne escapowanie to zabawa z ogniem - poczytaj o PDO oraz prepared statements, to jedyne w 100% bezpieczne oraz prawidłowe rozwiązanie (a do tego dużo wygodniejsze!).

1

Na moje oko to kolejny ktoś który nie da się przekonać. Nawet gdyby 30 osób mu pisało, żeby to robił w PDO, to on będzie swoje, ale może się myle...

0

Przepisałem całość wedle poradnika i manuala, tak powinno wyglądać sprawdzenie nicku i dodanie do bazy, czy jeszcze o czymś zapomniałem, żeby było bezpiecznie?

    private function nNick() {
        $rezultat;
        if(!preg_replace('/[^\w\d_\-]/imsu','', $this->nickname)) {
            $rezultat = false;
        } else {
            $rezultat = true;
        }
        return $rezultat;
    }

    $stmt = $this->polacz()->prepare();
0

Ta funkcja która napisałeś nic nie zmienia w $this->nickname, to musi być tak:


$this->nickname = preg_replace('/[^\w\d_\-]/imsu','', $this->nickname);

a potem prep. statment coś w rodzaju:

$sql = "INSERT INTO tabela (nickname) VALUES (:n)";
$stmt = $PDO->prepare($sql);
$stmt->execute(['n' => $this->nickname]);
0

@TomRZ: 'Ta funkcja która napisałeś nic nie zmienia w $this->nickname, to musi być tak:'
Tak tak, wiem o tym podesłałem tylko wycinek kodu zawierający sprawdzenie poprawności nicku i czy ta funkcja ochroni przed wstrzykiwaniem mysql'a

1

Ta funkcja nie jest po to aby chronić przed SQLInjection - od tego jest PDO. Ta funkcja jest po to aby nick nie zawierał jakichś dziwnych znaków, jedynie litery, cyfry oraz znak - lub _

2

No jeśli używasz prepared statements to nie musisz się bać SQL Injection.

A nick zostaw w spokoju, najwyżej zrób trim() i tyle

0

Mam jeszcze jedno pytanko do tego wątku, a mianowicie po przepisaniu wszystkiego na PDO nie chce mi logować użytkownika przy użyciu adresu e-mail

protected function pobierzUzytkownika($nick, $haslo) {
  $stmt = $this->connect()->prepare('SELECT haslo FROM uzytkownicy WHERE nick = ? OR email = ?;');

  if(!$stmt->execute(array($nick, $nick))) {
    echo "Error connect";
  }
  if($stmt->rowCount() == 0) {
    echo "Nieprawidłowy login lub hasło!";
  }

  $hash = $stmt->fetchAll(PDO::FETCH_ASSOC);
  $sprawdz = password_verify($haslo, $hash[0]["password"]);
  if($sprawdzHaslo == false) {
      echo "Nieprawidłowy login lub hasło!";
  } elseif($sprawdzHaslo == true) {
    $stmt = $this->connect()->prepare('SELECT * FROM uzytkownicy WHERE nick = ? OR email = ? AND password = ?;');

    if(!$stmt->execute(array($nick, $nick, $haslo))) {
      echo "Error connect";
    }

    if($stmt->rowCount() == 0) {
      echo "Nieprawidłowy login lub hasło!";
    }

    //Udana rejestracja pobieranie $_SESSION
  }
}
5

Nie wiem co to znaczy, że nie loguje przez email.
Podejrzany kod może dotyczyć działania rowCount.
Za domuentecją:

If the last SQL statement executed by the associated PDOStatement was a SELECT statement, some databases may return the number of rows returned by that statement. However, this behaviour is not guaranteed for all databases and should not be relied on for portable applications.

www.php.net/manual/en/pdostatement.rowcount.php
No i zawsze staraj się używać potrójnego porównania.

0

Chodzi mi o to, że dajmy na przykład konto o nicku "test" i emailu "[email protected]"

I w momencie jak przechodzimy do logowania na stronę to nick "test" normalnie działa, a jak chce się zalogować poprzez email "[email protected]" to się nie da, a SELECT'y są ustawione na nick i email
php $stmt = $this->connect()->prepare('SELECT haslo FROM uzytkownicy WHERE nick = ? OR email = ?;');
php $stmt = $this->connect()->prepare('SELECT * FROM uzytkownicy WHERE nick = ? OR email = ? AND password = ?;');

Edit: Zrobiłem echo po każdym if'ie i dokładnie $_SESSION['logowanie_error'] = "Nieprawidłowy login lub hasło! #3"; wywala error

        } elseif($sprawdzHaslo == true) {
            $stmt = $this->connect()->prepare('SELECT * FROM uzytkownicy WHERE nick = ? OR email = ? AND password = ?;');

            if(!$stmt->execute(array($nick, $nick, $haslo))) {
                $stmt = null;
                $_SESSION['logowanie_error'] = "Błąd serwera! #L02";
                exit();
            }

            if($stmt->rowCount() == 0) {
                $stmt = null;

                $stmt = $this->connect()->prepare('INSERT INTO logi VALUES (NULL, ?, ?, ?, ?)');
        
                if(!$stmt->execute(array($nick, $data_logowania, $ipHash, $logowanieNieudane))) {
                    $_SESSION['Error_stmt'] = "Error #LS3 | Nie udało się dodac logów (stmt)";
                }
                $stmt = null;
                $_SESSION['logowanie_error'] = "Nieprawidłowy login lub hasło! #3";
                exit();
            }
1

Używaj nawiasów:

To

$stmt = $this->connect()->prepare('SELECT * FROM uzytkownicy WHERE nick = ? OR email = ? AND password = ?;');

Powinno być tak:

$stmt = $this->connect()->prepare('SELECT * FROM uzytkownicy WHERE (nick = ? OR email = ?) AND password = ?;');

Po drugie nie sprawdzaj przez rowCount, tylko po prostu:

$existingUser = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$existingUser) {
...nieudane zalogowanie...
}

Już nie mowiąc o tym, że to powinno być objęte transakcją, a poberanie użytkownika powinno być z klauzulą FOR UPDATE

0

Powinno być tak:

php $stmt = $this->connect()->prepare('SELECT * FROM uzytkownicy WHERE (nick = ? OR email = ?) AND password = ?;');

Jak dodam te nawiasy do kodu to już nawet logowanie za pomocą nicku nie działa. Sprawdziłem jeszcze czy może to wina emaila i jednak nie, jak zamienimy w kodzie kolejność na WHERE email = ? OR nick = ? AND password = ? To wtedy logujemy się do konta tylko i wyłącznie adresem e-mail, a nick nie działa.

Po drugie nie sprawdzaj przez rowCount, tylko po prostu:

$existingUser = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$existingUser) {
...nieudane zalogowanie...
} 
  • Poprawione
2

Inne rzeczy które można poprawić:

Nie tak:

$stmt = $this->connect()->prepare('SELECT * FROM uzytkownicy WHERE (nick = ? OR email = ?) AND password = ?;');

Tylko tak:

$stmt = $this->connect()->prepare('SELECT * FROM uzytkownicy WHERE (nick = :n OR email = :e) AND password = :p;');
$stmt->execute( ['n' => $nick, 'e' => $email, 'p' => $haslo] );

Przy okazji tutaj błąd, masz 2x nick:

if(!$stmt->execute(array($nick, $nick, $haslo))) {

Programowanie wymaga uważności.

Tak samo insert, nie tak:

$stmt = $this->connect()->prepare('INSERT INTO logi VALUES (NULL, ?, ?, ?, ?)');

tylko tak:

$stmt = $this->connect()->prepare('INSERT INTO logi (kolumna, kolumna, kolumna, kolumna, kolumna) VALUES (NULL, :n, :d, :ip, :ln)');
if(!$stmt->execute(['n' => nick, 'd' => $data_logowania, 'ip' => $ipHash, 'ln' => $logowanieNieudane])) {

Używając nazywanych parametrów, oraz kolumn przy insert raz trudniej sie pomylić a dwa, jeżeli w bazie zmienisz kolejność kolumn, to kod się nie zepsuje.

0

Przy kazji tutaj błąd, masz 2x nick:

if(!$stmt->execute(array($nick, $nick, $haslo))) {
Tak miało być, bo w logowaniu są 2 miejsca, na nick i hasło, lecz chce umożliwić jak ktoś wpisze w miejsce nicku adres e-mail, żeby też zalogowało

3

AND ma wyższy priorytet. Tak więc jak dasz takie zapytanie

select * from Users WHERE email = ? OR nick = ? AND password =?

To najpierw zostanie sprawdzony warunek
nick = ? AND password =? a potem z tego sprawdzony będzie OR między wynikiem tego Anda i podwarunkiem email = ?
Jeśli chcesz mieć lub między email a nick to daj nawiasy.
Spróbuj sobie wykonać te zapytania ręcznie w bazie.
Obecnie warunek wskazuje, że wystarczy podać prawidłowo Nick lub email w zależności od pozycji i podać błędne hasło. Spróbuj.
Debuguj sobie zapytania ręcznie i na wszelki wypadek z
debugDumpParams żebyś miał pewność co i gdzie Ci się podstawia w parametrach i jak wygląda finalne zapytanie.
https://www.php.net/manual/en/pdostatement.debugdumpparams.php

3

W bazie jak sobie ręcznie robię zapytanie to wszystko działa poprawnie

Zobacz https://www.db-fiddle.com/f/5fzZGzvsUwgTZJetsnadRc/0

select * from Users;
-- Test ze zlym haslem a tylko pradiwdlowym loginem, zobacz ze zwraca wiersze czyli Cie zaloguje tylko po podaniu samego nicku
select * from Users where Nick = 'James' OR Email = 'wrongemail!' AND Password = 'NOPass';
-- powyzsze zapytanie jest interpretowane tak:
select * from Users where Nick = 'James' OR (Email = 'wrongemail!' AND Password = 'NOPass');
-- Test  z prawidlowymi nawiasami i zlym haslem
select * from Users where (Nick = '[email protected]' OR Email = '[email protected]') AND Password = '!!!wronghash!!!';

-- Test  z prawidlowymi nawiasami
select * from Users where (Nick = '[email protected]' OR Email = '[email protected]') AND Password = 'somehash1';
0

Poprzednio testowałem w bazie właśnie z nawiasami, problemem jest AND password = hasło jest sprawdzane wcześniej i jeżeli == true wykonuje się to problematyczne zapytanie:

$hasloHash = $stmt->fetchAll(PDO::FETCH_ASSOC);
        $sprawdzHaslo = password_verify($haslo, $hasloHash[0]["password"]);

        if($sprawdzHaslo == false) {
            $stmt = null;

            $stmt = $this->connect()->prepare('INSERT INTO logi_historia VALUES (NULL, ?, ?, ?, ?)');
    
            if(!$stmt->execute(array($nick, $data_logowania, $ipHash, $logowanieNieudane))) {
                $_SESSION['Error_stmt'] = "Error #LS2 | Nie udało się dodac logów (stmt)";
            }
            $stmt = null;
            $_SESSION['logowanie_error'] = "Nieprawidłowy login lub hasło! #2";
            exit();
        } elseif($sprawdzHaslo == true) {
            $stmt = $this->connect()->prepare('SELECT * FROM uzytkownicy WHERE (nick = :n OR email = :e) AND password = :p;');
            if(!$stmt->execute( ['n' => $nick, 'e' => $nick, 'p' => $haslo ] )) {

Czy po prostu nie jest tak, że $haslo przetrzymuje hasło wpisane w formularzu "np. test123" i próbuje porównać to z hasłem, które jest przetrzymywane w bazie za pomocą hashu?

0

Wywal elseif bo wiadomo ze jak nie false to tru z lini 15 i daj samo else. i w lini 16 nie potrzebujesz ; na koncu zapytania a zeby sei zalogowac na email lub login to dajesz

select * from Users WHERE (email = ? OR nick = ?) AND password =?

A do logowania logow nie uzywaj

 $stmt = $this->connect()->prepare('INSERT INTO logi_historia VALUES (NULL, ?, ?, ?, ?)');

tylko zrob sobie procdeure na bazie i nazwij ja loginFaild($nick, $data_logowania, $ipHash, $logowanieNieudane)
i w kodzie php wywoluj sobie tylko te procedure z danymi

1

hasło jest sprawdzane wcześnie

Ale jak? Co sprawdzasz teraz w tym password_verify ?
Może się zgubiłem ale tak, masz sprawdzić co najmniej parę (dwie) dane wejściowe - czyli czy w bazie dla użytkownika jest zapisane poprawne hasło.
Nie wiem jak to w końcu zapisujesz, bo wychodzi na to, że zapisujesz hasło w plaintekscie skoro Ci działają zapytania w bazie - powinieneś trzymać hash hasła.
Obecnie do zapytania idzie jak rozumiem hasło w pleintekśćie bez hashowania wprowadzone przez użytkownika.
Metoda password_verify albo jest błędnie nazwa albo sprawdza tylko hasło. Czyli co? Jak podam hasło dla Użytkownika User1 to mogę być zalogowany na innego użytkownika, po podam jego login?

0

@jurek1980: No wedlug tego bezsensownego kodu jest tak:

 $sprawdzHaslo = password_verify($haslo, $hasloHash[0]["password"]);

czyli tutaj powinno byc sprawdzenie hasla z bazy danch z jakims haslohashem ? jesli tak to znaczy ze dwie osoby moga miec taki sam hash hasla i zalogujesz sie na ierwsza ? bo jesli jest tru to po co pozniej w funkcji elseif ponownie wyciagac z bazy uzytkownika po hasle i loginie ? bez sensu to jest

Powinno byc prosto:

  1. wez login (email lub login i haslo) z formularza
  2. zmien haslo na hash i wybierz WSZYSTKO z bazy gdzie Login = login lub login = email i hahs hasla_z_bazy = hashhasla z formularza
  3. jesli nie ma to daj ze login bledny
  4. jesli jest ok to tworz sesje i zaloguj usera
0

Przy rejestracji hash hasła jest tworzony standardowo $hasloHash = password_hash($haslo1, PASSWORD_DEFAULT);

A w pliku do logowania jest tylko i wyłącznie

$hasloHash = $stmt->fetchAll(PDO::FETCH_ASSOC);
$sprawdzHaslo = password_verify($haslo, $hasloHash[0]["haslo"]); 

Moje pytanie miało na celu rozwianie moich wątpliwości, czy może jak w zapytaniu SELECT określami nick albo email i hasło,
hasło z logowania - np.test123
jest porównany z hashem przy rejestracji: np. 2$1054dsfadsf4352435fds

2

Sprawdź co masz w zmiennej $haslo przed wykonaniem zapytania albo poprzez wspomniane debugowanie zapytań. Dla mnie tam jest zwykłe hasło spisane przez użytkowania typu paass123 i to samo się nie zahasuje do porównania. A wcześniej działało bo przy tak skonstruowanym zapytaniu wystarczyło podać istniejącego użytkownika.

2

Przy logowaniu uzytkownika nie pobiera sie z bzy hasla zeby je porownac i potem jesli jest ok to ponownie pobierac dane zeby uzytkownika zalogowac. Musisz od razu poberac dane do zalogowania wiec zadna funkcja if($sprawdzHaslo == false) { nie jest ci potrzebna tylko jedynie ta od veryfikacji hasla jesli nie jest puste i juz. Po co jeszcze raz sprawdzac i pobirac dane od usera jak haslo jest ok ?

3

To jeszcze raz, bo po komentarzu widzę, że nie rozumiesz.
Użytkownik wpisuje: User: test4prog , Pass: test4programmers Jeśli w bazie trzymasz hash tego hasła czyli np. $1054dsfadsf4352435fds to do zapytania do bazy musi iść też hash bo przecież test4programmers != $1054dsfadsf4352435fds .
Wcześniej Ci zadziałało bo źle skonstruowałeś zapytanie i co byś nie wpisał w hasło to by zalogowało Cię zawsze. Sprawdź tego SQL fiddla co Ci zrobiłem.

1 użytkowników online, w tym zalogowanych: 0, gości: 1