# 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 值變成其他人登入。

這樣就變成其他人登入並發言了,所以 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 , 也不知道要更改些甚麼。