Login system security

8
Hello, I've done a torrent file download and upload system, and this site has a login system with a database, and I'd like you to explain a little about security in PHP and mySql and what should I do to maintain the security of the site.

If you can avoid sending me a material / topic in English or have me look for it on Google, I would appreciate it.

Registration system

    include("connection.php");

$login = $_POST['login_cadastro'];
$senha = $_POST['senha_cadastro'];
$confirmarsenha = $_POST['confirmarsenha_cadastro'];
$email = $_POST['email_cadastro'];

if($senha != $confirmarsenha)
{   
    echo "<meta http-equiv='refresh' content='0; url=index.php'>
          <script type='text/javascript'>alert('As senhas estão diferentes')</script>";
}
else
{
    $sqlpegar = mysqli_query($coneccao, "SELECT * FROM usuarios");

    while($linha = mysqli_fetch_array($sqlpegar))
    {   
        $login_db = $linha['login'];
        $email_db = $linha['email'];
    }

    if($login_db == $login)
    {
        echo "  <meta http-equiv='refresh' content='0'>
                <script type='text/javascript'>alert('Esse usuario já existe')</script>";
    }
    if($email_db == $email)
    {
        echo "  <meta http-equiv='refresh' content='0'>
                <script type='text/javascript'>alert('Esse email já esta sendo usado')</script>";
    }
    else
    {

        $sql = mysqli_query($coneccao, "INSERT INTO usuarios(login, senha, email) VALUES ('$login', '$senha', '$email')");  
        header("location: index.php");  
    }
}

mysqli_close($coneccao);

Login system

    include "connection.php";

$login = $_POST['login_entrar'];
$senha = $_POST['senha_entrar'];

$sql = mysqli_query($coneccao, "SELECT * FROM usuarios WHERE login = '$login'");    


while($linha = mysqli_fetch_array($sql))
{
    $senha_db = $linha['senha'];
    $login_db = $linha['login'];
}

$cont = mysqli_num_rows($sql);

if($login_db != $login || $login == "")
{       
    echo "<meta http-equiv='refresh' content='0; url=index.php'>
    <script type='text/javascript'>alert('Este usuario não existe')</script>";      
}
else
{
    if($senha_db != $senha )
    {
        echo "<meta http-equiv='refresh' content='0; url=index.php'>
        <script type='text/javascript'>alert('Senha incorreta')</script>";  
    }
    else
    {
        session_start();

        $_SESSION['login_usuario'] = $login;

        header("location: index.php");  
    }
}

mysqli_close($coneccao);

connection.php

$coneccao = mysqli_connect("localhost", "root", "");
mysqli_select_db($coneccao, "luppbox");
    
asked by anonymous 12.05.2015 / 02:12

3 answers

4

Hello, this is a "scribble" of a tutorial login system I created recently, it is not the most complex, but it will help you understand the basics about security today.

Login form & registration

<?php
// index.php
require_once("database.php");
// [Nao usar estes modelos em aplicacoes reais]
// Estes são apenas para demonstração e teste, e não são nada seguros
if(isset($_POST["submit"]) && isset($_POST["tipo"]) && $_POST["tipo"] === "novo"){
    $usuario = $_POST["usuario"];
    $email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
    $password = $_POST["password"];
    // Limpar caracteres invalidos
    // Incompleto [Nao usar estes modelos em aplicacoes reais]
    // Validar Letras para o usuarios e remover espaços brancos com o trim()
    $usuario = preg_replace("/[^A-Za-z0-9]/","", $usuario) ? trim($usuario) : NULL;
    // Validar Conjunto alfa-numérico para o password e remover espaços brancos com o trim()
    $password = preg_match("/[A-Za-z0-9]/", $password) ? trim($password) : NULL;
    if($usuario !== NULL && $password !== NULL){
        $sim = registar($usuario, $email, $password);
        if($sim){
            header("Location: index.php");
            exit;
        } else {
            //Erro para o caso de o usuario ja existir, ou nao ser armazenado
            echo "<span style=\"color:red;\">erro: cadastro falhou, tente novamente.</span>";
            exit;
        }
    } else {
        //Erro para o caso de a senha, usuario, email estar(em) vazio(s)
        echo "<span style=\"color:red;\">erro: usuario/senha vazio(s)</span>";
        exit;
    }
}

if(isset($_POST["submit"]) && isset($_POST["tipo"]) && $_POST["tipo"] === "entrar"){
    $usuario = $_POST["usuario"];
    // $email = $_POST["email"];
    $password = $_POST["password"];
    // Limpar caracteres invalidos
    $usuario = preg_replace("/[^A-Za-z0-9]/","", $usuario) ? trim($usuario) : NULL;
    $password = preg_match("/[A-Za-z0-9]/", $password) ? trim($password) : NULL;
    if($usuario !== NULL && $password !== NULL){
        $sim = login($usuario, $password);
        if($sim){
            header("Location: privado.php");
            exit;
        } else {
            //Erro para o caso de o usuario nao ser encontrado ou para o caso de os dados nao corresponderem
            echo "<span style=\"color:red;\">erro: usuario/senha nao encontrados</span>";
            exit;
        }
    } else {
        //Erro para o caso de a senha e usuario estar(em) vazio(s)
        echo "<span style=\"color:red;\">erro: usuario/senha vazio(s)</span>";
        exit;
    }
}
?>
<!-- HTML !-->
<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Login/Cadastro [Seguro]</title>
    </head>
    <body>
        <?php 
            if(isset($_GET["opcao"]) && $_GET["opcao"] === "entrar"){
            ?>
            <!-- LOGIN !-->
            <h1>Login</h1>
            <form method="POST" action="index.php">
                <input type="hidden" name="tipo" value="entrar"/>
                Usuario:<br/>
                <input type="text" name="usuario" value="" size="40"/><br/>
                Password:<br/>
                <input type="password" name="password" value="" size="40"/><br/>
                <input type="submit" name="submit" value="Entrar"/>
            </form>
            <a href="index.php?opcao=novo">Cadastrar</a><br/><br/>
            <?php
            } elseif(isset($_GET["opcao"]) && $_GET["opcao"] === "novo"){
            ?>
            <!-- CADASTRO !-->
            <h1>Cadastrar</h1>
            <form method="POST" action="index.php">
                <input type="hidden" name="tipo" value="novo"/>
                Usuario:<br/>
                <input type="text" name="usuario" value="" size="40"/><br/>
                Email:<br/>
                <input type="email" name="email" value="" size="40"/><br/>
                Password:<br/>
                <input type="password" name="password" value="" size="40"/><br/>
                <input type="submit" name="submit" value="Entrar"/>
            </form>
            <a href="index.php?opcao=entrar">Login</a><br/><br/>
            <?php
            } else {
            ?>
            <!-- LOGIN !-->
            <h1>Login</h1>
            <form method="POST" action="index.php">
                <input type="hidden" name="tipo" value="entrar"/>
                Usuario:<br/>
                <input type="text" name="usuario" value="" size="40"/><br/>
                Password:<br/>
                <input type="password" name="password" value="" size="40"/><br/>
                <input type="submit" name="submit" value="Entrar"/>
            </form>
            <a href="index.php?opcao=novo">Cadastrar</a><br/><br/>
            <?php
            }
            ?>
    </body>
</html>

The Login and Registration form is in a single file along with the validation for both. In a login script and registration, it is always crucial to sanear, validar the information coming from the forms in order to verify if they have the right size, or if it does not contain any problematic character. Pay attention to the notes in the script, and notice that I did not even use a decent validation.

Database

<?php
// database.php
session_start();
// Em fase de correcção não vamos querer essa função no ativo
// Se esta linha for descomentada, os erros estarao visiveis apenas no log.log
// ini_set("error_reporting", "E_ALL");
require_once("blowfish.php");

// Host, normalmente é o local
DEFINE("HOST", "localhost");
// O port, na maior parte das vezes é dispensavel
// Ainda assim o POST inicia conexoes seguras/nao seguras dependendo do PORT usado
// DEFINE("PORT", "80");
// Usuario do banco de dados
DEFINE("USR", "root");
// Senha do usuario do banco de dados
DEFINE("PWD", "");
// Banco de dados
DEFINE("BD", "_banco_de_dados_em_uso_");

// Ficheiro de log, caso não exista, crie um manualmente;
// Ou crie uma função que o faça de forma segura e autonoma
$error_log = "log.log";

$db = new mysqli(HOST, USR, PWD, BD);

if(mysqli_connect_errno()){
    error_logi("Conexao falhou", mysqli_connect_error());
}

function error_logi($error,$msg=""){
    global $error_log;

    $log_msg = $error . " : " . $msg . PHP_EOL;

    return file_put_contents($error_log, $log_msg, FILE_APPEND | LOCK_EX);
    exit;
}

// Função para encontrar usuario por nome;
function encontrar_usuario($usuario){
    global $db;
    ($stmt = $db->prepare("SELECT username,senha FROM usuarios WHERE username=?")) || error_logi("STMT Encontrar Usuario", $db->error);

    $stmt->bind_param('s', $usuario) || error_logi("STMT Bind Param", $db->error);

    $stmt->execute() || error_logi("STMT Execute", $db->error);

    $stmt->bind_result($username,$senha) || error_logi("STMT Bind Result", $db->error);

    $stmt->fetch();

    $result = ["username"=>$username,"senha"=>$senha];

    return $result;
}
// Função para efectuar o registo;
function registar($usuario, $email, $password){
    global $db;
    $password = hash_password($password);
    ($stmt = $db->prepare("INSERT INTO usuarios (username, email, senha) VALUES (?, ?, ?)"))
    || error_logi("SQL Prepared Statment",$db->error);
    ($stmt->bind_param('sss', $usuario, $email, $password)) || error_logi("SQL BindParam",$db->error);
    $exec = $stmt->execute() ? true : error_logi("SQL Execute",$db->error);
    return $exec;
    $stmt->close();
    $db->close();
}
// Tentar fazer o login
function login($usuario, $password){
    $usuario = encontrar_usuario($usuario);
    if($usuario){
        // usuario encontrado
        // Verificar a hash para a password
        if(verifica_hash($password, $usuario["senha"])){
            $_SESSION["usuario"] = $usuario["username"];
            return true;
        } else {
            // hash não encontrada
            return false;
        }
    } else {
        // usuario não encontrado
        return false;
    }
}

function check_login($usuario){
    $existe = encontrar_usuario($usuario);
    if($existe){
        return $existe["username"] === $usuario ? true : false;
    } else {
        return false;
    }
}

?>

In the database the password field must be of type VARCHAR(60) to be able to store hash .

Private Page (restricted to non-members)

<?php
// privado.php
// Esta é a página protegida
require_once("database.php");

if(isset($_SESSION["usuario"])){
    if(check_login($_SESSION["usuario"])){
        echo "Logado";
        // Isto é um sistema para teste, daí usar esta função aqui
        // Significa que a página só pode ser visualizada apenas 1 vez por login
        session_destroy();
    } else {
        header("Location:index.php");
        exit;
    }
} else {
    header("Location:index.php");
    exit;
}
?>

HASH

<?php
// blowfish.php
// Script util apenas para versões do PHP < 5.5.0;
// Função que gera a hash
function hash_password($password){
    $formato = "$2y$10$";
    $salt = salt(22);
    $formato_salt = $formato.$salt;
    $password_hash = crypt($password, $formato_salt);
    return $password_hash;
    // Se algo correr mal a função vai retornar falso;
}
// Função que gera o salt
function salt($tamanho){
    //$random = md5(uniqid(mt_rand(), true));
    // ambas funções geram valores aleatorios
    $random = md5(uniqid(mcrypt_create_iv(22, MCRYPT_DEV_URANDOM), true));
    $base = base64_encode($random);
    $base64 = str_replace('+', '.', $base);
    $salt = substr($base64, 0, $tamanho);
    return $salt;
}
// Função para comparar as duas hash
function verifica_hash($password, $hash_existente){
    $hash = crypt($password, $hash_existente);
    if($hash === $hash_existente){
        return true;
    } else {
        return false;
    }
}


?>

In this script, functions are only for PHP < 5.5.0 , for higher versions I recommend replacing in the database.php file the hash_password, verifica_hash functions with new functions officially entered by PHP >= 5.5.0 :

password_hash () password_verify ()

And if possible, use these functions instead of using the blowfish.php file.

There is also a API for versions of PHP >= 5.3.7 that initializes these new functions of PHP >= 5.5.0 in lower versions, just that the script in use makes a require of this API .

PASSWORD COMPAT API

Other security tips:

* Avoid users of type root to handle applications as a client.

* Avoid saving passwords as texto simples or plain-text to English.

* Requests of type GET must always be idempotentes , that is, unable to perform modifications on the server side (for modifications use POST ).

In addition to these, there are several points to note. However I leave only this summary, and good luck.

WikiHow - How to create a secure login script in PHP and MySQL

GitHub - PHP Login Advanced

    
12.05.2015 / 14:36
3

Storing Passwords in the Database

It is recommended that when storing passwords in the database, you do not do so in order to store them as text-plain (or plain text). One way to save them would be by using hashs and salts . There is a well didactic example here .

Avoiding SQL Injection

Regarding the connection and execution of queries in the database, it is important to use Statements to avoid SQL Injection attacks. This is a type of attack that allows anyone running it to execute malicious queries when you perform a query with concatenated variables.

Instead of concatenating the attributes in the query string, use wildcards and statements

Change this:

$sql = mysqli_query($coneccao, "SELECT * FROM usuarios WHERE login = '$login'"); 

by:

$mysqli = new mysqli('localhost', 'usuario', 'senha', 'database');
$stmt = $mysqli->prepare("SELECT * FROM usuarios WHERE login = ?");
$stmt->bind_param('s', $login);
$stmt->execute();

In this way you indicate to the database that you will send an attribute in place of the wildcard character '?' in the prepare () method. Then it informs its type in bind_param, sending the 's' to indicate that it will be a string. More information about the types of data that can be sent here (in parameters & types )

For examples purposes, a code snippet with more than 1 parameter (and with different types) is used:

$stmt = $mysqli->prepare("SELECT * FROM cidades WHERE populacao > ? AND estado = ?")
$stmt->bind_param('is', 42000, "PR"); // 42000 é inteiro (i), "PR" é string (s)
$stmt->execute();
    
12.05.2015 / 07:11
-1

I think it would be interesting to do these passwords (if they are equal) on the front end, so you separate your JS from PHP.

    
12.05.2015 / 13:18