Ajax and php protection

Asked

Viewed 77 times

3

Using ajax in my case can bring some security flaw? If so, how do I resolve it? javascript:

$(function() {
    if($('#login_submit').length !== 0) {
        $('#login_submit').on('click', function() {
            if(!$('#login_submit').hasClass('disabled')) {

                $.ajax({
                    url: url + 'login/ajaxLogin',
                    type: 'POST',
                    data: $('#login_form').serialize(),
                    success: function(result) {
                        alert(result);
                        $("#login_submit").removeClass("m-progress disabled");
                    }
                });

                return false;
            }
        });
    }
});

Controller:

class Login extends Controller {
    public function index() {
        //if($this->model->isUserLoggedIn()) 
            //header('Location: ' . URL . 'me');

        $news = $this->model->latestNews();

        require APP . 'view/_templates/header.php';
        require APP . 'view/login/index.php';
        require APP . 'view/_templates/footer.php';
    }

    /**
     * AJAX-ACTION: AjaxLogin
     * TODO documentation
     */
    public function ajaxLogin() {
        $errors = $this->model->doLoginWithPostData($_POST['user_email'], $_POST['user_password']);
        echo $errors;
    }
}

Model:

public function doLoginWithPostData($user_email, $user_password) {
    if(empty($user_email) AND empty($user_password)) {
        return MESSAGE_ALL_EMPTY;
    } else if(empty($user_email)) {
        return MESSAGE_USERNAME_EMPTY;
    } else if(empty($user_password)) {
        return MESSAGE_PASSWORD_EMPTY;
    } else {
        $result_row = $this->getUserData(trim($user_email));

        if(!isset($result_row->id)) {
            return MESSAGE_USER_DOES_NOT_EXIST;
        } else if(($result_row->user_failed_logins >= 3) && ($result_row->user_last_failed_login > (time() - 30))) {
            return MESSAGE_PASSWORD_WRONG_3_TIMES;
        } else if(!password_verify($user_password, $result_row->password)) {
            $sth = $this->db->prepare('UPDATE users '
                    . 'SET user_failed_logins = user_failed_logins+1, user_last_failed_login = :user_last_failed_login '
                    . 'WHERE mail = :user_email');
            $sth->execute(array(':user_email' => $user_email, ':user_last_failed_login' => time()));

            return MESSAGE_PASSWORD_WRONG;
        } else {
            // write user data into PHP SESSION [a file on your server]
            $_SESSION['user_id'] = $result_row->id;
            $_SESSION['user_name'] = $result_row->username;
            $_SESSION['user_email'] = $result_row->mail;
            $_SESSION['user_logged_in'] = 1;

            // reset the failed login counter for that user and set last ip and time
            $query = $this->db->prepare('UPDATE users '
                    . 'SET user_failed_logins = 0, user_last_failed_login = NULL, last_online = :time, ip_last = :ip '
                    . 'WHERE id = :user_id AND user_failed_logins != 0');
            $query->execute(array(':time' => time(), ':ip' => $this->getUserIP(), ':user_id' => $result_row->id));

            if(defined('HASH_COST_FACTOR')) {
                if(password_needs_rehash($result_row->password, PASSWORD_DEFAULT, array('cost' => HASH_COST_FACTOR))) {
                    $user_password_hash = password_hash($user_password, PASSWORD_DEFAULT, array('cost' => HASH_COST_FACTOR));

                    $query = $this->db->prepare('UPDATE users SET password = :user_password_hash WHERE id = :user_id');
                    $query->bindValue(':user_password_hash', $user_password_hash, PDO::PARAM_STR);
                    $query->bindValue(':user_id', $result_row->id, PDO::PARAM_INT);
                    $query->execute();
                }
            }
        }
    }
}
  • What framework are you using? Cakephp?

  • None, it’s my own framework.

1 answer

3

The only thing I would recommend is using some kind of nonce.

A nonce is a number that is used only once (for example, a GUID). When the page is created, also create a nonce with a certain key. When you do the POST to the server, you pass the nonce together (both in the Header or in content/body). On the server side, check that nonce exists in the session, and if right, accept the login.

Why the nonce?

The purpose of nonce in web applications, it is to confirm that the POST is being made from reliable source - in this case, your page, which is the only place that has the logic to create the nonce, because it uses the same algorithm that the server uses to verify it. So I can’t send a POST to that page and try to log into your site through another.

Moreover

I see you’re using various good practices, including paramaterization of its queries to the database.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.