###### tags: `Back-End` `php` `資訊安全` # [week 11] 資訊安全 - 雜湊與加密 & 常見攻擊:SQL Injection、XSS > 本篇為 [[BE101] 用 PHP 與 MySQL 學習後端基礎](https://lidemy.com/p/be101-php-mysql) 這門課程的學習筆記。如有錯誤歡迎指正。 相關筆記: - [[week 9] 後端基礎 - PHP 語法、資料庫 MySQL](https://github.com/heidiliu2020/This-is-Codediary/blob/master/week9_%E5%BE%8C%E7%AB%AF%E5%9F%BA%E7%A4%8E_PHP%E3%80%81MySQL.md) - [[week 9] 利用 PHP 實作留言板 - 初階實作篇](https://github.com/heidiliu2020/This-is-Codediary/blob/week11/week9_%E5%88%A9%E7%94%A8PHP%E5%AF%A6%E4%BD%9C%E7%95%99%E8%A8%80%E6%9D%BF-%E5%88%9D%E9%9A%8E%E5%AF%A6%E4%BD%9C%E7%AF%87.md) ``` 學習目標: P1 你知道什麼是雜湊(Hash function) P1 你知道什麼是加密(Encryption) P1 你知道雜湊與加密的差別 P1 你知道什麼是 SQL Injection 以及如何防範 P1 你知道什麼是 XSS 以及如何防範 P1 你知道為什麼儘管前端做了驗證,後端還是要再做一次驗證 P2 你知道什麼是 CSRF 以及如何防範 ``` --- ## 資訊安全 我們先前[利用 PHP 實作的初階留言板](https://github.com/heidiliu2020/This-is-Codediary/blob/master/week9_%E5%88%A9%E7%94%A8PHP%E5%AF%A6%E4%BD%9C%E7%95%99%E8%A8%80%E6%9D%BF-%E5%88%9D%E9%9A%8E%E5%AF%A6%E4%BD%9C%E7%AF%87.md),其實暗藏許多安全性漏洞,接下來的重點,就是說明這些漏洞如何產生,以及該如何解決這些問題。 ## 用雜湊保護密碼 當我們以「明文」方式將密碼存在資料庫時,可能面臨下列資安風險: - 網站管理者隨時能夠查看使用者帳密 - 如果有駭客成功入侵資料庫,即可竊取所有使用者資料 因此我們需要透過「雜湊」來保護密碼。這也是為什麼,當我們忘記密碼時,網站會要我們重設密碼,而不是得到原先的密碼,因為雜湊不可逆,無法回推得到原碼。 ## 加密?雜湊? 兩者最大差別在於:「加密可逆,雜湊不可逆」。 ![](https://i.imgur.com/ymLoNYw.png) ### 加密(Encrypt) - 一對一,可逆 - 加密與解密必須要有金鑰(key)才能進行 - 對稱式加密 - 常見演算法:AES、DES、3DES - 加密解密都使用同一個 key,速度快,通常用於加密大量資料 - 資料傳輸前,雙方必須先講好密碼,不易日後管理,因此有了非對稱式加密 - 非對稱式加密 - 常見演算法:RSA、DSA、ECC - 加密和解密使用不同的金鑰,速度慢,通常用於少量數據加密 - 一對金鑰由公鑰(可交給任何請求方)和私鑰(由一方保管)組成,可互相解密加密 - 可使用數位簽章(Digital Signature)確認是否為本人傳遞訊息,形成雙重保障 ### 雜湊(Hash) - 多對一,不可逆,因此可有效保護密碼 - 將`不定長度、無窮可能`的輸入,透過雜湊演算法,轉換成`固定長度`的雜湊值 - 雖然機率極低,但不同輸入可能產生相同的雜湊值,這種情形稱為碰撞(collision) - 對伺服器來說,安全性越高的雜湊函數也代表速度越慢 - 用途: - 檔案校驗碼(Checksum):檢查檔案是否正確 - 不需要被還原的資料:避免明文儲存密碼 - 常見雜湊演算法 - MD5:已被證實不夠安全 - SHA-1:已被證實不夠安全 - SHA-256 - RIPEMD-160 - 常見攻擊方法 - 暴力破解(brute-force):嘗試所有數列組合,非常耗時 - 字典法(Dictionary Attacke):嘗試常見的密碼組合,為主流方法 - 彩虹表(rainbow table):類似暴力破解,使用預先計算的雜湊演算法,以查表方式破解 - 解決方式:將密碼加鹽(salt)形成新的雜湊值,即可提高安全性 - 例如:1234-(加鹽)→1234XXX ### PHP 內建雜湊函式:`password_hash()` [PHP 提供內建函式 ](https://www.php.net/manual/en/function.password-hash.php)`password_hash()` 能夠實現加密, 指令如下: ```php <?php $hash = password_hash("要加密的字串", PASSWORD_DEFAULT); ?> ``` 驗證指令:`password_verify("要驗證的字串", "加密過的字串")`,會回傳一個 boolean ```php= // 使用範例 <?php $password = '1234'; // 原始密碼 $hash_password = password_hash($password, PASSWORD_DEFAULT); if (password_verify($password , $hash_password)){ echo "密碼正確"; // true } else { echo "密碼錯誤"; // false } ?> ``` 參考資料: 1. [[資訊安全] 密碼存明碼,怎麼不直接去裸奔算了?淺談 Hash , 用雜湊保護密碼](https://tinyurl.com/y65pgo4w) 2. [一次搞懂密碼學中的三兄弟 — Encode、Encrypt 跟 Hash](https://medium.com/starbugs/what-are-encoding-encrypt-and-hashing-4b03d40e7b0c) 3. [加密和雜湊有什麼不一樣? | Just for noting](https://blog.m157q.tw/posts/2017/12/25/differences-between-encryption-and-hashing/) 4. [現代PHP password_hash 雜湊加密採用隨機SALT 使用方式](https://adon988.logdown.com/posts/2513623-modern-php-password-hash-hash-encryption-using-random-salt-uses) 5. [PHP password_hash() 函数| 菜鸟教程](https://www.runoob.com/php/php-password_hash.html) --- ## 常見資安攻擊 以下介紹幾種常見的資安攻擊,其原理與防範方法。 ## SQL injection 是一種注入式攻擊。發生於應用程式與資料庫層的安全漏洞。 ### 攻擊原理 在輸入資料時,夾帶不正當的 SQL 指令,若網頁忽略字元檢查,資料庫將會視為正常的 SQL 指令並執行惡意程式碼,進而破壞或入侵。 #### 以輸入帳號密碼進行驗證為例: - 正常的 SQL 語法 - 帳號:123 - 密碼:456 ```php= SELECT * FROM users WHERE username='123' AND password='456'; ``` - SQL injection - 帳號:`' or 1=1#` - 密碼:可輸入任意值(甚至不需輸入) ```php= SELECT * FROM users WHERE username='' or 1=1# AND password=''; ``` - `'`:將 username 的 input 關閉 - `or`:條件式的「或是」 - `#`:代表註解,後面的條件均會被忽略 - 由於 `WHERE` 條件式中的 `1=1` 恆正,如此即可略過權限檢查,沒有密碼也能成功登入帳號 #### 以之前實作的初階留言版為例: 1. 登入後在新增留言輸入:`'), ('admin', 'Hacking!')#` 2. SQL 指令就變成: ```php= INSERT INTO comments(nickname, content) VALUES('aaa', ''), ('admin', 'Hacking!')# ``` 3. 執行後會新增兩筆留言,nickname 分別是 aaa 和 admin ![](https://i.imgur.com/zLfxN7g.png) ### 防範方法:Prepare Statement 預處理 透過 Prepare Statement 進行跳脫,以防止 SQL 注入。 步驟如下: 1. 將 SQL 語法儲存在變數 `$sql` 2. 使用 `prepare()` 預處理 `$sql`:此時只傳送佔位符號 `?`,可避免利用拼接 SQL 字串語句的攻擊 4. 使用 `bind_param()` 帶入參數。常用參數類型: - `s`:string(字串) - `i`:integer(整數) 5. 呼叫 `execute()` 執行 SQL 語法:此時才會將參數傳送給資料庫 6. 若執行成功,則使用 `get_result()` 取出執行結果 程式碼範例: ```php= <?php // SQL 語法 $sql = "INSERT INTO comments(nickname, content) VALUES(?, ?)"; // prepare() 預處理 $stmt = $conn->prepare($sql); // bind_param() 帶入參數 $stmt->bind_param('ss', $nickname, $content); // execute() 執行 SQL 語法 $result = $stmt->execute(); // 確認是否執行成功 if (!$result) { die($conn->error); // 取出執行結果 $result = $stmt->get_result(); } ``` 參考資料: 1. [[Postx1] 攻擊行為-SQL 資料隱碼攻擊 SQL injection](https://ithelp.ithome.com.tw/articles/10189201) 2. [Php中用PDO查詢Mysql來避免SQL隱碼攻擊風險的方法](https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/240808/) 3. [PreparedStatement 如何防止SQL注入](https://www.itread01.com/p/1403324.html) --- ## XSS 攻擊 全名是 Cross Site Scripting(跨網站指令碼攻擊),為了不與 CSS 混淆,故簡稱 XSS。 是一種注入式攻擊。通常是透過 JavaScript 或 HTML,因此又被稱作 JavaScript Injection 攻擊。 ### 攻擊原理 攻擊者利用網頁開發時留下的安全漏洞,在網頁插入惡意程式碼,讓使用者載入並執行惡意程式,攻擊者可藉此取得更高的權限,或是竊取使用者的 Cookie 等資料。 XSS 常見的攻擊種類大致上可分為兩種: - Reflected XSS(反射型) ![](https://i.imgur.com/zNQGEPN.png) 以網頁輸入欄位為例。若在輸入欄位內刻意植入 Javascript 語法,例如下列程式碼,即可竊取使用者的 cookie 資料: ```= <script>alert(document.cookie);</script> ``` - Stored XSS(儲存型) ![](https://i.imgur.com/A5qvwBe.png) 以留言板為例。攻擊者將惡意程式碼透過「新增留言」寫入資料庫,當使用者瀏覽到該頁面時,就會觸發程式碼,達到攻擊目的。 ### 防範方法:過濾特殊字元 責任主要還是歸咎於網頁開發時留下的安全漏洞,但 Server 端與 Client 端可以藉由下列方法,來避免遭受攻擊: #### Client 端 1. 定期更新瀏覽器至最新版本 2. 避免點擊來路不明的網址 #### Server 端 1. 過濾特殊字元:過濾所有 Client 端提供的內容,並轉譯成純文字。例如:使用 PHP 內建函式 `htmlentities()`或 `htmlspecialchars()` ```php= <?php // utils.php function escape($str) { return htmlspecialchars($str, ENT_QUOTES, 'UTF-8'); } ?> ``` 2. 限制輸入內容長度或型態。雖然可藉由引入外部 JavaScript 來繞過,但還是能提高 XSS 攻擊難度 參考資料: 1. [Code Injection Attack – Cross-Site Scripting Attack – 三甲科技](https://cms.aaasec.com.tw/index.php/2018/09/21/code-injection-attack-cross-site-scripting-attack/) 2. [前端安全系列(一):如何防止XSS攻擊?](https://www.itread01.com/content/1544575149.html) 3. [[Day24] 攻擊行為-反射式跨網站指令碼 Reflected XSS](https://ithelp.ithome.com.tw/articles/10188646) --- ## CSRF 全名是 Cross Site Request Forgery(跨站請求偽造)。也被稱為 one-click attack 或 session riding。 和 XSS 同樣是跨站式的請求攻擊,兩者卻有明顯區別,不同之處在於: - XSS:透過在網頁輸入惡意程式碼的方式來進行攻擊 - 使用者對目標網站的信任 - CSRF:攻擊者利用網站使用者的身分發送請求,去執行一些未經授權的操作 - 目標網站對該使用者的信任 ### 攻擊原理 要完成一個 CRSF 攻擊,必須條件是「使用者仍保持登入狀態」。 假設 A 為目標網站,B 為惡意網站,步驟大致如下: 1. 使用者登入網站 A 2. 網站 A 返回 session id 等資訊(使用 cookie 儲存) 3. 使用者訪問網站 B,攻擊者利用隱藏圖片或表單,讓使用者在不知情的情況發送 request 到網站 A 5. 由於瀏覽器的機制,發送 request 給某個網域時,會附帶關聯的 cookie 6. 網站 A 誤以為是合法請求,攻擊者即可假冒使用者身分進行操作 簡言之,CSRF 就是「在不同 domain 下,偽造使用者本人發出的 request」。 ### 防範方法 #### Client 端 由於 CSRF 攻擊的必要條件,是使用者在被攻擊的網頁處於「已登入狀態」。使用者在每次使用完網站就登出,即可避免 CSRF 攻擊。 #### Server 端 1. 加上圖形驗證碼(或簡訊驗證碼等) 在網站加上驗證碼,可多一道檢查程序,常用於金流相關操作。 - 缺點:每次都要進行驗證,可能造成使用者體驗不佳 2. 加上 CSRF token - 產生:Server - 儲存:Server 在 `form` 裡面加上一個 `hidden` 的欄位,叫做 `csrftoken`,填入的值為 Server 隨機產生的亂碼,並且存在 Server 的 session 中。 按下 submit 後,Server 會比對表單中的 csrftoken 與存在 Server 端的是否相同。 ```php= // 範例程式碼 <form action="https://myblog.com/delete" method="POST"> <input type="hidden" name="id" value="3"/> <input type="hidden" name="csrftoken" value="<亂碼>"/> <input type="submit" value="刪除文章"/> </form> ``` - 缺點:若攻擊者先發出 request,還是能取得 csrftoken 來攻擊目標網站 3. Double Submit Cookie - 產生:Server - 儲存:Client 與前一種解法相似,好處是不需要存任何東西在 Server 端。 而是在 cookie 與 form 設置相同的 csrftoken,利用「cookie 只會從相同 domain 帶上來」機制,使攻擊者無法從不同 domain 戴上此 cookie。 ```php= // 範例程式碼 Set-Cookie: csrftoken=<亂碼> <form action="https://myblog.com/delete" method="POST"> <input type="hidden" name="id" value="3"/> <input type="hidden" name="csrftoken" value="<亂碼>"/> <input type="submit" value="刪除文章"/> </form> ``` - 缺點:攻擊者若掌握使用者底下任何一個 subdomain,就能夠幫使用者寫 cookie,藉此進行攻擊 4. Client 端生成的 Double Submit Cookie - 產生:Client - 儲存:Client 和前面提的 Double Submit Cookie 核心概念相同。不同之處在於「改由 Client 端」生成 csrf token。 生成之後放到 form 裡面以及寫到 cookie,其他流程就和之前相同。 此 cookie 只是確保攻擊者無法取得,不含任何資訊,因此由 Client 或 Server 生成都是一樣的。 - 例如:library axios 就有提供此功能。只要設定好 header 與 cookie 名稱,設定好之後的每一個 request,即可自動在 header 統一加上 cookie 值 ```php= // 範例程式碼 // `xsrfCookieName` is the name of the cookie to use as a value for xsrf token xsrfCookieName: 'XSRF-TOKEN', // default // `xsrfHeaderName` is the name of the http header that carries the xsrf token value xsrfHeaderName: 'X-XSRF-TOKEN', // default ``` #### 瀏覽器端的防禦:SameSite cookie CSRF 之所以能夠成立,是瀏覽器的機制所導致,但我們也能從瀏覽器著手進行防禦,也就是透過 SameSite cookie。其原理是「幫 Cookie 再加上一層驗證」,防止來自不同 domain 的請求。 做法是在 Set-cookie 的 session_id 後加上 SameSite 即可啟用此功能。由於這是較新的功能,目前瀏覽器中只有 Chrome 支援,使用前須注意。 使用範例如下: ```php= Set-Cookie: session_id=<亂碼>; SameSite ``` SameSite 有分 Strict(預設)與 Lax 兩種模式: - Strict 模式(嚴格) - 使用 `<a href="">`, `<form>`, `new XMLHttpRequest` 等標籤 - 只要瀏覽器驗證不是同一個 domain 發出的 request,就不會帶上此 cookie - 由於連結不會帶上 cookie,對於使用者體驗並不佳 - Lax 模式(寬鬆) - 上述標籤都還是會帶上 cookie - 除了 Get 刑事,其他方法如 POST、DELETE、PUT 等都不會帶上 cookie - 意即 Lax 模式無法擋掉 GET 形式的 CSRF 參考資料: 1. [讓我們來談談CSRF - TechBridge 技術共筆部落格](https://blog.techbridge.cc/2017/02/25/csrf-introduction/) 2. [[技術分享] Cross-site Request Forgery (Part 1)](https://cyrilwang.pixnet.net/blog/post/31813568-%5B%E6%8A%80%E8%A1%93%E5%88%86%E4%BA%AB%5D-cross-site-request-forgery-(part-1)) 3. [[第十二週] 資訊安全 - 常見攻擊:CSRF](https://yakimhsu.com/project/project_w12_Info_Security-CSRF.html) 4. [程式猿必讀-防範CSRF跨站請求偽造](https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/109775/)