[BE101] 留言板(中-修正問題篇)


Posted by s103071049 on 2021-06-13

發現問題 => 嘗試改一些東西 => 如何進行修正

大原則:永遠不要相信 client 的資料

  1. client 端資料都是可以改的,永遠抱持懷疑心態
  2. 假設程式碼被偷,如何維持資料安全性 ?

發現問題:偽造身份

cookie 是可以被改的,所以我們可以藉由竄改 cookie 的 value 偽造他人身分。

修正問題:通行證機制簡介與實作

本來在 cookie 裡面的東西都可以隨便改,我們沒辦法讓他不能被改。但,我們可以利用通行證的機制,發給他一張很難被偽造的通行證。

可以把要給他的東西換成 token,token 裡的值隨機產生,並將他傳給瀏覽器,因此瀏覽器下次再帶就會帶這個上來。也就是將原本的對應關係存到 server去,在 server 刻意給他這個 token,誰拿到這個 token 就可以代表這個身分。

為了要完成這個機制,在 server 端需要一個表格,去存我發的 token、這個 token 對應的 username。當下一次使用者帶了這個 token,我就去資料庫查表,查這個 username。

因為這個 token 是亂數隨機產生,通行者要破解他人的 token 必須猜非常多次,組合可能有好幾兆,因此形同給了一張很難被偽造的通行證。

第一步、建立 DB

建立資料表tokens,3 個欄位:id, token, username

第二步、改登錄機制 handle_login.php

本來存 username,現在我要進行修改。

利用 rand() 隨機產生數字、char()可以將數字轉成字母

// 實作一組隨機 16 碼的 token
  function generateToken() {
    $s = '';
    for($i=1; $i<=16; $i++) {
      $s .= chr(rand(65,90)); 
    }
    return $s;
  }
  echo generateToken();

對於比較常用/其他地方也會用到的 funct,可以獨立成一個 php 檔,再將它引入。require_once('utils.php');

給 token,將對應關係存到資料庫

$token = generateToken();
$sql = sprintf("INSERT INTO tokens(token, username) Values('%s', '%s')",
  $token, $username
);
$result = $conn -> query($sql);
if (!$result) {
  die($conn -> error);
}

在首頁,調整原本從 cookie 直接拿 username 的機制。變成先檢查 cookie 內有沒有 token,如果有將它拿出來,然後去資料庫查這個 token 對應到的 username。

  $username = NULL;
  if (!empty($_COOKIE['token'])) {
    $token = $_COOKIE['token'];
    $sql = sprintf(
      "select username from tokens where token = '%s'",
      $token
    );
    $result = $conn -> query($sql);
    $row = $result -> fetch_assoc();
    $username = $row['username'];
  }

修改登出的程式碼,原本是將 username 清空。現在除了要將 token 清空,還要將資料庫裡的資料刪掉。

<?php
  // 引入資料庫
  require_once('conn.php');
  // 刪除 token
  $token = $_COOKIE['token'];
  $sql = sprintf('DELETE FROM tokens WHERE token="%s"', $token);
  $conn -> query($sql);
  $expire = time() - 3600;
  setcookie("token", '', $expire);
  header('Location: index.php');
?>

調整 index.php

<?php
  require_once('conn.php');

  $username = NULL;
  if (!empty($_COOKIE['token'])) {
    $token = $_COOKIE['token'];
    $sql = sprintf(
      "select username from tokens where token = '%s'",
      $token
    );
    $result = $conn -> query($sql);
    $row = $result -> fetch_assoc();
    $username = $row['username'];
  }

  $result = $conn->query("SELECT * FROM comments ORDER BY id DESC");
  if (!$result) {
    die('錯誤訊息' . $conn->error);
  }
?>

調整新增留言的部分,原本從 cookie 抓 username 出來查,再去串他的 nickname。現在,要從 token 串 username,再從 username 串 nickname。這個流程很多地方都用的到,可以將他包成一個 funct。

要在 funct 中用 conn,要特別宣告為 global

// utils.php
  function getUserFromToken($token) {
    global $conn;
    $sql = sprintf(
      "select username from tokens where token = '%s'",
      $token
    );
    $result = $conn -> query($sql);
    $row = $result -> fetch_assoc();
    $username = $row['username'];

    $sql = sprintf(
      "SELECT * FROM users WHERE username = '%s'",
      $username
    );
    $result = $conn -> query($sql);
    $row = $result -> fetch_assoc();
    return $row; // username, id, nickname
  }

現在,index.php 可以利用這個 funct 進行變身

// index.php
<?php
  require_once('conn.php');
  require_once('utils.php');
  $username = NULL;
  if (!empty($_COOKIE['token'])) {
    $user = getuserFromToken($_COOKIE['token']);
    $username = $user['username'];
  }

  $result = $conn->query("SELECT * FROM comments ORDER BY id DESC");
  if (!$result) {
    die('錯誤訊息' . $conn->error);
  }
?>

修改新增留言的部分,用 require_once 引入

// handle_add_comment.php
  $user = getUserFromToken($_COOKIE['token']);
  $nickname = $user['nickname'];

小結

用通行證的方式,在 server 中存對應關係。username 只會在 db 看到,token 對外。以上機制的專有名詞:session 機制。php 也有自己的 session 機制。

(上面的還不太熟)


PHP 內建 session 機制

要用內建的 session 都要在第一行加上 session_start()

//handle_login.php
if ($result -> num_rows) {
    $_SESSION['username'] = $username;
    /*
      1. 產生 session id(token)
      2. 把 username 寫入檔案
      3. set-cookie: session-id
    */









Related Posts

React 入門 4 - Hooks: useState

React 入門 4 - Hooks: useState

DE2_115(DAY1)以niosii去控制板子上的led燈

DE2_115(DAY1)以niosii去控制板子上的led燈

留言板、API篇

留言板、API篇


Comments