# 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 , 也不知道要更改些甚麼。