iT邦幫忙

0

Day23 BehaviorGuard:建立 Response Policy,驗證白名單與程序身分

  • 分享至 

  • xImage
  •  

在 Day22,我們透過 Python 的 subprocess 模組,成功使用 SIGTERM 終止 Kali Linux 中的 sleep 60 測試程序。

這代表 BehaviorGuard 已經具備基本的程序終止能力,不再只是顯示 Response 建議。

不過,當時的終止功能仍然很簡單,只要傳入 authorized=True,並符合指定的測試指令,就可以執行終止。

這樣的設計如果直接用在真正的自動回應系統中,可能產生安全問題。

例如,Detection Engine 誤判正常程序,或是原本的程序已經結束,PID 被其他程序重複使用,就可能導致錯誤的終止操作。

因此,Day23 我們要建立 Response Policy(回應安全策略),並加入程序保護清單與身分驗證機制。

今天的重點不是增加偵測規則,而是讓程序終止功能在執行前,先確認目標是否符合安全條件。


一、什麼是 Response Policy?

Response Policy 可以理解成事件回應的安全規則。

例如,當系統收到一筆要求終止程序的請求時,不應該立即執行 SIGTERM,而是先檢查:

  1. 是否具有執行授權?
  2. 目標是不是受到保護的系統程序?
  3. 程序是否仍然存在?
  4. 程序名稱與執行指令是否符合預期?
  5. 目前的程序是否與原本記錄的是同一個?

只有所有條件都通過,才允許執行終止操作。

我們今天採用的設計是:

Process Termination Request
              |
              v
       Response Policy
              |
              v
        授權檢查
              |
              v
        白名單檢查
              |
              v
       程序身分驗證
              |
              v
       ALLOW / DENY
          |       |
        DENY     ALLOW
          |       |
       拒絕執行   SIGTERM
                  |
                  v
             驗證程序退出

這樣可以讓 Response Engine 與安全策略分開,未來要增加其他檢查時,也不需要一直修改程序終止的主要邏輯。


二、Linux 如何識別一個程序?

之前我們主要透過 PID 與 Command 識別程序。

但 PID 並不是永久不變的識別碼。

例如,某個程序的 PID 是 1234,當它結束後,Linux 未來可能把相同 PID 分配給另一個程序。

如果系統只記住 PID,就可能無法分辨現在的 PID 1234 是否還是原本的程序。

因此,這次我們要取得更多程序資訊。

Linux 提供 /proc 虛擬檔案系統,可以讀取程序相關資料。

Linux 路徑 用途
/proc/[pid]/comm 取得程序名稱
/proc/[pid]/cmdline 取得程序命令列參數
/proc/[pid]/exe 取得執行檔的符號連結
/proc/[pid]/stat 取得程序狀態與啟動時間等資訊

其中比較重要的是 /proc/[pid]/stat。

它的第 22 個欄位 starttime,記錄程序在系統開機後經過多少 clock ticks 才啟動。

如果我們同時記錄 PID 與 Start Time,就能降低只使用 PID 造成的身分混淆風險。

另外,Executable 可以幫助我們確認程序實際執行的檔案位置,而不只是相信程序名稱。

不過,Executable 路徑比對也不是完整的檔案完整性驗證,這次尚未檢查檔案雜湊值。


三、建立 Process Identity Verification

首先進入專案:

cd ~/behaviorguard

編輯:

nano response/policy.py

我們在這個檔案中建立 get_process_identity() 函式,專門取得 Linux 程序身分資訊。

def get_process_identity(pid):

    try:
        base = f"/proc/{pid}"

        with open(f"{base}/comm", "r") as f:
            name = f.read().strip()

        with open(f"{base}/cmdline", "rb") as f:
            command = [
                x.decode(errors="replace")
                for x in f.read().split(b"\x00")
                if x
            ]

        executable = os.readlink(f"{base}/exe")

        with open(f"{base}/stat", "r") as f:
            stat = f.read().strip()

        end = stat.rfind(")")

        if end == -1:
            return None

        fields = stat[end + 2:].split()

        start_time = int(fields[19])

        return {
            "pid": pid,
            "name": name,
            "command": command,
            "executable": executable,
            "start_time": start_time
        }

    except (OSError, ValueError, IndexError):
        return None

技術說明

1. 讀取 Command

Linux 的 /proc/[pid]/cmdline 使用 Null Byte(\x00)分隔命令列參數。

因此,我們使用:

f.read().split(b"\x00")

將原始資料拆成不同參數,再解碼成文字。

例如:

["sleep", "60"]

2. 取得 Executable

executable = os.readlink(f"{base}/exe")

/proc/[pid]/exe 是指向程序執行檔的符號連結。

透過 os.readlink(),可以取得它指向的路徑。

3. 取得 Start Time

end = stat.rfind(")")
fields = stat[end + 2:].split()
start_time = int(fields[19])

由於 /proc/[pid]/stat 的第二個欄位 comm 被括號包住,而且可能包含空格,所以不能直接對整行進行簡單的空格切割。

我們先找到括號結束的位置,再解析後面的欄位。

移除前兩個欄位後,fields[0] 對應原本的第 3 欄,因此 fields[19] 就是原本的第 22 欄 Start Time。

這次取得的程序資料包含 PID、Name、Command、Executable 和 Start Time,供後續 Policy 比對使用。


四、建立 Response Policy 與白名單

接著,我們建立受保護程序清單:

PROTECTED_PROCESSES = {
    "systemd",
    "sshd",
    "NetworkManager",
    "dbus-daemon",
    "cron",
    "crond"
}

這裡的白名單代表受到保護的程序。

如果程序名稱出現在清單中,Response Policy 就會拒絕終止請求。

另外,今天仍然限制只有指定測試指令可以被終止:

ALLOWED_TEST_COMMAND = ["sleep", "60"]

因此,我們目前的 Policy 並不是允許終止所有非白名單程序,而是只允許符合測試條件的程序。

程序身分比對

除了授權、PID 與白名單檢查,我們也加入:

keys = [
    "pid",
    "name",
    "command",
    "executable",
    "start_time"
]

for key in keys:
    if current[key] != expected_identity.get(key):
        return False, f"Process identity mismatch: {key}"

return True, "Policy and identity verified"

這裡使用兩份資料:

  • expected_identity:事先保存的程序身分。
  • current:執行終止前,重新從 Linux /proc 取得的程序身分。

如果兩份資料有任何欄位不一致,就拒絕執行。

例如,Start Time 不同,代表目前觀察到的程序身分與之前記錄的不一致。

這種不符合條件就拒絕的設計,符合 Fail Closed(無法確認安全條件時預設拒絕)的概念。


五、整合 Response Engine

完成 Policy 後,我們修改 response/engine.py。

原本 Day20 的 handle_alert() 仍然保留 Dry Run,不會自動終止可疑程序。

今天修改的是 Day22 建立的 terminate_test_process()。

主要邏輯如下:

def terminate_test_process(
    proc,
    authorized=False,
    expected_identity=None
):

    print("\n========== BehaviorGuard Termination ==========")

    allowed, reason = check_termination_policy(
        proc,
        authorized=authorized,
        expected_identity=expected_identity
    )

    if not allowed:
        print(f"[DENIED] {reason}")
        print("Executed   : False")
        return False

    print(f"[POLICY] ALLOW: {reason}")

    try:
        proc.terminate()
        proc.wait(timeout=3)

        print("Executed   : True")
        print("Result     : Process exited")
        return True

    except subprocess.TimeoutExpired:
        print("[FAIL] Process did not exit in time.")
        return False

    except OSError as e:
        print(f"[ERROR] {e}")
        return False

這次最重要的改變是:

Response Engine 必須先取得 Response Policy 的 ALLOW 結果,才能執行 proc.terminate()。

如果沒有授權、程序身分不一致或程序受到保護,就直接回傳 False。

這也讓我們可以把安全檢查與實際處置分成不同模組。


六、實際測試 Response Policy

今天建立了 test_day23_advanced.py,分別測試五種情況。

測試使用 Python subprocess.Popen() 建立真正的 sleep 60 子程序,再由 get_process_identity() 取得程序資訊。

測試結束後,也會清理自己建立的程序,避免殘留背景程序。

執行:

PYTHONPATH=. python3 test_day23_advanced.py

測試一:未授權終止

第一個測試沒有提供授權:

[DENIED] Authorization required
Executed   : False

[PASS] Unauthorized request denied.

結果符合預期,Response Engine 沒有執行終止操作。

測試二:白名單保護

第二個測試將 sleep 暫時加入受保護清單。

即使提供 authorized=True,Policy 仍然應該拒絕。

實際結果:

[DENIED] Protected process: sleep
Executed   : False

[PASS] Whitelist protection worked.

這證明程序保護清單有實際參與 Policy 判斷。

測試結束後,程式會移除暫時加入的 sleep 保護設定。

測試三:Start Time 不一致

第三個測試修改預期身分中的 Start Time:

fake_identity = identity.copy()
fake_identity["start_time"] += 1

這次不是實際製造 PID 重複使用,而是透過修改測試資料,模擬程序啟動時間不一致。

實際結果:

[DENIED] Process identity mismatch: start_time
Executed   : False

[PASS] Start time mismatch denied.

Policy 成功發現 Start Time 不符合預期並拒絕終止。

測試四:Executable 不一致

第四個測試將預期執行檔路徑改成:

fake_identity["executable"] = "/tmp/fake_sleep"

實際結果:

[DENIED] Process identity mismatch: executable
Executed   : False

[PASS] Executable mismatch denied.

這代表 Executable 路徑比對確實有效,當目前執行檔與預期資料不一致時,系統會拒絕處置。

測試五:通過 Policy 後真正終止

最後建立一個符合條件的測試程序,使用正確的身分資料與授權參數。

實際執行結果:

========== BehaviorGuard Termination ==========

[POLICY] ALLOW: Policy and identity verified

PID        : 2551678
Action     : Process Termination
Mode       : CONTROLLED TEST
Executed   : True
Result     : Process exited

[PASS] Authorized process terminated.

這次 Response Policy 成功允許處置,而 Response Engine 也成功向測試程序發送 SIGTERM,並確認程序結束。


七、今天的測試結果整理

測試項目 Policy 結果 測試結果
未授權請求 DENY PASS
白名單保護 DENY PASS
Start Time 不一致 DENY PASS
Executable 不一致 DENY PASS
符合所有安全條件 ALLOW PASS

今天共完成五項進階測試,全部通過。

與 Day22 相比,這次的進步不只是能終止程序,而是加入了執行終止前的安全條件檢查。


八、目前還有哪些限制?

雖然今天增加了程序身分驗證,但目前仍然只是初階的 Response Policy。

首先,authorized=True 只是函式參數,不是真正的使用者身分驗證或授權系統。

其次,今天使用 PID、Start Time 和 Executable 來比對程序身分,可以降低身分混淆風險,但不能完全消除檢查與執行之間的競態條件。

這涉及 TOCTOU(Time-of-Check to Time-of-Use)問題。

例如,系統完成程序檢查後,到真正發出終止訊號之間,程序狀態仍可能發生改變。

Linux 有提供 pidfd 相關機制,可以在未來研究如何更可靠地參照特定程序。

另外,目前的白名單主要根據程序名稱判斷,這不是完整的程序可信度驗證機制。

最後,Response Policy 仍然只應用在受控測試程序終止流程,還沒有讓真正的 Detection Alert 自動觸發 SIGTERM。

因此,今天不能宣稱已經完成企業級自動化防禦或完全解決誤判問題。


九、今日心得

Day23 讓我更了解,真正的事件回應並不是簡單地找到 PID,然後直接執行終止指令。

在 Day22,我們已經成功使用 SIGTERM 終止測試程序,但當時的安全檢查相對簡單。

今天透過 Response Policy,加入了程序白名單、指令限制、啟動時間與執行檔路徑比對,讓終止操作在執行之前必須先經過更多檢查。

其中我認為最重要的收穫,是了解 PID 並不能單獨作為程序身分的可靠依據。

過去我只知道 Linux 可以透過 PID 管理程序,但這次才進一步學到 /proc/[pid]/stat 中的 Start Time,以及 /proc/[pid]/exe 提供的執行檔資訊,可以協助判斷程序是否符合預期。

另外,Fail Closed 也是今天很重要的觀念。

如果系統無法確認程序身分,就應該拒絕執行可能影響系統正常運作的動作,而不是在資訊不完整的情況下繼續處置。

不過,目前我們的保護仍然有限,還沒有完整的身分授權、處置稽核與競態防護機制。

下一篇 Day24,我們預計將 Detection 與受控 Process Termination 進一步整合,測試符合明確安全條件的事件,能否經過 Response Policy 後自動觸發程序終止。

我們會先限制在自己建立的測試程序,不會直接讓所有 HIGH Alert 都自動終止 Linux 程序。


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言