0%

[week 11] 資訊安全 - 雜湊與加密 & 常見攻擊:SQL Injection、XSS

本篇為 [BE101] 用 PHP 與 MySQL 學習後端基礎 這門課程的學習筆記。如有錯誤歡迎指正。

相關筆記:

  • [week 9] 後端基礎 - PHP 語法、資料庫 MySQL
  • [week 9] 利用 PHP 實作留言板 - 初階實作篇
    學習目標:
    
     P1 你知道什麼是雜湊(Hash function)
     P1 你知道什麼是加密(Encryption)
     P1 你知道雜湊與加密的差別
     P1 你知道什麼是 SQL Injection 以及如何防範
     P1 你知道什麼是 XSS 以及如何防範
     P1 你知道為什麼儘管前端做了驗證,後端還是要再做一次驗證
     P2 你知道什麼是 CSRF 以及如何防範

資訊安全

我們先前利用 PHP 實作的初階留言板,其實暗藏許多安全性漏洞,接下來的重點,就是說明這些漏洞如何產生,以及該如何解決這些問題。

用雜湊保護密碼

當我們以「明文」方式將密碼存在資料庫時,可能面臨下列資安風險:

  • 網站管理者隨時能夠查看使用者帳密
  • 如果有駭客成功入侵資料庫,即可竊取所有使用者資料

因此我們需要透過「雜湊」來保護密碼。這也是為什麼,當我們忘記密碼時,網站會要我們重設密碼,而不是得到原先的密碼,因為雜湊不可逆,無法回推得到原碼。

加密?雜湊?

兩者最大差別在於:「加密可逆,雜湊不可逆」。

加密(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 提供內建函式 password_hash() 能夠實現加密, 指令如下:

 <?php
$hash = password_hash("要加密的字串", PASSWORD_DEFAULT);
?>

驗證指令:password_verify("要驗證的字串", "加密過的字串"),會回傳一個 boolean

// 使用範例
<?php 
$password = '1234';    // 原始密碼
$hash_password = password_hash($password, PASSWORD_DEFAULT);

if (password_verify($password , $hash_password)){
  echo "密碼正確";  // true
} else {
  echo "密碼錯誤";  // false
}
?> 

參考資料:

  1. [資訊安全] 密碼存明碼,怎麼不直接去裸奔算了?淺談 Hash , 用雜湊保護密碼
  2. 一次搞懂密碼學中的三兄弟 — Encode、Encrypt 跟 Hash
  3. 加密和雜湊有什麼不一樣? | Just for noting
  4. 現代PHP password_hash 雜湊加密採用隨機SALT 使用方式
  5. PHP password_hash() 函数| 菜鸟教程

常見資安攻擊

以下介紹幾種常見的資安攻擊,其原理與防範方法。

SQL injection

是一種注入式攻擊。發生於應用程式與資料庫層的安全漏洞。

攻擊原理

在輸入資料時,夾帶不正當的 SQL 指令,若網頁忽略字元檢查,資料庫將會視為正常的 SQL 指令並執行惡意程式碼,進而破壞或入侵。

以輸入帳號密碼進行驗證為例:

  • 正常的 SQL 語法

    • 帳號:123
    • 密碼:456
      SELECT * FROM users WHERE username='123' AND password='456';
  • SQL injection

    • 帳號:' or 1=1#
    • 密碼:可輸入任意值(甚至不需輸入)
      SELECT * FROM users WHERE username='' or 1=1# AND password='';
  • ':將 username 的 input 關閉

  • or:條件式的「或是」

  • #:代表註解,後面的條件均會被忽略

  • 由於 WHERE 條件式中的 1=1 恆正,如此即可略過權限檢查,沒有密碼也能成功登入帳號

以之前實作的初階留言版為例:

  1. 登入後在新增留言輸入:'), ('admin', 'Hacking!')#
  2. SQL 指令就變成:
    INSERT INTO comments(nickname, content)
    VALUES('aaa', ''), ('admin', 'Hacking!')#
  3. 執行後會新增兩筆留言,nickname 分別是 aaa 和 admin

防範方法:Prepare Statement 預處理

透過 Prepare Statement 進行跳脫,以防止 SQL 注入。

步驟如下:

  1. 將 SQL 語法儲存在變數 $sql
  2. 使用 prepare() 預處理 $sql:此時只傳送佔位符號 ?,可避免利用拼接 SQL 字串語句的攻擊
  3. 使用 bind_param() 帶入參數。常用參數類型:
    • s:string(字串)
    • i:integer(整數)
  4. 呼叫 execute() 執行 SQL 語法:此時才會將參數傳送給資料庫
  5. 若執行成功,則使用 get_result() 取出執行結果

程式碼範例:

<?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
  2. Php中用PDO查詢Mysql來避免SQL隱碼攻擊風險的方法
  3. PreparedStatement 如何防止SQL注入

XSS 攻擊

全名是 Cross Site Scripting(跨網站指令碼攻擊),為了不與 CSS 混淆,故簡稱 XSS。

是一種注入式攻擊。通常是透過 JavaScript 或 HTML,因此又被稱作 JavaScript Injection 攻擊。

攻擊原理

攻擊者利用網頁開發時留下的安全漏洞,在網頁插入惡意程式碼,讓使用者載入並執行惡意程式,攻擊者可藉此取得更高的權限,或是竊取使用者的 Cookie 等資料。

XSS 常見的攻擊種類大致上可分為兩種:

  • Reflected XSS(反射型)

以網頁輸入欄位為例。若在輸入欄位內刻意植入 Javascript 語法,例如下列程式碼,即可竊取使用者的 cookie 資料:

<script>alert(document.cookie);</script>
  • Stored XSS(儲存型)

以留言板為例。攻擊者將惡意程式碼透過「新增留言」寫入資料庫,當使用者瀏覽到該頁面時,就會觸發程式碼,達到攻擊目的。

防範方法:過濾特殊字元

責任主要還是歸咎於網頁開發時留下的安全漏洞,但 Server 端與 Client 端可以藉由下列方法,來避免遭受攻擊:

Client 端

  1. 定期更新瀏覽器至最新版本
  2. 避免點擊來路不明的網址

Server 端

  1. 過濾特殊字元:過濾所有 Client 端提供的內容,並轉譯成純文字。例如:使用 PHP 內建函式 htmlentities()htmlspecialchars()
<?php
// utils.php
  function escape($str) {
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
  }
?>
  1. 限制輸入內容長度或型態。雖然可藉由引入外部 JavaScript 來繞過,但還是能提高 XSS 攻擊難度

參考資料:

  1. Code Injection Attack – Cross-Site Scripting Attack – 三甲科技
  2. 前端安全系列(一):如何防止XSS攻擊?
  3. [Day24] 攻擊行為-反射式跨網站指令碼 Reflected XSS

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
  4. 由於瀏覽器的機制,發送 request 給某個網域時,會附帶關聯的 cookie
  5. 網站 A 誤以為是合法請求,攻擊者即可假冒使用者身分進行操作

簡言之,CSRF 就是「在不同 domain 下,偽造使用者本人發出的 request」。

防範方法

Client 端

由於 CSRF 攻擊的必要條件,是使用者在被攻擊的網頁處於「已登入狀態」。使用者在每次使用完網站就登出,即可避免 CSRF 攻擊。

Server 端

  1. 加上圖形驗證碼(或簡訊驗證碼等)

在網站加上驗證碼,可多一道檢查程序,常用於金流相關操作。

  • 缺點:每次都要進行驗證,可能造成使用者體驗不佳
  1. 加上 CSRF token
  • 產生:Server
  • 儲存:Server

form 裡面加上一個 hidden 的欄位,叫做 csrftoken,填入的值為 Server 隨機產生的亂碼,並且存在 Server 的 session 中。

按下 submit 後,Server 會比對表單中的 csrftoken 與存在 Server 端的是否相同。

// 範例程式碼
<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 來攻擊目標網站
  1. Double Submit Cookie
  • 產生:Server
  • 儲存:Client

與前一種解法相似,好處是不需要存任何東西在 Server 端。

而是在 cookie 與 form 設置相同的 csrftoken,利用「cookie 只會從相同 domain 帶上來」機制,使攻擊者無法從不同 domain 戴上此 cookie。

// 範例程式碼
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,藉此進行攻擊
  1. Client 端生成的 Double Submit Cookie
  • 產生:Client
  • 儲存:Client

和前面提的 Double Submit Cookie 核心概念相同。不同之處在於「改由 Client 端」生成 csrf token。

生成之後放到 form 裡面以及寫到 cookie,其他流程就和之前相同。
此 cookie 只是確保攻擊者無法取得,不含任何資訊,因此由 Client 或 Server 生成都是一樣的。

  • 例如:library axios 就有提供此功能。只要設定好 header 與 cookie 名稱,設定好之後的每一個 request,即可自動在 header 統一加上 cookie 值
// 範例程式碼
// `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

CSRF 之所以能夠成立,是瀏覽器的機制所導致,但我們也能從瀏覽器著手進行防禦,也就是透過 SameSite cookie。其原理是「幫 Cookie 再加上一層驗證」,防止來自不同 domain 的請求。

做法是在 Set-cookie 的 session_id 後加上 SameSite 即可啟用此功能。由於這是較新的功能,目前瀏覽器中只有 Chrome 支援,使用前須注意。

使用範例如下:

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 技術共筆部落格
  2. [技術分享] Cross-site Request Forgery (Part 1)
  3. [第十二週] 資訊安全 - 常見攻擊:CSRF
  4. 程式猿必讀-防範CSRF跨站請求偽造