首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 桌面中心系列(三):數據修改介面與安全性強化

     編輯:PHP 綜合知識

功能需求

提供管理員介面:輸入 ID → 查詢該筆記錄 → 顯示預填表單 → 修改提交 → 更新資料庫 → 顯示結果。原始碼存在 SQL 注入、XSS、CSRF、明文密碼、硬編碼等嚴重問題,本文給出完整重構版。

重構架構

update/
├── config.php          # 共用配置
├── Database.php        # PDO 單例
├── WallpaperRepository.php  # 查詢/更新封裝
├── Auth.php            # 簡易認證 (Session + CSRF)
├── edit.php            # 顯示編輯表單 (GET)
└── update.php          # 處理更新提交 (POST)

核心代碼實作

1. 共用配置與資料庫 (同系列四)

<?php
// config.php
return [
    'db' => [
        'host' => '127.0.0.1',
        'name' => 'desktop_center',
        'user' => 'db_user',
        'pass' => 'db_pass',
        'charset' => 'utf8mb4',
    ],
    'app' => [
        'table_prefix' => 'desk_',
        'allowed_tables' => ['jpg', 'jpg1', 'jpg2', 'jpg3', 'jpg4'],
        'csrf_token_name' => 'csrf_token',
    ],
];

2. 認證與 CSRF 工具 (Auth.php)

<?php
// Auth.php
class Auth {
    public static function startSession(): void {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }
    }
    
    public static function generateCsrfToken(string $name): string {
        self::startSession();
        if (empty($_SESSION[$name])) {
            $_SESSION[$name] = bin2hex(random_bytes(32));
        }
        return $_SESSION[$name];
    }
    
    public static function validateCsrfToken(string $name, string $token): bool {
        self::startSession();
        return isset($_SESSION[$name]) && hash_equals($_SESSION[$name], $token);
    }
    
    public static function requireLogin(): void {
        self::startSession();
        if (empty($_SESSION['admin_id'])) {
            header('Location: login.php');
            exit;
        }
    }
    
    public static function isAdmin(): bool {
        self::startSession();
        return !empty($_SESSION['admin_id']);
    }
}

3. Repository 擴展更新方法 (WallpaperRepository.php)

<?php
// WallpaperRepository.php (續)
    public function findById(string $table, int $id): ?array {
        $this->validateTable($table);
        $stmt = $this->pdo->prepare("SELECT * FROM `{$this->prefix}{$table}` WHERE `id` = :id");
        $stmt->bindValue(':id', $id, PDO::PARAM_INT);
        $stmt->execute();
        $row = $stmt->fetch();
        return $row ?: null;
    }
    
    public function update(string $table, int $id, array $data): bool {
        $this->validateTable($table);
        
        // 動態構建 SET 子句,僅允許白名單欄位
        $allowedFields = ['url', 'mess', 'fromx', 'url1', 'mess1', 'fromx1', 'url2', 'mess2', 'fromx2', 'url3', 'mess3', 'fromx3'];
        $setParts = [];
        $params = ['id' => $id];
        
        foreach ($allowedFields as $field) {
            if (array_key_exists($field, $data)) {
                $setParts[] = "`$field` = :$field";
                $params[$field] = $data[$field];
            }
        }
        
        if (empty($setParts)) return false;  // 無有效欄位
        
        $sql = "UPDATE `{$this->prefix}{$table}` SET " . implode(', ', $setParts) . " WHERE `id` = :id";
        $stmt = $this->pdo->prepare($sql);
        return $stmt->execute($params);
    }

4. 編輯表單頁 (edit.php) — GET 請求

<?php
// edit.php
require_once 'config.php';
require_once 'Database.php';
require_once 'WallpaperRepository.php';
require_once 'Auth.php';

Auth::requireLogin();

$config = require 'config.php';
$db = Database::getInstance($config['db']);
$repo = new WallpaperRepository($db->getPdo(), $config['app']['table_prefix'], $config['app']['allowed_tables']);

$table = $_GET['table'] ?? 'jpg';
$id = (int)($_GET['id'] ?? 0);

if ($id <= 0) {
    die('無效的 ID');
}

try {
    $item = $repo->findById($table, $id);
    if (!$item) {
        die('記錄不存在');
    }
} catch (Exception $e) {
    die('系統錯誤: ' . htmlspecialchars($e->getMessage()));
}

$csrfToken = Auth::generateCsrfToken($config['app']['csrf_token_name']);
?>
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
  <meta charset="UTF-8">
  <title>編輯記錄 - ID: <?= $id ?></title>
  <style>
    .form-grid { display: grid; grid-template-columns: 140px 1fr; gap: 12px 20px; max-width: 900px; }
    .form-grid label { font-weight: 500; text-align: right; }
    .form-grid input[type="text"], .form-grid input[type="url"] { width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }
    .form-group { margin-bottom: 16px; }
    .btn { padding: 10px 24px; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; }
    .btn-primary { background: #0066cc; color: #fff; }
    .btn-secondary { background: #6c757d; color: #fff; }
    .alert { padding: 12px 16px; border-radius: 4px; margin-bottom: 16px; }
    .alert-error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
    .section-title { margin: 24px 0 12px; padding-bottom: 8px; border-bottom: 1px solid #eee; color: #333; }
  </style>
</head>
<body>
  

編輯桌面圖片記錄

← 返回列表

第 1 組圖片

第 2 組圖片

第 3 組圖片

第 4 組圖片

取消返回
</body> </html>

5. 更新處理邏輯 (update.php) — POST 請求

<?php
// update.php
require_once 'config.php';
require_once 'Database.php';
require_once 'WallpaperRepository.php';
require_once 'Auth.php';

Auth::requireLogin();

$config = require 'config.php';
$db = Database::getInstance($config['db']);
$repo = new WallpaperRepository($db->getPdo(), $config['app']['table_prefix'], $config['app']['allowed_tables']);

$table = $_POST['table'] ?? '';
$id = (int)($_POST['id'] ?? 0);
$csrfToken = $_POST[$config['app']['csrf_token_name']] ?? '';

if ($id <= 0 || !$table || !Auth::validateCsrfToken($config['app']['csrf_token_name'], $csrfToken)) {
    die('無效請求或 CSRF 驗證失敗');
}

try {
    // 檢查記錄是否存在
    $existing = $repo->findById($table, $id);
    if (!$existing) {
        die('記錄不存在或已被刪除');
    }
    
    // 收集允許更新的欄位
    $allowedFields = ['url', 'mess', 'fromx', 'url1', 'mess1', 'fromx1', 'url2', 'mess2', 'fromx2', 'url3', 'mess3', 'fromx3'];
    $data = [];
    foreach ($allowedFields as $field) {
        if (isset($_POST[$field])) {
            $data[$field] = trim($_POST[$field]);
        }
    }
    
    if (empty($data)) {
        die('無任何有效更新欄位');
    }
    
    $success = $repo->update($table, $id, $data);
    
    if ($success) {
        // 重新查詢顯示最新資料
        $updated = $repo->findById($table, $id);
        ?>
        
        
        
          
          更新成功
          
        
        
          
✅ 記錄 ID: 更新成功!
$v): ?>
欄位值

返回列表

getMessage()); die('系統錯誤,請稍後再試'); }

安全性檢查清單

風險原始碼缺陷重構防護
SQL Injection`update $jpg set ... where id='$id'` 直接拼接PDO 參數化 + 動態白名單欄位構建
XSS直接 `value='$url'` 輸出`htmlspecialchars()` 所有輸出 + `type="url"` 驗證
CSRF無 Token 驗證Session 隨機 Token + `hash_equals()` 驗證
IDOR (越權)無權限檢查,任意 ID 可改`Auth::requireLogin()` + 管理員 Session 驗證
大量賦值所有 POST 參數直接入庫`$allowedFields` 白名單過濾
錯誤資訊洩漏`die("發生錯誤!")` 顯示 SQL通用錯誤訊息 + 後台記錄詳細日誌

總結

「修改數據」看似簡單,實則是 **讀取 → 表單預填 → 驗證 → 更新 → 回顯** 的完整閉環。重構版引入 **Repository 模式封裝 SQL、Auth 類統一認證授權、CSRF Token 防偽造、白名單防大量賦值、htmlspecialchars 全域輸出編碼**,消除了原始碼所有高危漏洞。此模式可直接套用至任何 CRUD 修改場景,建議封裝為通用的 `CrudController` 基類進一步減少樣板代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved