提供管理員介面:輸入 ID → 查詢該筆記錄 → 顯示預填表單 → 修改提交 → 更新資料庫 → 顯示結果。原始碼存在 SQL 注入、XSS、CSRF、明文密碼、硬編碼等嚴重問題,本文給出完整重構版。
update/
├── config.php # 共用配置
├── Database.php # PDO 單例
├── WallpaperRepository.php # 查詢/更新封裝
├── Auth.php # 簡易認證 (Session + CSRF)
├── edit.php # 顯示編輯表單 (GET)
└── update.php # 處理更新提交 (POST)
<?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',
],
];
<?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']);
}
}
<?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);
}
<?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>
編輯桌面圖片記錄
</body>
</html>
<?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: = $id ?> 更新成功!
欄位 值
$v): ?>
= htmlspecialchars($k) ?> = htmlspecialchars((string)$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` 基類進一步減少樣板代碼。