You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于XMLHttpRequest的PHP登录注册脚本安全性评估及优化建议请求

Security Hardening Recommendations for Your Login/Signup PHP Scripts

Great question—let’s break down the security gaps in both your login and signup scripts and walk through concrete fixes to harden them. I’ll go through each script one by one, pointing out risks and providing revised code snippets.


1. Login Script (login.inc.php)

Your script uses prepared statements (good call!), but there are several key areas to improve:

Key Issues & Fixes:

  • Missing Error Handling: You don’t check if the database connection fails or if statement preparation/execution errors occur. This can lead to silent failures or expose sensitive debug info to attackers.
  • Insecure Session Configuration: session_start() lacks security flags to prevent XSS and CSRF attacks.
  • Overly Specific Error Messages: Returning DOESNT EXISTS or PASSWORD_FAIL lets attackers enumerate valid accounts (a common brute-force vector).
  • No Input Validation: You accept raw input without checking for empty values or invalid formats (e.g., malformed emails).

Revised Code:

<?php
// Enable strict error reporting for development (disable in production)
error_reporting(E_ALL);
ini_set('display_errors', 0); // Don't show errors to end users in production

// Secure session configuration to block XSS/CSRF
session_set_cookie_params([
    'lifetime' => 3600, // 1 hour session timeout
    'path' => '/',
    'domain' => $_SERVER['HTTP_HOST'],
    'secure' => true, // Only send cookie over HTTPS (enable in production)
    'httponly' => true, // Prevent JS access to cookie
    'samesite' => 'Strict' // Block cross-site request forgery
]);
session_start();

// Database connection with error handling
$conn = mysqli_connect("localhost", "root", "", "users");
if (!$conn) {
    error_log("Database connection failed: " . mysqli_connect_error());
    echo json_encode(['status' => 'ERROR']);
    exit;
}

// Validate input presence
$params = json_decode(file_get_contents('php://input'), true);
if (!$params || empty($params['inserted_id']) || empty($params['inserted_password'])) {
    echo json_encode(['status' => 'INVALID_INPUT']);
    exit;
}

$inserted_id = trim($params['inserted_id']);
$inserted_password = $params['inserted_password'];

// Prepare statement with error checks
$stmt = mysqli_stmt_init($conn);
if (!mysqli_stmt_prepare($stmt, "SELECT id, password FROM user WHERE account_name=? OR email=?;")) {
    error_log("Statement preparation failed: " . mysqli_error($conn));
    echo json_encode(['status' => 'ERROR']);
    mysqli_stmt_close($stmt);
    mysqli_close($conn);
    exit;
}

mysqli_stmt_bind_param($stmt, "ss", $inserted_id, $inserted_id);
if (!mysqli_stmt_execute($stmt)) {
    error_log("Statement execution failed: " . mysqli_stmt_error($stmt));
    echo json_encode(['status' => 'ERROR']);
    mysqli_stmt_close($stmt);
    mysqli_close($conn);
    exit;
}

$result = mysqli_stmt_get_result($stmt);
$row = mysqli_fetch_assoc($result);

// Generic error message to prevent account enumeration
if (!$row || !password_verify($inserted_password, $row['password'])) {
    echo json_encode(['status' => 'INVALID_CREDENTIALS']);
} else {
    // Regenerate session ID to fix session fixation risks
    session_regenerate_id(true);
    $_SESSION['user_id'] = $row['id'];
    echo json_encode(['status' => 'SUCCESS']);
}

// Clean up database resources
mysqli_stmt_close($stmt);
mysqli_close($conn);
?>

2. Signup Script (signup.inc.php)

Similar to the login script, there are critical security and reliability gaps here:

Key Issues & Fixes:

  • Unsafe User ID Generation: rand() is not cryptographically secure—attackers can predict generated IDs. Use a database auto-increment ID or random_int() instead.
  • Missing Input Validation: No checks for empty fields, valid email formats, password strength, or duplicate account names.
  • Lack of Error Handling: No checks for connection failures or statement errors.
  • Unstructured Output: Echoing raw strings makes frontend handling fragile—use JSON for consistent responses.
  • Resource Leaks: You don’t always close statements or connections properly.

Revised Code:

<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);

// Database connection with error handling
$conn = mysqli_connect("localhost", "root", "", "users");
if (!$conn) {
    error_log("Database connection failed: " . mysqli_connect_error());
    echo json_encode(['status' => 'ERROR']);
    exit;
}

// Validate required input fields exist
$params = json_decode(file_get_contents('php://input'), true);
$required_fields = ['first_name', 'last_name', 'dob', 'email', 'account_name', 'password'];
foreach ($required_fields as $field) {
    if (!isset($params[$field]) || empty(trim($params[$field]))) {
        echo json_encode(['status' => 'INVALID_INPUT', 'message' => "Missing or empty $field"]);
        mysqli_close($conn);
        exit;
    }
}

// Validate email format
$inserted_email = trim($params['email']);
if (!filter_var($inserted_email, FILTER_VALIDATE_EMAIL)) {
    echo json_encode(['status' => 'INVALID_INPUT', 'message' => "Invalid email format"]);
    mysqli_close($conn);
    exit;
}

// Enforce password strength (adjust rules as needed)
$inserted_password = $params['password'];
if (strlen($inserted_password) < 8 || !preg_match('/[A-Za-z]/', $inserted_password) || !preg_match('/[0-9]/', $inserted_password)) {
    echo json_encode(['status' => 'INVALID_INPUT', 'message' => "Password must be at least 8 characters long and include letters and numbers"]);
    mysqli_close($conn);
    exit;
}

// Validate date of birth (expects YYYY-MM-DD format)
$inserted_dob = trim($params['dob']);
$dob_date = DateTime::createFromFormat('Y-m-d', $inserted_dob);
if (!$dob_date || $dob_date->format('Y-m-d') !== $inserted_dob) {
    echo json_encode(['status' => 'INVALID_INPUT', 'message' => "Invalid date format (use YYYY-MM-DD)"]);
    mysqli_close($conn);
    exit;
}

$inserted_first_name = trim($params['first_name']);
$inserted_last_name = trim($params['last_name']);
$inserted_account_name = trim($params['account_name']);

// Check for duplicate email AND account name
$stmt = mysqli_stmt_init($conn);
if (!mysqli_stmt_prepare($stmt, "SELECT id FROM user WHERE email=? OR account_name=?;")) {
    error_log("Duplicate check statement failed: " . mysqli_error($conn));
    echo json_encode(['status' => 'ERROR']);
    mysqli_stmt_close($stmt);
    mysqli_close($conn);
    exit;
}

mysqli_stmt_bind_param($stmt, "ss", $inserted_email, $inserted_account_name);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);

if (mysqli_num_rows($result) > 0) {
    $row = mysqli_fetch_assoc($result);
    $message = ($row['email'] === $inserted_email) ? "EMAIL_TAKEN" : "ACCOUNT_NAME_TAKEN";
    echo json_encode(['status' => 'ERROR', 'message' => $message]);
    mysqli_stmt_close($stmt);
    mysqli_close($conn);
    exit;
}

// Hash password with higher cost (adjust based on server performance)
$hashed_password = password_hash($inserted_password, PASSWORD_BCRYPT, ['cost' => 12]);

// Use database auto-increment ID (remove 'id' from query if your table uses this)
// If you must generate an ID manually, use: $created_id = random_int(111111111, 999999999);

// Insert new user
if (!mysqli_stmt_prepare($stmt, "INSERT INTO user(first_name, last_name, dob, email, account_name, password) VALUES (?, ?, ?, ?, ?, ?);")) {
    error_log("Insert statement failed: " . mysqli_error($conn));
    echo json_encode(['status' => 'ERROR']);
    mysqli_stmt_close($stmt);
    mysqli_close($conn);
    exit;
}

mysqli_stmt_bind_param($stmt, "ssssss", $inserted_first_name, $inserted_last_name, $inserted_dob, $inserted_email, $inserted_account_name, $hashed_password);
$success = mysqli_stmt_execute($stmt);

echo json_encode(['status' => $success ? 'SUCCESS' : 'FAIL']);

// Clean up resources
mysqli_stmt_close($stmt);
mysqli_close($conn);
?>

Additional General Tips

  • Use HTTPS: Always serve your authentication pages over HTTPS to prevent credential interception.
  • Rate Limiting: Add rate limiting to both login and signup endpoints to block brute-force attacks.
  • Password Reset: If you add a password reset feature, use time-limited, one-time tokens.
  • Database Credentials: Never hardcode database credentials in your scripts—use environment variables or a secure config file outside the web root.
  • Regular Updates: Keep PHP, MySQL, and any libraries you use up to date to patch security vulnerabilities.

内容的提问来源于stack exchange,提问作者aman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:30:47