动态更改html表单动作? (节流策略)

时间:2019-01-26 00:49:02

标签: php mysql login bots throttling

我浏览了多次有关失败的登录尝试的问答,并根据他们尝试的无效密码登录的数量来限制用户。

“阻止”用户尝试的确切含义是什么?怎么做?通过将表单操作指向其他页面?

我可能错过了明显的地方,因此我尝试提出一种“阻止”用户尝试登录x次的方法:将登录表单的操作更改为指向另一个页面,该页面不会转到数据库,它只是说“在15分钟内重试”。

我的问题是,“阻止用户”是什么意思,我如何/在何处获得此信息?似乎已经完成了某件事,所以感觉就像我只是在尝试用自己的方法重新发明轮子。

这是PHP中的完整代码,很多学分归功于StackOverflow上的不同答案以及我自己的一些答案:

<div class="nav-login">
        <?php
            //check if user is logged in
            if (isset($_SESSION['u_id'])) {
                echo '<form action="includes/logout.inc.php" method="POST">
                    <button type="submit" name="submit">Logout</button>
                </form>';
            }
            //creat a default form action which processes the login form normally
            $form_action = 'includes/login.inc.php';

            //if the above page responded with an error in the url, which looks something like login?pwd=error&uid=admin123
            if (isset($_GET['pwd']) && isset($_GET['uid']) && $_GET['pwd'] === 'error') {
                //create empty variables which can be used later on
                $response = '';
                $captcha = '';
                //get the username which was attempted
                $uid = $_GET['uid'];

                //insert the failed logins into a database along with his IP address 
                //(note: learn about and use prepared statements here!)
                mysqli_query($conn, "INSERT INTO failed_logins SET username = '$uid', ip_address = INET_ATON('".get_client_ip()."'), attempted = CURRENT_TIMESTAMP;") or die(mysqli_error($conn));

                // array of throttling
                $throttle = array(
                    //attempts => delay
                    10 => 7000, //secs
                    20 => 12000, //secs
                    30 => 'recaptcha'); //if it reaches this level, only an email link can get them back into their account, where they can either reset password or login through that link.

                // retrieve the latest failed login attempts
                $sql_1 = 'SELECT MAX(attempted) AS attempted FROM failed_logins';
                $result_1 = mysqli_query($conn, $sql_1) or die(mysqli_error($conn));
                if (mysqli_num_rows($result_1) > 0) {
                    $row_1 = mysqli_fetch_assoc($result_1);
                    //if more than 0 results appear, record the latest attempt timestamp inside a variable
                    $latest_attempt = (int) date('U', strtotime($row_1['attempted']));

                    // get the number of failed attempts
                    $sql_1 = 'SELECT COUNT(1) AS failed FROM failed_logins WHERE attempted > DATE_SUB(NOW(), INTERVAL 15 minute)';
                    $result_1 = mysqli_query($conn, $sql_1) or die(mysqli_error($conn));
                    if (mysqli_num_rows($result_1) > 0) {
                        // get the returned row
                        $row_1 = mysqli_fetch_assoc($result_1);
                        $failed_attempts = (int) $row_1['failed'];

                        // assume the number of failed attempts was stored in $failed_attempts
                        krsort($throttle);
                        foreach ($throttle as $attempts => $delay) {
                            if ($failed_attempts > $attempts) {
                                // we need to throttle based on delay
                                if (is_numeric($delay)) {

                                    $remaining_delay = time() - $latest_attempt - $delay;

                                    $remaining = abs(number_format($remaining_delay)); //abs() converts a negative number to a positive format. This is because I want to use the timer for a javascript countdown.

                                    // output remaining delay
                                    $response .= '<p id="counting_string">You must wait <span id="counter">' . $remaining . '</span> seconds before your next login attempt.</p>'; 
                                    //Okay, the user got the message, but how do I ban him from clicking login until the countdown is over? (aside from disabling the button via javascript...which seems pretty weak/lame)
                                    //option 1, change the form action?... 
                                    $form_action = "includes/failed_logins.inc.php";

                                } else {
                                    //if the user reaches above 30 attempts, display the captcha. Even if he types in the correct password this time, tho, he won't get to log in, until he clicks on the email link we sent him (with a token, kind of like a reset-password-email-token).
                                    //implement a captcha/recaptcha here
                                    $captcha .= 'captcha!';
                                    //keep the fake form_action
                                    $form_action = "includes/failed_logins.inc.php";
                                    // code to display recaptcha on login form goes here
                                }
                                break;
                            }
                        }        
                    }
                }
            }

            mysqli_close($conn); // Always close your SQL connection and free the memory or you may be needlessly holding a SQL connectio , remember that a shared hosting environment only allows max 30 SQL connections at the same time

            echo '<form action="'.$form_action.'" method="POST">'; // login.inc.php or failed_logins.inc.php 
            echo '
                <input type="text" name="uid" placeholder="Username/e-mail">
                <input type="password" name="pwd" placeholder="password">
                <button type="submit" name="submit" id="login_submit">Login</button>
            </form>';
            if (!empty($response)) {
                echo $response;
            }
            if (!empty($captcha)) {
                echo $captcha;
            }
            echo '<a href="signup.php">Sign up</a>';

            ?>
        </div>

该表如下所示:(我知道ip_address是int(11)数据类型...它来自另一个StackOverflow答案-它使用INET_ATON进行插入)

+------------+------------------+------+-----+--+
|   Field    |       Type       | Null | Key |  |
+------------+------------------+------+-----+--+
| id         | int(11) unsigned | NO   | PRI |  |
|            | NULL             |      |     |  |
|            | auto_increment   |      |     |  |
| username   | varchar(16)      | NO   |     |  |
|            | NULL             |      |     |  |
|            |                  |      |     |  |
| ip_address | int(11) unsigned | YES  |     |  |
|            | NULL             |      |     |  |
|            |                  |      |     |  |
| attempted  | datetime         | NO   | MUL |  |
|            | NULL             |      |     |  |
|            |                  |      |     |  |
+------------+------------------+------+-----+--+

此外,为了更加清楚,这是include / login.inc.php的外观:

<?php

//We created this last, but place it before anything else in the document. It simply means that users who are logged in should be able to see this page.
session_start();

//Check if user actually clicked submit button, else throw an error 
if (isset($_POST['submit'])) {

include 'dbh.inc.php';

$uid = mysqli_real_escape_string($conn, $_POST['uid']);
$pwd = mysqli_real_escape_string($conn, $_POST['pwd']);

//Error handlers
//Check if inputs are empty
if (empty($uid) || empty($pwd)) {
    header("Location: ../index.php?login=empty");
    exit();
} else {
    //Check if username exists in database
    //Prepare these fields! (Use prepared statements here)
    $sql = "SELECT * FROM users WHERE user_uid='$uid' OR user_email='$uid'"; 
    //check if username or email is existing
    $result = mysqli_query($conn, $sql);
    $resultCheck = mysqli_num_rows($result);
    if ($resultCheck < 1) {
        header("Location: ../index.php?login=error");
        exit();
    } else {
        if ($row = mysqli_fetch_assoc($result)) { //This takes all the data we requested from the user_uid column in the database, and places it into an array called $row. If we wanted, we could echo out the username like this: echo $row['user_uid'];
            //De-hashing the password
            //Again, use prepared statement
            $hashedPwdCheck = password_verify($pwd, $row['user_pwd']); //Check if password matches, returns true or false
            if ($hashedPwdCheck == false) {
                //wrong password!
                header("Location: ../index.php?pwd=error&uid=".$uid);
                exit();
            } elseif ($hashedPwdCheck == true) {
                //Log in the user here
                $_SESSION['u_id'] = $row['user_id'];
                $_SESSION['u_first'] = $row['user_first'];
                $_SESSION['u_last'] = $row['user_last'];
                $_SESSION['u_email'] = $row['user_email'];
                $_SESSION['u_uid'] = $row['user_uid'];
                header("Location: ../index.php?login=success");

                exit();
            }
        }
    }
}
} else {
    header("Location: ../index.php?login=error");
    exit();
}

我已经注释掉了每个步骤以跟踪我在做什么...代码以任何方式看起来是“奇怪”还是“错误”?老实说,我很想知道,请问在节流延迟期间“阻止”用户尝试的正确方法是什么?

0 个答案:

没有答案