永遠保持懷疑來自 client 的資料---->因為可以改
確保資料安全性---->因為程式碼可能被偷

問題修正

問題一: 偽造身分,竄改 cookie 資料

因為 cookie 可以被改,所以才會需要 session 機制,確保當下 cookie 裡的 token 跟當前登入的使用者存起來的 token 是同一個

解決方法:通行證機制,帶 token,用 token 來確認身分
cookie 的 token 是對外的,若竄改 cookie,就會發現與存起來的 token 不是同一個

自己寫 session 機制

1. 建立 token 的 table
2. 產生 token:寫個 function 產生一組隨機字串
3. token、username 寫進 database
4. setCookie 存成 token,把 token 存到 client 端
5. 驗證 token:

  • 檢查 cookie 內是否存在 token,有的話去 token 的 table 查對應的 username,把 username 取出
    select username from tokens where token = %s
    $username = $row[username]
    
  • 用 username 去 users 的 table 查對應的資料,把資料取出
    select * from users where username = %s
    

6. 刪除 token:刪除資料庫的 token、清空 cookie 的 token,登出後 token 就被刪除了,下次再登入後又會產生一組新的 token

補充:

  • 在 function 內用 $conn 要用 global
    global $conn
    

使用 php 內建 session 機制

1. 第一行加上 sesseion_start(),告訴 php 要使用 session

<?php
    session_start();
    ...
?>

2. 產生 token:在登入成功的時候加上 $_SESSION['username'] = $username,設定 session

  1. 產生 session id (token)
  2. 把 session id 跟 usename 寫入檔案(檔案會自動建立存在某個地方)
  3. set Cookie:session id(token)
if ($result->num_rows) {
     $_SESSION['username'] = $username;
}

3. 驗證 token:用 $_SESSION['username']取出對應資料

  1. 在 cookie 裡讀取 PHPSSID(token)
  2. 從檔案讀取 session id 對應的內容
  3. 把內容放到 $_SESSION
$user = $_SESSION['username'];

4. 刪除 token:加上 session_destroy() 清除 session 的內容

<?php
    session_start();
    session_destroy();
    ...
?>

問題二:明文密碼,資料庫被偷密碼就被看光了

加密 Encryption:可以解開(一對一)
雜湊 Hash:不可解開(多對一) ex. SHA256

解決方法:hash 密碼

使用 php 內建 hash

1. hash password:把 password 傳進去 password_hash(),讓 password 經過 hash 之後存進資料庫

$password = password_hash($_POST['password'], PASSWORD_DEAFULT);

2. 驗證 password:用 verify_password 驗證輸入的 password 跟資料庫經過 hash 的 password 是否對應

password_verify(輸入的密碼, 資料庫的密碼);

問題三:Cross Site Scripting(xss),在別人網站執行 JS

輸入的東西被當作程式碼而不是文字,利用這個就可以偷別人資料 ex. cookie 或可以導到釣魚網站

解決方法:跳脫字串

使用 php 內建轉換

把字串帶入 htmlspecialchars(),所有使用者可以自己調控的地方都要跳脫
註:在顯示的時候才跳脫,不要再輸入的時候跳脫,保持資料庫內輸入的原貌

function escape($str) {
    return htmlspecialchars($str, ENT_QUOTES)
}

問題四:SQL Injection

注入惡意字串改變 SQL 原有的意思
ex.

// 原本的 SQL 語法
SELECT * FORM users WHERE username = '%s' and password ='%s'

//惡意字串
username:' or 1=1#(# 會被當作註解)
password: 123

// 呈現
SELECT * FORM users WHERE username = '' or 1=1#' and password ='123'
// 原本的 SQL
INSERT INTO comments(nickname, comment) VALUES(%s, %s) 

// 惡意字串
nickname: aa
comment:'), ('admin', 'test

//呈現
INSERT INTO comments(nickname, comment) VALUES('aa', ''), ('admin', 'test')

解決方法: Prepared Statement

$sql = "insert into comments(nickname, content) values(?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param('ss', $nickname, $content);
$result = $stmt->execute();
$result = $stmt->get_result(); // 拿結果

新增功能

資料正規化

SQL JOIN

SELECT * FORM comments LEFT JOIN users ON comments.username = users.username

join 後,欄位名稱會被覆蓋,加上別名,指定要使用哪個 table 的欄位

SELECT 
    C.id AS id, C.content AS content, 
    C.created_at AS created_at, 
    U.nickname AS nickname 
FORM comments AS C LEFT JOIN users AS U ON C.username = U.username

編輯留言

1. 新增編輯留言按鈕,href 帶 update_comment.php,帶上留言的 id

<a href="update_comment.php?id=<?php echo $row['id']>">編輯留言

2. 驗證如果留言的 username 跟使用者 username 一樣,再顯示編輯留言按鈕

<?php if ($row['username'] === $username) {
    <a href="update_comment.php?id=<?php echo $row['id']>">編輯留言
}
?>

3. 新增 upadate_comment.php 的檔案,form action 寫 handle_update_comment.php
4. 用 GET 取得留言 id,用 id 找到 comment

<?php 
...
$id = $_GET['id']
$stmt = $conn->prepare('select * from comments where id = ?')
$stmt->bind_param("i", $id)
...
?>

5. 把 comment 抓出來放進編輯框裡

<textarea name="content" row="5"><?php echo $row['content'] ?></textarea>

6. 加一行隱藏的 input 把 id 帶著

<input type="hidden" name="id" value="<?php $row['id'] ?>"

7. 新增 handle_update_comment.php 的檔案
8. 確認 POST content 是否有東西,沒有的話就導回去 update_comment.php 的頁面
9. 編輯留言用 update

$sql = "update comments set content=? where id=?"

10. 最後導回 index.php

刪除留言

Hard DELETE

資料會直接從資料庫裡被刪掉,無法復原
1. 新增刪除按鈕,href 帶 delete_comment.php
2. 新增 delete_comment.php 的檔案
3. 用 GET 取得留言 id,用 delete 刪除 id

$sql = "delete from comments where id =?";

Soft DELETE

不會真的把資料刪掉,避免資料不小心誤刪的時候,還有機會可以復原
1. 在資料庫新增一個欄位 is_deleted

  • 欄位名稱:is_deleted
  • 型態:TINYINT
  • 預設值:NULL

2. 用 update 改變 id_deleted 的狀態

$sql = "update comments set is_deleted=1 where id=?"

3. 在 index.php 選取 is_deleted 是 NULL

WHERE C.is_deleted IS NULL //加在 left join 的後面

分頁功能

1. 用 limit ... offset ... 決定一頁顯示的量
limit:限制數量 offset:跳過數量

// 一次選擇五個,跳過前三個
SELECT * FROM comments ORDER BY id LIMIT 5 OFFSET 3

2. 計算出所有 comment 的量

// count 可以計算數量
SELECT count(id) FORM comments WHERE is_deleted is NULL
$count = $row['count']

3. 計算總共幾頁

$total_page = ceil($count / $items_per_page)

4. 用 GET 取得當前頁碼

// intval() 把字串轉成數字
$page = intval($_GET['page'])

5. 用<a href=""></a> 導到各分頁,如果不是首頁就出現上一頁的按鈕,不是最後一頁就出現下一頁的按鈕

    <?php if ($page != 1) { ?>
        <a href="index.php?page=1">首頁</a>
        <a href="index.php?page=<?php echo $page - 1; ?>">上一頁</a>
    <?php } ?>
    <?php if ($page != 1) { ?>
        <a href="index.php?page=<?php echo $page + 1; ?>">下一頁</a>
        <a href="index.php?page=<?php echo $total_page; ?>">最後一頁</a>
    <?php } ?>

權限管理問題:即便按鈕被隱藏起來,還是有機會可以連進去

當沒有按鈕可以導到該網址時,只要直接在網址列輸入已知的網址,就算沒有登入也可以導到該網址進行刪除或編輯別人的留言

解決方法:做權限檢查,確保執行動作的是本人

$sql = update comments set is_deleted=1 where id=? and username=?

#Web #PHP #MySQL #資訊安全 #cookie #session #SQL Injection #hash #Prepared Statement







Related Posts

克努斯-莫里斯-普拉特演算法(KMP Algorithm)

克努斯-莫里斯-普拉特演算法(KMP Algorithm)

CS75 (Summer 2012) Lecture 9 Scalability Harvard Web Development David Malan

CS75 (Summer 2012) Lecture 9 Scalability Harvard Web Development David Malan

[#005] 383. Ransom Note

[#005] 383. Ransom Note


Comments