iT邦幫忙

2024 iThome 鐵人賽

DAY 9
0
Security

資安這條路:系統化學習網站安全與網站滲透測試系列 第 9

資安這條路:Day 9 從登入功能了解 SQL injection

  • 分享至 

  • xImage
  •  

拆解 SQL injection 問題

a) 現象(question)

  • SQL injection 是常見且危險的 Web 應用程式漏洞
  • 許多資料外洩事件源於 SQL injection 攻擊
  • 即使是經驗豐富的開發者也可能犯這類錯誤

b) 難題(problem)

  • 如何識別和預防 SQL injection 漏洞
  • 如何在不影響功能的情況下安全處理使用者輸入
  • 如何在現有系統中檢測和修復 SQL injection 漏洞

c) 阻礙

  • 開發人員可能對安全編碼實踐缺乏了解
  • 舊有系統可能存在大量遺留程式碼
  • 修復漏洞可能需要大範圍的程式碼重構

d) 期待

  • 能夠開發出安全的、抵抗 SQL injection 的應用程式
  • 提高對 SQL injection 的認識和防禦能力
  • 在安全和功能之間找到平衡

e) 目標

  • 掌握 SQL injection 的原理和各種類型
  • 學習和實施有效的預防技術
  • 能夠進行 SQL injection 的安全測試和修復

引導學習 SQL injection

  1. 理解基礎概念:

    • 什麼是 SQL injection?為什麼它如此危險?
    • SQL injection 的工作原理是什麼?
    • 常見的 SQL injection 類型有哪些?
  2. 識別漏洞:

    • 如何識別容易受到 SQL injection 攻擊的程式碼?
    • 哪些編程習慣會增加 SQL injection 的風險?
    • 如何使用自動化工具檢測 SQL injection 漏洞?
  3. 預防技術:

    • 參數化查詢如何預防 SQL injection?
    • 存過程在防禦 SQL injection 中的作用是什麼?
    • 如何正確使用 ORM(對象關係映射)來預防 SQL injection?
  4. 實踐練習:

    • 設置一個包含 SQL injection 漏洞的測試環境
    • 嘗試不同類型的 SQL injection 攻擊
    • 實施各種防禦技術,並驗證其有效性
  5. 高級主題:

    • 如何應對二階 SQL injection?
    • 在 NoSQL 資料庫中,injection 攻擊有何不同?
    • 如何在大型、複雜的應用程式中系統地解決 SQL injection 問題?
  6. 持續學習:

    • 如何跟蹤最新的 SQL injection 技術和防禦方法?
    • 參與哪些社區或資源可以提高 SQL injection 防禦能力?
    • 如何將 SQL injection 防禦納入持續集成/持續部署(CI/CD)流程?

理解 SQL injection 基礎

什麼是 SQL injection?

為什麼被視為重要的安全問題?
SQL injection 是一種針對資料庫驅動的應用程式的攻擊技術。攻擊者透過在輸入欄位中插入惡意的 SQL 程式碼,來操縱或破壞應用程式的資料庫查詢。

SQL injection 被視為重要的安全問題,因為它可能導致未授權訪問、資料洩露、資料篡改,甚至是整個系統的被接管。這種攻擊方式利用了應用程式對使用者輸入的信任,可能造成嚴重的安全漏洞。

SQL injection 的工作原理是什麼?

SQL injection 的工作原理是透過在使用者輸入中插入特製的 SQL 程式碼片段,使得原本的 SQL 查詢的語義被改變。在文章中的不安全模式下,使用者輸入直接被拼接到 SQL 查詢字串中,例如:

const result = await db.query(`SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`);

這種方式使得攻擊者可以輸入如 ' or '1'='1' -- - 這樣的內容,改變查詢的邏輯,繞過身份驗證。

除了文章中展示的基本 SQL injection,還有哪些其他類型?

  • 基於錯誤的 SQL injection:利用應用程式回傳的錯誤訊息來推斷資料庫結構。
  • 基於時間的盲注:透過觀察查詢執行時間來推斷查詢結果。
  • 聯合查詢攻擊:使用 UNION 語句來取得額外的資料。
  • 儲存過程注入:針對使用儲存過程的資料庫系統的攻擊。
  • 二次注入:利用已儲存在資料庫中的惡意資料進行攻擊。

識別漏洞

分析不安全程式碼

指出哪些部分容易受到 SQL injection 攻擊。
不安全程式碼主要體現在直接將使用者輸入拼接到 SQL 查詢字串中:

const result = await db.query(`SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`);

這行程式碼中,usernamepassword 變量直接插入 SQL 字串,沒有進行任何轉義或驗證。

為什麼直接將使用者輸入拼接到 SQL 查詢中是危險的。

直接拼接使用者輸入到 SQL 查詢中是危險的,因為:

  • 攻擊者可以輸入特殊字串(如單引號)來改變 SQL 語句的結構。
  • 惡意輸入可以增加額外的 SQL 命令,執行未預期的操作。
  • 它允許攻擊者繞過應用程式的邏輯檢查,直接與資料庫互動。

還有哪些地方可能存在 SQL injection 風險

  • 搜尋功能:使用者輸入直接用於構建查詢條件。
  • 資料排序:使用者可能控制 ORDER BY 子句。
  • 使用者註冊:插入新使用者資料時可能存在風險。
  • 資料報表生成:動態生成的報表查詢可能受到攻擊。
  • API 端點:接受外部參數並用於資料庫操作的 API。

預防技術

深入解釋文章中使用的參數化查詢技術,為什麼它能有效預防 SQL injection?

文章中的安全模式使用了參數化查詢:

const result = await db.query('SELECT * FROM users WHERE username = $1', [username]);

參數化查詢將 SQL 語句結構和資料分開處理。$1 是參數佔位符,實際值作為獨立參數傳遞。

這種方法能有效預防 SQL injection,因為:

  • 資料庫引擎會將參數值視為純資料,而不是 SQL 語句的一部分。
  • 即使輸入中包含 SQL 關鍵字或特殊字串,也不會改變原始查詢的結構。

討論其他可能的預防措施

  • 儲存過程:將 SQL 邏輯封裝在資料庫中,減少直接的 SQL 操作。
  • ORM:抽象化資料庫操作,自動處理參數化和轉義。
  • 輸入驗證:在應用程式層面過濾和清理使用者輸入。
  • 最小權限原則:限制應用程式使用的資料庫使用者權限。
  • 錯誤處理:避免向使用者展示詳細的錯誤信息。

分析文章中的安全模式實現,並思考如何進一步改進。

文章中的安全模式已經使用了參數化查詢,這是一個很好的開始。進一步改進可以包括:

  • 使用 ORM 框架,如 Sequelize,進一步抽象化資料庫操作。
  • 實施嚴格的輸入驗證,限制輸入的長度和字串類型。
  • 使用預編譯語句,提高性能和安全性。
  • 實施錯誤日誌記錄,但避免向使用者展示詳細錯誤。
  • 定期進行安全審計和滲透測試。

實作

為了展示 SQL injection 且保留過於詳細的錯誤訊息的問題,因此設計一個功能提供給學習者可以切換不同的弱點。

前端修改

  • 編輯 web/public/login.html

HTML

  • 總標籤 "安全模式"
  • 兩個單選按鈕(radio buttons)
    • 一個標記為 "安全",值為 "true",預設被選中
    • 另一個標記為 "不安全",值為 "false"
  • 兩個按鈕共享 name="safemode",
    • 代表兩個屬於同一組
    • 使用者只能選擇其中之一
<!-- 前面省略 -->
            <!-- 安全/不安全切換 -->
            <label for="safemode">安全模式</label>
            <div>
                <input type="radio" id="safemode-true" name="safemode" value="true" checked>
                <label for="safemode-true">安全</label>
            </div>
            <div>
                <input type="radio" id="safemode-false" name="safemode" value="false">
                <label for="safemode-false">不安全</label>
            </div>

JS(1/2)

  • 使用 querySelector 查找被選中的 safemode 單選按鈕
  • 取得該按鈕的 value 屬性值 (即 "true" 或 "false")
  • 將這個值儲存在 safemode 變量中
            // 前省略
            // 取得安全模式的值
            const safemode = document.querySelector('input[name="safemode"]:checked').value;

JS(2/2)

  • 使用 JSON.stringify 將一個物件轉換為 JSON 字串
  • 這個物件包含三個屬性: username, password, 和 safemode
  • safemode 的值就是新增取得的安全模式設定值
            // 前省略
                    body: JSON.stringify({ username, password, safemode }),

後端修改

  • web/controllers/authController.js
    • 針對登入當中多一個欄位用來切換兩種問題
    • safemode 從前端會多送一個資訊給後端來查看
    • 透過 if 迴圈來確認是哪一個模式

登入方法

  • 針對前端送進來的 safemode 從 請求中的 body 取得
async login(req, res) {
    const { username, password, safemode } = req.body;
    try {
        // ...
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
},

安全模式:查詢

  • 差異在於如何查詢: 參術化查詢
  • 使用 $1 作為參數佔位符
    • 再將實際值 [username] 作為參數傳遞
    • 輸入不會直接嵌入到 SQL 文字
if (safemode === 'true') {
    const result = await db.query('SELECT * FROM users WHERE username = $1', [username]);
    // 驗證帳號和密碼
    // ...
}

安全模式:使用者錯誤訊息過於詳細

  • 駭客容易知道使用者是否存在
// 如果沒有找到使用者,回傳 401 狀態碼
if (result.rows.length === 0) {
    return res.status(401).json({ message: '使用者不存在' }); // 狀態過於詳細,不建議這樣寫,因為會讓駭客知道使用者不存在
    // return res.status(401).json({ message: '使用者不存在或密碼錯誤' }); // 這樣寫比較好
}

image

安全模式:密碼未使用雜湊

  • 直接取得密碼,並沒有進行雜湊
// 沒有使用 bcrypt 進行雜湊,所以不用 await,直接比對密碼,但這樣不安全
const isPasswordValid = password === user.password;

// 要引入 bcrypt 進行雜湊,再比對密碼
// const isPasswordValid = await bcrypt.compare(password, user.password); // 有使用 bcrypt,所以要引入 bcrypt 

不安全模式: 查詢方法進行拼接

// 不安全的寫法,容易被 SQL Injection 攻擊
const result = await db.query(`SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`);
if (result.rows.length === 0) {
    return res.status(401).json({ message: '使用者不存在或密碼錯誤' });
}
try {
    const user = result.rows[0];
  1. SQL 注入風險
    • 寫法直接將 usernamepassword 變量插入到 SQL 查詢字串中。
    • 攻擊者可以輸入特殊構造的字串來修改 SQL 查詢的結構和邏輯。
    • 例如,如果攻擊者在 username 輸入 ' OR '1'='1,那麼整個 WHERE 子句將始終為真,可能導致未授權訪問。
  2. 密碼明文傳輸
    • 密碼直接作為明文在 SQL 查詢中使用,增加了密碼被攔截或洩漏的風險。
    • 最佳實踐是在資料庫中儲存密碼的雜湊值,而不是明文密碼。
  3. 單一步驟驗證
    • 帳號和密碼在同一個 SQL 查詢中一起驗證。
    • 這減少了對特定錯誤(如帳號不存在或密碼錯誤)的精確識別能力。
  4. 錯誤處理
    • 使用 if (result.rows.length === 0) 來檢查查詢結果。
    • 回傳一個共通的錯誤訊息 "使用者不存在或密碼錯誤",這是好的做法,因為它不洩漏具體是帳號錯誤還是密碼錯誤。
  5. 性能考慮:
    • 此方法在一次查詢中完成身份驗證,可能比分步驗證稍快。
    • 雖然這種微小的性能優勢遠不及其帶來的安全風險。
  6. 錯誤的密碼儲存
    • 這種方法暗示密碼可能以明文形式儲存在資料庫中,這是一個嚴重的安全隱患。
    • 應該使用如 bcrypt 等安全的雜湊算法來儲存密碼。
  7. 缺乏輸入驗證
    • 在執行 SQL 查詢之前,沒有對 usernamepassword 進行任何形式的輸入驗證或清理。
    • 這增加了 SQL 注入和其他類型攻擊的風險。
  8. try-catch 的使用
    • 在查詢之後使用 try-catch 塊,但主要的風險操作(SQL 查詢)已經在 try 之外執行了。
    • 這種錯誤處理結構可能無法取得 SQL 注入導致的錯誤。
  9. 資料暴露
    • 如果查詢成功,result.rows[0] 可能包含帳號的所有資訊,包括可能的敏感資料。
    • 應該只選擇和回傳必要的帳號資訊,而不是整個帳號記錄。

實作: SQL injection

  • 選擇不安全
  • 輸入 ' or '1'='1' -- -
  • 出現登入成功且為 testuser 的身分

image

image

實作

建立環境

基本認證繞過

使用 ' or '1'='1' -- - 作為帳號。

原理解釋:

  • ' 閉合原始 SQL 查詢中的字符串。
  • or '1'='1' 增加一個永遠為真的條件。
  • -- - 註解掉查詢的剩餘部分。

結果:
這使得 WHERE 子句永遠為真,導致查詢回傳第一個使用者的資料,從而繞過認證。

原始查詢可能類似:

SELECT * FROM users WHERE username = '' or '1'='1' -- -' AND password = 'anything'

聯合查詢攻擊

嘗試 ' UNION SELECT NULL,NULL,NULL,NULL,NULL FROM users -- -

image

image

嘗試 ' UNION SELECT NULL,NULL,NULL,NULL,NULL FROM users -- -

原理解釋:

  • ' 閉合原始查詢的字符串。
  • UNION 將另一個 SELECT 語句的結果與原始查詢結果合併。
  • SELECT NULL,NULL,NULL,NULL,NULL 建立一個與原始查詢結果列數相同的結果集。
  • FROM users 從使用者表中選擇資料。
  • -- - 註解掉查詢的剩餘部分。

結果:
這會回傳所有使用者的資料,即使原始查詢沒有配對的結果。

注意:需要猜測或知道原始查詢回傳的列數,這就是為什麼使用多個 NULL。

基於時間注入

使用 '; SELECT CASE WHEN (username='testuser') THEN pg_sleep(10) ELSE pg_sleep(0) END FROM users -- -

image

使用 '; SELECT CASE WHEN (username='testuser') THEN pg_sleep(10) ELSE pg_sleep(0) END FROM users -- -

原理解釋:

  • '; 結束原始查詢並開始一個新的查詢。
  • SELECT CASE...END 建立一個條件語句。
  • WHEN (username='testuser') 檢查是否存在使用者名為 'testuser' 的使用者。
  • THEN pg_sleep(10) ELSE pg_sleep(0) 如果條件為真,則暫停 10 秒,否則立即回傳。
  • FROM users 應用於使用者表。
  • -- - 註解掉查詢的剩餘部分。

結果:
如果 'testuser' 存在,查詢會延遲 10 秒。攻擊者可以透過觀察回應時間來推斷使用者是否存在。

安全模式

驗證其對 SQL injection 的防禦效果

  • 將不安全的查詢替換為參數化查詢
  • 重複之前的攻擊嘗試,驗證它們是否被阻止
  • 測試正常的登錄功能,確保合法操作不受影響

上一篇
資安這條路:Day 8 從 Cookie HTTPOnly 了解 Session Hijacking
下一篇
資安這條路:Day 10 從文章功能了解 Union Select SQL injection
系列文
資安這條路:系統化學習網站安全與網站滲透測試13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言