防止TOCTTOU(检查时间到使用时间)

时间:2014-05-21 04:36:55

标签: php security pdo

我一直在学习如何使身份验证系统更安全。我的代码的一个问题是它容易受到使用时间检查错误的影响。这是我的代码:

$stmt = $connection->prepare("SELECT username, password, email FROM users WHERE username=:username");
    $stmt->execute(array(':username' => $username));
    $rows = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($rows != null) {
            $_SESSION["message"] = "name already exists";
        } else {
            $stmt = $connection->prepare("INSERT INTO users(company, username, password, first_name, last_name, address, address2, city, state, zip, phone, email) VALUES (:company, :username, :password, :first_name, :last_name, :address, :address2, :city, :state, :zip, :phone, :email)");
        $result = $stmt->execute(array(':company' => $company, ':username' => $username, ':password' => $hashed_password, ':first_name' => $firstName, ':last_name' => $lastName, ':address' => $address1, ':address2' => $address2, ':city' => $city, ':state' => $states, ':zip' => $zip, ':phone' => $phone, ':email' => $email));
        if ($result) {
            // Success
            $_SESSION["message"] = "You've successfully registered.";
            redirect_to("login.php");
        } else {
            // Failure
            $_SESSION["message"] = "Registration failed.";
        }
    }
}
} else {
// This is probably a GET request
}

因此,它首先看到用户是否存在,如果不存在,则继续将用户的信息插入数据库。但这会给TOCTTOU带来漏洞。为了解决这个问题,我将“用户名”列设为UNIQUE。这是我唯一需要做的事情吗?有没有办法将这两个语句结合起来,以免它受到这种类型的攻击?

我一直在阅读这方面的资源,似乎使列UNIQUE或PRIMARY是最好的选择。但是,我不想依赖数据库。有什么建议?谢谢!

这是我的固定代码:

在文件顶部:

ini_set('display_errors', 'On'); error_reporting(E_ALL);

然后:

$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$connection->beginTransaction();
try {
$stmt = $connection->prepare('SELECT 1 FROM users WHERE username = :username LIMIT 1');
$stmt->execute([':username' => $username]);
$check = $stmt->fetchColumn();
if ($check) {
    $_SESSION["message"] = "name already exists";
} else {
    $stmt = $connection->prepare('INSERT INTO users(company, username, password, first_name, last_name, address, address2, city, state, zip, phone, email) VALUES (:company, :username, :password, :first_name, :last_name, :address, :address2, :city, :state, :zip, :phone, :email)');
    $stmt->execute(array(':company' => $company, ':username' => $username, ':password' => $hashed_password, ':first_name' => $firstName, ':last_name' => $lastName, ':address' => $address1, ':address2' => $address2, ':city' => $city, ':state' => $states, ':zip' => $zip, ':phone' => $phone, ':email' => $email)); // this should throw an exception if it fails, no need to check the return value
    $_SESSION["message"] = "You've successfully registered.";
}
$connection->commit();
redirect_to("login.php");
} catch (PDOException $e) {
$connection->rollBack();
throw $e;
}

1 个答案:

答案 0 :(得分:1)

我会在你的桌面上保持唯一约束,因为从数据的角度来看这是有意义的。

对于您的PHP代码,只需使用事务。例如......

// make sure you have $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)
$connection->beginTransaction();
try {
    $stmt = $connection->prepare('SELECT 1 FROM users WHERE username = :username LIMIT 1');
    $stmt->execute([':username' => $username]);
    $check = $stmt->fetchColumn();
    if ($check) {
        $_SESSION["message"] = "name already exists";
    } else {
        $stmt = $connection->prepare('INSERT INTO  ...');
        $stmt->execute([...]); // this should throw an exception if it fails, no need to check the return value
        $_SESSION["message"] = "You've successfully registered.";
    }
    $connection->commit();
    redirect_to("login.php");
} catch (PDOException $e) {
    $connection->rollBack();
    throw $e;
}