Paragraph

I don't like spam!

Допустим, Вы написали классный сайт, чтобы показать свой сервис или продукт и включили форму, чтобы посетители сайта смогли отправить Вам свои контактную информацию.

А вдруг БАХ спам. Трудно найти настоящих заинтересованных среди мусора.

Что сделать?

Наш сервис "Felix" предотвращает много видов атак, в том числе спам. Но самая лучшая защита имеет несколько слоев.

Если нет в Вашем форме уникального идентификатора для каждого показа форма, то легко злоумышленнику создать скрипт, чтобы отправить фальшивые заявки. Покажем тут легкий способ (на PHP) добавления такого идентификатора.

Популярные CMS (конструкторы) тоже добавляют и проверяют уникальные идентификаторы, но они обычно используют хэш сессии. Проблема с таким подходом следующая: если посетитель открыл Ваш сайт, потом закрыл браузер, открыл заново без обновления страницы и попробует отправить заявку, он, скорее всего, получить отказ. Наш нижеуказанное простое решение не страдает от этого нюанса.

Решение предполагает mysqli/pdo, но легко его адаптировать под свои требования.

И не забудьте нас контактировать, чтобы узнать больше о сервисе "Felix", который защищает от DDoS, брутфорс, ряд целенаправленных атак и других!

<?php
define('SERVER_NAME', 'localhost');
define('USER_NAME', 'sqluser');
define('PASSWORD', 'password');
define('DB_NAME', 'site_db');
define('TABLE_NAME', 'nonce_table');
/**
 * Note that we do not use _SESSION, since we want to allow users to submit the
 * form potentially later, but we want the nonce to be unique for any given
 * submission to avoid reuse spam.
 * @param string $field_id
 * @param string $name
 * @param int $timeout
 * @return string
 */
function nonce_get_field(string $field_id, string $name, int $timeout) {
    $conn = nonce_get_conn();
    do {
        $nonce = md5(time() . '' . rand(1, 1023));
        $sql = 'SELECT * FROM ' . TABLE_NAME . " WHERE nonce='{$nonce}'";
        $result = $conn->query($sql);
        if ($result->num_rows == 0) {
            break;
        }
    } while (true);
    $created = now();
    $timeout = ($timeout > 0 ? $created + $timeout : 0);
    $sql = 'INSERT INTO ' . TABLE_NAME . " (created, nonce, timeout)
VALUES ({$created}, '{$nonce}', {$timeout})";
    if ($conn->query($sql) !== TRUE) {
        echo "Error: {$sql}<br>{$conn->error}";
    }
    $conn->close();
    $field = "<input type='hidden' id='{$field_id}' name='{$name}' value='{$nonce}' />";
    return $field;
}
/**
 * Check that the nonce is in the database; delete if so
 * @param string $nonce
 * @return true if found and deleted; false otherwise
 */
function nonce_verify_field(string $nonce) {
    $conn = nonce_get_conn();
    $sql = 'SELECT * FROM ' . TABLE_NAME . " WHERE nonce='{$nonce}'";
    $result = $conn->query($sql);
    $success = ($result->num_rows > 0);
    $sql = 'DELETE FROM ' . TABLE_NAME . " WHERE nonce = '{$nonce}'";
    if ($conn->query($sql) !== TRUE) {
        echo "Error deleting record: {$conn->error}";
    }
    $conn->close();
    return $success;
}
/**
 * Deleted from the nonce table everything older than the given timeout (seconds)
 */
function nonce_cron_cleanup() {
    $conn = nonce_get_conn();
    $now = now();
    $sql = 'DELETE FROM ' . TABLE_NAME . " WHERE timeout < {$now}";
    if ($conn->query($sql) !== TRUE) {
        echo "Error deleting expired records: {$conn->error}";
    }
    $conn->close();
}
/**
 * Assumes mysqli/pdo
 * @param string $servername
 * @param string $username
 * @param string $password
 * @param string $dbname
 * @param string $tablename
 */
function nonce_create_table() {
    $conn = nonce_get_conn();
    $tablename = TABLE_NAME;
    $sql = "CREATE TABLE {$tablename} (
id INT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
created INT(20) UNSIGNED NOT NULL,
nonce VARCHAR(20) NOT NULL,
timeout INT(20) UNSIGNED NOT NULL
)";
    if ($conn->query($sql) !== TRUE) {
        echo "Error creating table: {$conn->error}";
    }
    $conn->close();
}
/**
 * Get a new database connection
 * @return \mysqli
 */
function nonce_get_conn() {
    $conn = new mysqli(SERVER_NAME, USER_NAME, PASSWORD, DB_NAME);
    if ($conn->connect_error) {
        die("Connection failed: {$conn->connect_error}");
    }
    return $conn;
}