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

杏林同學錄系統:個人資料管理、通訊錄與照片上傳實作分析

     編輯:PHP 綜合知識

專案背景

「杏林同學錄」的個人管理模組包含三個頁面:class/address.php(班級通訊錄)、class/modify.php(個人資料修改)、class/uploadphoto.php(照片上傳)。系統採用 Session 驗證登入狀態,直接操作 MySQL user 表。

模組一:班級通訊錄 address.php

功能邏輯

  1. Session 驗證,未登入導向首頁。
  2. 查詢 SELECT * FROM user 取得所有會員。
  3. 迴圈逐筆輸出 HTML 表格,顯示:大頭照、姓名、性別、生日、電話、傳呼、Email、OICQ、工作單位、郵編、通訊地址、個人照片連結、個人說明。

程式碼缺陷

<?php
$result = mysql_query("SELECT * FROM user", $db);
$row = mysql_num_rows($result);
for ($i = 0; $i <= ($row - 1); $i++) {
    // 逐欄位 mysql_result() 取值,效能極差
    $name = mysql_result($result, $i, 'name');
    $sex = mysql_result($result, $i, 'sex');
    // ... 共 14 次 mysql_result() 呼叫/筆
    
    $face = 'image/face/icon' . $face; // 圖片路徑拼接
    echo "<table>...<img src='$face.gif'>姓名:$name...</table>"; // 直接輸出未轉義
}
?>
  1. 效能災難:mysql_result() 每次呼叫都定位資料指標,N 筆資料 × M 欄位 = N×M 次函式呼叫。應用 mysql_fetch_assoc() 一次取得整列關聯陣列。
  2. XSS 風險:所有欄位直接 echo 輸出,未 htmlspecialchars()。
  3. 無分頁:一次載入所有會員,資料量大時頁面巨大、記憶體耗盡。
  4. HTML 結構錯誤:迴圈內重複輸出 <table>,導致巢狀表格無效。
  5. 圖片路徑硬編碼:image/face/icon{$face}.gif 假設檔案存在,無預設圖片機制。

模組二:個人資料修改 modify.php

功能流程

  1. Session 驗證。
  2. GET:查詢目前會員資料,預填表單(含 64 個大頭照單選鈕)。
  3. POST:驗證必填欄位、密碼確認、Email 格式(複雜正則),更新 user 表,寄送確認信,顯示成功訊息。

核心程式碼片段

<?php
if ($submit) {
    // 1. 必填檢查
    if (!$username || !$psw || !$psw2 || !$name || !$ad || !$ph || !$email) {
        echo "必填欄位不完整"; exit;
    }
    // 2. 密碼確認
    if ($psw !== $psw2) { echo "密碼不一致"; exit; }
    // 3. Email 正則(過度複雜且可能誤判)
    if (!ereg('^[-!#$%&\'*+./0-9=?A-Z^_`a-z{|}~]+@[-!#$%&\'*+\/0-9=?A-Z^_`a-z{|}~]+\.[-!#$%&\'*+\./0-9=?A-Z^_`a-z{|}~]+$', $email)) {
        echo "Email 不合法"; exit;
    }
    // 4. 更新資料庫(直接拼接,SQL Injection 高風險)
    $sql = "UPDATE user SET user='$user', psw='$psw', name='$name', sex='$sex', birth='$birth', 
        work='$work', ad='$ad', post='$post', ph='$ph', bp='$bp', email='$email', oicq='$oicq', 
        account='$account', signature='$signature', face='$face' WHERE user='$userregister'";
    mysql_query($sql, $db);
    
    // 5. 寄送確認信(含明文密碼!)
    mail($email, "祝賀修改成功", "您的密碼為 $psw...", $headers);
    echo "修改成功!";
    exit;
}
?>

嚴重問題

  1. 明文密碼寄送:確認信包含明文密碼,極度危險。
  2. SQL Injection:所有變數直接拼接,$user、$psw 等可注入任意 SQL。
  3. Email 正則過時:ereg() 已棄用(PHP 5.3+),正則過度嚴格可能誤攔合法信箱(如含 +、子網域)。
  4. 無 CSRF Token:表單可被偽造提交修改他人資料。
  5. 大頭照選擇 UX 差:64 個單選鈕一次渲染,建議改用圖片網格 + JS 選取。
  6. 輸出未轉義:表單 value="" 未 htmlspecialchars(),屬性注入風險。

模組三:照片上傳 uploadphoto.php

功能流程

  1. Session 驗證。
  2. 接收 $_FILES['picurl'] 上傳檔案。
  3. 檢查 image/ 目錄存在否,不存在則 mkdir()。
  4. copy($picurl, "image/$picurl_name") 將暫存檔移至目標(應用 move_uploaded_file())。
  5. 讀取舊 photo 欄位,追加新連結 <a href='upload/image/...'>看照片</a>| 寫回資料庫。
  6. 刪除暫存檔 unlink($picurl)。

程式碼缺陷

<?php
if ($upload) {
    if ($picurl == 'none') { echo "未上傳檔案"; exit; }
    $v = opendir('image');
    if (!$v) { mkdir('image'); $v = opendir('image'); }
    
    // 使用 copy() 而非 move_uploaded_file(),無法確保檔案真為上傳檔
    $up = copy($picurl, "image/$picurl_name");
    if ($up) {
        $result = mysql_query("SELECT photo FROM user WHERE user='$userregister'", $db);
        $photo = mysql_result($result, 0, 'photo');
        $photo = str_replace("'", "''", $photo); // 手動跳脫單引號,不完全
        $photo .= "<a href='upload/image/$picurl_name'>看照片</a>|";
        mysql_query("UPDATE user SET photo='$photo' WHERE user='$userregister'", $db);
        echo "上傳成功!檔名:$picurl_name 大小:$picurl_size 類型:$picurl_type";
    } else { echo "上傳失敗"; }
    unlink($picurl);
    closedir($v);
    exit;
}
?>

關鍵安全漏洞

  1. 檔案型別未驗證:僅靠瀏覽器 accept="image/*" 前端限制,伺服端未檢查 MIME、副檔名、魔術位元組,可上傳 .php 執行檔導致 RCE。
  2. 檔名可控:直接使用 $picurl_name(用戶端檔名),可含 ../ 路徑遍歷、特殊字元。
  3. 使用 copy() 非 move_uploaded_file():無法保證來源為合法上傳檔案。
  4. 資料庫儲存 HTML 片段:photo 欄位存累積的 <a> 標籤字串,查詢、刪除、維護極度困難。
  5. 無檔案大小限制:可上傳巨大檔案耗盡磁碟。
  6. 目錄權限:mkdir('image') 預設 0777,且上傳目錄可直接 HTTP 存取,若上傳 PHP 檔可直接執行。

現代化重構方案

1. 資料表正規化

-- 用戶主表
CREATE TABLE users (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    nickname VARCHAR(50) NOT NULL,
    sex ENUM('M','F') NULL,
    birth DATE NULL,
    phone VARCHAR(30) NULL,
    pager VARCHAR(30) NULL,
    email VARCHAR(100) NULL,
    oicq VARCHAR(20) NULL,
    workplace VARCHAR(100) NULL,
    zipcode VARCHAR(10) NULL,
    address VARCHAR(200) NULL,
    bio TEXT NULL,
    signature TEXT NULL,
    face_id TINYINT UNSIGNED DEFAULT 0,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 用戶相簿(獨立表,支援多張照片、標題、排序)
CREATE TABLE user_photos (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNSIGNED NOT NULL,
    filename VARCHAR(255) NOT NULL,      -- 系統產生唯一檔名
    original_name VARCHAR(255) NOT NULL, -- 用戶原始檔名
    mime_type VARCHAR(50) NOT NULL,
    size_bytes BIGINT UNSIGNED NOT NULL,
    sort_order INT UNSIGNED DEFAULT 0,
    is_avatar BOOLEAN DEFAULT FALSE,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    INDEX idx_user_created (user_id, created_at)
);

2. 安全檔案上傳處理(PHP 8)

<?php
// upload_photo.php
session_start();
require 'db.php';
require 'csrf.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') exit;
if (!validate_csrf($_POST['csrf'] ?? '')) exit('CSRF failed');
if (empty($_SESSION['user_id'])) exit('Unauthorized');

$file = $_FILES['photo'] ?? null;
if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
    echo json_encode(['success' => false, 'error' => '上傳失敗或無檔案']);
    exit;
}

// 1. 檔案大小限制(2MB)
if ($file['size'] > 2 * 1024 * 1024) {
    echo json_encode(['success' => false, 'error' => '檔案不得超過 2MB']);
    exit;
}

// 2. MIME 型別驗證(使用 finfo)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($mime, $allowedMimes, true)) {
    echo json_encode(['success' => false, 'error' => '僅允許 JPG/PNG/GIF/WebP 格式']);
    exit;
}

// 3. 檯面名稱安全化
$ext = match ($mime) {
    'image/jpeg' => 'jpg',
    'image/png' => 'png',
    'image/gif' => 'gif',
    'image/webp' => 'webp',
};
$safeName = bin2hex(random_bytes(16)) . '.' . $ext; // 隨機檔名
$saveDir = __DIR__ . '/../uploads/photos/' . date('Y/m/');
if (!is_dir($saveDir)) mkdir($saveDir, 0755, true);
$savePath = $saveDir . $safeName;

// 4. 移動檔案
if (!move_uploaded_file($file['tmp_name'], $savePath)) {
    echo json_encode(['success' => false, 'error' => '檔案儲存失敗']);
    exit;
}

// 5. 寫入資料庫
$relativePath = 'uploads/photos/' . date('Y/m/') . $safeName;
$stmt = $pdo->prepare("
    INSERT INTO user_photos (user_id, filename, original_name, mime_type, size_bytes)
    VALUES (?, ?, ?, ?, ?)
");
$stmt->execute([$_SESSION['user_id'], $relativePath, $file['name'], $mime, $file['size']]);

echo json_encode([
    'success' => true, 
    'photo' => [
        'id' => $pdo->lastInsertId(),
        'url' => '/'.$relativePath,
        'name' => $file['name']
    ]
]);
?>

3. 通訊錄分頁 API

<?php
// api/address_book.php
session_start();
require 'db.php';

$page = max((int)($_GET['page'] ?? 1), 1);
$perPage = min(max((int)($_GET['per_page'] ?? 20), 10), 100);
$offset = ($page - 1) * $perPage;

$stmt = $pdo->prepare("
    SELECT id, nickname, sex, birth, phone, email, oicq, workplace, zipcode, address, bio, face_id
    FROM users ORDER BY nickname LIMIT :offset, :perPage
");
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->bindValue(':perPage', $perPage, PDO::PARAM_INT);
$stmt->execute();
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

// 總數
$total = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();

header('Content-Type: application/json');
echo json_encode([
    'data' => $users,
    'pagination' => [
        'page' => $page,
        'per_page' => $perPage,
        'total' => $total,
        'total_pages' => (int)ceil($total / $perPage)
    ]
]);
?>

總結

「杏林同學錄」的個人管理模組雖實作了通訊錄、資料修改、照片上傳完整流程,但存在SQL Injection、XSS、明文密碼郵件、檔案上傳無驗證、資料結構反正規化、效能災難(mysql_result)、無分頁、無 CSRF 保護等嚴重缺陷。重構應採用PDO 預備語句、輸出轉義、密碼雜湊、安全檔案上傳流程、資料表正規化、分頁 API、CSRF Token等現代最佳實務,並引入圖片處理庫(縮圖、格式轉換)、雲端儲存(S3)等擴充基礎設施。

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