Injection Attacks#

概述#

Injection attacks(注入攻擊)是指攻擊者將外部程式碼注入應用程式中,藉此控制應用程式或讀取敏感資料。這是網路上最常見且破壞力最大的攻擊類型之一。

網路架構基於 client-server architecture,瀏覽器發送 HTTP request,伺服器回傳 HTTP response。然而:

  • 伺服器無法可靠地分辨請求來自瀏覽器還是腳本
  • User-Agent header 可以被輕易偽造(spoof)
  • 攻擊者可以在 HTTP request 中夾帶惡意程式碼,誘使伺服器執行

撰寫網站程式碼時,必須考慮 HTTP request 中可能出現的所有內容,而不僅是你預期的內容。

本章涵蓋四種注入攻擊類型:

  • SQL Injection
  • Command Injection
  • Remote Code Execution
  • File Upload Vulnerabilities

SQL Injection#

SQL injection 攻擊針對使用 SQL 資料庫的網站,利用不安全的方式建構 SQL 查詢來入侵資料庫。這是網站面臨的最大風險之一。

2008 年,駭客透過 SQL injection 從 Heartland Payment Systems 竊取了 1.3 億筆信用卡資料。

SQL 基礎#

Structured Query Language (SQL) 用於操作關聯式資料庫中的資料。資料庫以 table 儲存資料,每一列(row)代表一筆資料項目。基本操作包括:

  • INSERT — 新增資料
  • SELECT — 讀取資料
  • UPDATE — 更新資料
  • DELETE — 刪除資料
-- 新增使用者
INSERT INTO users (email, encrypted_password)
VALUES ('billy@gmail.com', '$1o$WMT9Y')

-- 查詢使用者
SELECT * FROM users WHERE email = 'billy@gmail.com'
AND encrypted_password = '$1o$WMT9Y'

-- 更新密碼
UPDATE users SET encrypted_password = '3D$MW$1oZ'
WHERE email='billy@gmail.com'

-- 刪除使用者
DELETE FROM users WHERE email = 'billy@gmail.com'

SQL Injection 攻擊剖析#

當 web server 不安全地建構 SQL statement 時,就會產生 SQL injection 漏洞。以下是一段有漏洞的 Java 程式碼:

Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
Statement statement = connection.createStatement();
String sql = "SELECT * FROM users WHERE email='" + email +
        "' AND encrypted_password='" + password + "'";
statement.executeQuery(sql);

這段程式碼直接將 HTTP request 中的 emailpassword 參數插入 SQL statement,未檢查 SQL 控制字元(如 ')。

繞過身份驗證的範例:

攻擊者將 email 設為 billy@gmail.com'--,SQL comment 語法 -- 會使密碼檢查部分被忽略,攻擊者可以不需要密碼就登入任何帳號。

更進階的攻擊:

攻擊者可以插入額外的 SQL statement,例如 DROP TABLE users;,直接破壞整個資料庫。

如果網站存在 SQL injection 漏洞,攻擊者可以:繞過身份驗證、讀取/下載/刪除任意資料、注入惡意 JavaScript。可使用 Metasploit 等工具掃描此類漏洞。

緩解措施 1:使用 Parameterized Statements#

防禦 SQL injection 的核心方法是使用 bind parameters(綁定參數)。含有 bind parameters 的 SQL statement 稱為 parameterized statement

Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
Statement statement = connection.createStatement();
String sql = "SELECT * FROM users WHERE email = ? and encrypted_password = ?";
statement.executeQuery(sql, email, password);
  • SQL query 使用 ? 作為佔位符
  • 資料庫驅動程式會安全地將參數值插入 SQL statement
  • 控制字元(如 '--;)會被當作輸入而非 SQL 語法的一部分

如果你不確定網站是否使用 parameterized statements,立刻去檢查!SQL injection 可能是你的網站面臨的最大風險。

類似的注入攻擊也可能發生在 NoSQL 資料庫(MongoDB、Cassandra)、分散式快取(Redis、Memcached)和 LDAP 目錄等系統上,各平台都有自己的 bind parameters 實作方式。

緩解措施 2:使用 Object-Relational Mapping (ORM)#

ORM 函式庫將資料庫 table 的 row 映射為程式碼中的物件,開發者通常不需要手動撰寫 SQL。例如 Ruby on Rails 的 ActiveRecord:

# 安全的寫法
User.find_by(email: "billy@gmail.com")

但要注意,大多數 ORM 也提供撰寫原生 SQL 的「後門」。如果使用這類功能,仍然可能產生漏洞:

# 不安全的寫法
def find_user(email, password)
  User.where("email = '" + email + "' and encrypted_password = '" + password + "'")
end

# 安全的寫法(使用 bind parameters)
def find_user(email, encrypted_password)
  User.where(["email = ? and encrypted_password = ?", email, encrypted_password])
end

進階防禦:Defense in Depth#

Defense in depth(縱深防禦)是在多個層級實施安全措施的策略,即使某一層被突破,其他層仍能提供保護。

Principle of Least Privilege#

最小權限原則要求每個程序和應用程式只擁有執行其功能所需的最低權限。

  • 大多數網站只需要 DML(Data Manipulation Language)權限:SELECTINSERTUPDATEDELETE
  • 通常不需要 DDL(Data Definition Language)權限:CREATEDROPMODIFY
  • 將 web server 的資料庫權限限制在最小 DML 集合,可以減少攻擊造成的傷害

Blind 與 Nonblind SQL Injection#

  • Nonblind SQL injection:錯誤訊息洩漏敏感資訊給攻擊者(如 Unique constraint violated: this email address already exists in users table),攻擊者可獲得即時回饋
  • Blind SQL injection:錯誤訊息較為通用(如 Could not find this username and password),攻擊者難以獲得有用資訊

避免在錯誤訊息中洩漏資料庫結構或資料內容,使用通用的錯誤訊息可以增加攻擊難度。

Command Injection#

Command injection(命令注入)利用網站對底層作業系統的不安全命令列呼叫。常見於使用直譯式語言(PHP、Python、Ruby)的應用程式,因為這些語言容易執行作業系統層級的命令。

Command Injection 攻擊剖析#

假設有一個 PHP 網站提供 nslookup 功能:

<?php
    if (isset($_GET['domain'])) {
        echo '<pre>';
        $domain = $_GET['domain'];
        $lookup = system("nslookup {$domain}");
        echo($lookup);
        echo '</pre>';
    }
?>

攻擊者可以傳入 google.com && echo "HAXXED" 作為 domain 參數。&& 語法會使系統執行兩個指令:正常的 nslookup 和注入的 echo 命令。

Command injection 讓攻擊者可以在 web server 上執行任意命令,探索檔案系統、讀取敏感資訊、完全控制應用程式。

Figure 6-1: 透過 URL 注入惡意命令

緩解措施:Escape 控制字元#

各語言都有對應的 escape 方式:

PHP — 使用 escapeshellarg()

$domain = escapeshellarg($_GET['domain']);
$lookup = system("nslookup {$domain}");

Python — 使用 subprocess.call() 並傳入陣列:

from subprocess import call
call(["nslookup", domain])

Ruby — 使用 system() 並將參數分開傳入:

system("nslookup", domain)

同樣應遵循最小權限原則

  • 限制 web server 程序可讀寫的目錄
  • 在 Linux 上使用 chroot 防止存取指定根目錄以外的檔案
  • 限制 web server 的網路存取權限(設定 firewall 和 ACL)

Remote Code Execution#

Remote code execution(遠端程式碼執行)是指攻擊者將惡意程式碼直接注入 web server 程序本身的語言中執行。雖然比其他注入攻擊少見,但同樣危險。

攻擊剖析#

  • 攻擊者發現特定 web server 的漏洞後,建立 exploit script 來攻擊使用該技術的網站
  • Exploit script 將惡意程式碼嵌入 HTTP request body,使伺服器在處理請求時讀取並執行該程式碼

2013 年初,安全研究人員發現 Ruby on Rails 的漏洞:Rails 會根據 Content-Type header 自動解析請求。攻擊者可以建立含有嵌入式 YAML 物件的 XML request,誘使解析過程執行任意程式碼。

緩解措施:在 Deserialization 時停用程式碼執行#

  • Serialization(序列化):將記憶體中的資料結構轉為二進位資料流
  • Deserialization(反序列化):將二進位資料轉回資料結構
  • 某些序列化函式庫(如 Rails 使用的 YAML parser)允許在反序列化時執行程式碼,這非常危險
  • 應透過設定停用 serialization library 的程式碼執行功能
  • 關注所使用的 web server 軟體和 serialization library 的安全公告
  • 關閉自身程式碼中的 active code execution 功能

File Upload Vulnerabilities#

網站常提供檔案上傳功能(頭像、附件、文件等),但瀏覽器不會仔細檢查檔案內容,攻擊者可以利用此功能注入惡意程式碼。

攻擊剖析#

攻擊者的典型手法:

  1. 撰寫一個 web shell(可執行的小型腳本,接收 HTTP request 參數並在命令列執行)
<?php
  if(isset($_REQUEST['cmd'])) {
    $cmd = ($_REQUEST['cmd']);
    system($cmd);
  } else {
    echo "What is your bidding?";
  }
?>
  1. 將此腳本偽裝為圖片上傳(如 hack.php 作為大頭貼)
  2. 即使有 JavaScript 端的檔案類型檢查,攻擊者可直接向 server-side endpoint 發送請求繞過
  3. 上傳成功後,透過該檔案的公開 URL 傳入 cmd 參數執行任意系統命令

Figure 6-2: 利用檔案上傳漏洞上傳惡意腳本

緩解措施 1:將檔案託管在安全的系統上#

  • 使用 CDN(如 Cloudflare、Akamai)託管上傳檔案,將安全責任轉移給第三方
  • CDN 也提供非安全相關的好處:快速傳輸、處理 pipeline、上傳 widget
  • 如果不能用 CDN,可使用雲端儲存(如 Amazon S3)或專用的內容管理系統

緩解措施 2:確保上傳檔案無法被執行#

  • 所有檔案寫入磁碟時不帶可執行權限
  • 將上傳檔案與程式碼分開存放在不同目錄或 partition
  • Hardening 伺服器,只安裝最低限度的軟體(如不需要 PHP 就卸載它)
  • 上傳時重新命名檔案,避免使用危險的副檔名
import os
file_descriptor = os.open("/path/to/file", os.O_WRONLY | os.O_CREAT, 0o600)
with os.fdopen(open(file_descriptor, "wb")) as file_handle:
    file_handle.write(...)

Center for Internet Security (CIS) 提供預先加固的作業系統映像檔,可作為 Docker image 或 Amazon Machine Image (AMI) 使用。

緩解措施 3:驗證上傳檔案的內容#

  • 檢查 Content-Type header 是否符合預期(但攻擊者可以偽造)
  • 在 server-side 驗證檔案類型,例如使用 Python 的 imghdr 模組讀取檔案標頭
>>> import imghdr
>>> imghdr.what('/tmp/what_is_this.dat')
'gif'

緩解措施 4:執行防毒軟體#

如果伺服器執行在容易受病毒感染的平台上(尤其是 Windows),確保安裝並更新防毒軟體。檔案上傳功能是病毒 payload 進入系統的途徑之一。

總結#

  • SQL injection:利用不安全的 SQL 字串建構。使用 parameterized statements 防禦
  • Command injection:利用不安全的作業系統命令呼叫。使用正確的 escape/binding 方式防禦
  • Remote code execution:利用 web server 程序本身的漏洞(通常來自不安全的 serialization)。保持關注安全公告
  • File upload vulnerabilities:利用檔案上傳功能上傳可執行程式碼。將檔案託管在第三方系統、移除執行權限、驗證檔案類型
  • 所有類型的注入攻擊都應遵循 principle of least privilege,將程序和軟體元件的權限限制在最低需求