---
# System prepended metadata

title: Week9 6-1 、 修正問題篇-別相信客戶端資料
tags: [2020十一月第二周, Lidemy心得, 進度筆記, BE101, PHP, 2020十一月第一周]

---

# Week9 6-1 、 修正問題篇-別相信客戶端資料
###### tags: `BE101` `PHP` `2020十一月第一周` `進度筆記` `Lidemy心得`

***

- 修正留言板問題，對留言板攻擊，測試。  
- ==永遠抱持懷疑，因為 Client data 是可被更改的。==  
```
如 User 的電腦就是 Client 端；架在 Server 端資料被改就是被黑客入侵了。
```  
- Client 端的 data 在 Server 端都要經過驗證。  
```
Cookie 、 IP 設定，都能做更改...
```
- ==假設程式碼被偷，如何要維持資料安全性？==  

***

# Week9 6-2 、 偽造身份的問題
###### tags: `BE101` `PHP` `2020十一月第二周` `進度筆記` `Lidemy心得`

==Cookie 可以被更改，代表身分可以被偽造。==

***

- 如 index.php 檔案:
```
  <main class="board">
      <div>
        <?php if (!$username) { ?>
          <a class="board__btn" href="register.php">註冊</a>
          <a class="board__btn" href="login.php">登入</a>
        <?php } else { ?>
          <a class="board__btn" href="logout.php">登出</a>
        <?php } ?>
		<h3>你好！<?php echo $username; ?></h3>
      </div>
```
- 並打開 dev tool 觀看 Application → Cookies 更改 Value 值變成其他人登入。  

![](https://i.imgur.com/JVz7l3z.png)  

這樣就變成其他人登入並發言了，所以 Cookies 就能被串改。    

***

# Week9 6-3 、 修正通行證機制簡介與實作
###### tags: `BE101` `PHP` `2020十一月第二周` `進度筆記` `Lidemy心得`

==Cookie 可以被更改，代表身分可以被偽造。==
- username 在 Cookie 中可以被更改，因此可以用通行證方式。  
- 給他一張不能被偽造的通行證(session)。  

***

## 設置一個通行證
設置 Cookie 的時候:  
```
瀏覽器(收到 header 後)會幫忙設置 token 並存在瀏覽器內後 request 登入 ↔ Server (經由 server 設定的 Set-Cookie: `token`) ↔ index.php 
```
- Token 是隨機亂數產生的。 
- 因此要在 Server 設立一個表格，設置 Token 和對應的 username 。 
- 因此攻擊者要猜的可能性會很低。 

## 建立 database 
資料表名稱: tokens 
- 名稱(id) 、 型態(INT) 、 AUTO_Increment 。 
- token 、 VARCHAR 、 長度 64 。  
- username 、 VARCHAR 、 長度 64 。  

## 第一步-產生 Token 
在 handle_login.php 檔案中新增下: 
從 utils.php (aka. utilitis 可共用)檔案引入 token 。  
```
require_once('utils.php');
```
接著產生 token (用 rand 產生隨機數字):  

```
if ($result->num_rows) {
    // 如果有資料的話登入成功；建立 token 並儲存
    $token = generateToken();
    $sql = sprintf(
      "insert into tokens(token,username) values('%s', '%s')",
      $token,
      $username
    );
    $result = $conn->query($sql);
    if (!$result) {
      die($conn->error);
    }


    $expire = time() + 3600 * 24 * 30; // 30 day
    setcookie("token", $token, $expire); // 把 token 傳到 client 端去
    header("Location: index.php");
  } else {
    header("Location: login.php?errCode=2");
  }

?>
```
- 登入的時候 Server 會產生 token 。
```
用 dev tool 看 index.php 的時候可以看到 cookies 的 value 會是一組隨機的字母。 
```


***

## 設立一個共用檔案產生 Token

接著在 utils.php 檔案中新增: 
- 用迴圈產生隨機字母或數字。  
- 可以用 php 語言 `chr` 產生字母。   
```
<?php
  require_once("conn.php");

  function generateToken() {
    $s = '';
    for($i=1; $i<=16; $i++) {
      $s .= chr(rand(65,90)); // 跑 16 圈，每圈產生隨機的英文字母，65~90 間隨機的字母
    }
    return $s;
  }

  function getUserFromToken($token) {
    global $conn; // 要先在函式內宣告全域變數 $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
    ); //再去 users 內查一次表，再把結果拿回來
    $result = $conn->query($sql);
    $row = $result->fetch_assoc();
    return $row; // 這個 row 是從 username, id, nickname 來的資料
  }
?>
```

***

## 在首頁中引入共用檔案
index.php 檔案中: 
```
<?php
  require_once("conn.php");
  require_once("utils.php");

  $username = NULL;
  if (!empty($_COOKIE['token'])) {
    $user = getUserFromToken($_COOKIE['token']); // 如果用 token 去資料庫查不到東西會登出
    $username = $user['username'];
  }

  $result = $conn->query("select * from comments order by id desc");
  if (!$result) {
    die('Error:' . $conn->error); // 這段要放下來，再做一次 query
  }
  
?>

```
- 檢查 COOKIE 並確定 token 有無數值 `if (!empty($_COOKIE['token']))` 。   
- 如果有數值 `$user = getUserFromToken($_COOKIE['token']);` 。
- 把 token 取出去資料庫 `$result = $conn->query("select * from comments order by id desc");` 。 
- 並看 token 有無對應到 username `$username = $user['username'];` ， 有對應就取出。 
- 取出後就知道 user 是誰了。 

***

## 登出方式修正
```
<?php
  require_once('conn.php'); // 資料庫引入

  // 刪除 token
  $token = $_COOKIE['token'];
  $sql = sprintf(
    "delete from tokens where token='%s'",
    $token
  ); // 刪除 token 並把對應的 token 代入
  $conn->query($sql);
  setcookie("token", "", time() - 3600);
  header("Location: index.php");
?>
```
- 第一，要先把 token 清空。   
- 第二，要把資料庫內的 token 刪除。  

***

## 新增留言部分修改
handle_add_comment.php 檔案修改: 
```
<?php
  require_once('conn.php');
  require_once('utils.php');

  if (
    empty($_POST['content'])
  ) {
    header('Location: index.php?errCode=1');
    die('資料不齊全');
  }

  $user = getUserFromToken($_COOKIE['token']); //只是要 nickname
  $nickname = $user['nickname']; // 把 nickname 指定給 user 

  $content = $_POST['content'];
  $sql = sprintf(
    "insert into comments(nickname, content) values('%s', '%s')",
    $nickname,
    $content
  );
  $result = $conn->query($sql);
  if (!$result) {
    die($conn->error);
  }

  header("Location: index.php");
?>
```
- 原本是從 cookie 抓 username 出來查。  
- 查到是誰之後才知道 nickname 。  
- 因此安全點是要，從 token 去查 username 再從 username 去查 nickname 。
- 因此 username 只有在 database 內才會看得到(對內)。 
- 對外則是顯示 token ， 也不知道要更改些甚麼。  
  