// ==UserScript== // @name Guardian // @namespace http://tampermonkey.net/ // @version 1.0 // @description 能拦截eval等危险操作,追踪其来源(网页自身或用户脚本),并弹出对话框让用户决定是否放行,最终实现了对恶意脚本攻击的有效防御。"。 // @author 恶搞之家 // @match *://*/* // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @grant GM_cookie // @run-at document-start // ==/UserScript== (function() { 'use strict'; const SCRIPT_VERSION = "11.0"; console.log(`%c[GUARDIAN] Guardian Script v${SCRIPT_VERSION} is running...`, 'color: blue; font-weight: bold;'); const CONFIG = { WHITELISTED_DOMAINS: [] }; let userWhitelist = []; let isWhitelisted = false; const currentHostname = window.location.hostname; const trueWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; let uiPanel = null; let riskCounters = { warn: 0, error: 0, blocked: 0 }; const originalGMApis = { getValue: GM_getValue, setValue: GM_setValue, }; const getCallSource = () => { try { const stackLines = new Error().stack.split('\n'); if (stackLines.length > 3) { const callerLine = stackLines[3]; const tmMatch = callerLine.match(/userscript.html\?name=([^&]+)/); if (tmMatch && tmMatch[1]) { return { type: '油猴脚本', name: decodeURIComponent(tmMatch[1].trim()) }; } const vmMatch = callerLine.match(/@([^/:]+)/); if (vmMatch && vmMatch[1]) { return { type: '油猴脚本', name: vmMatch[1].trim() }; } } } catch (e) { /* 忽略错误 */ } return { type: '网站自身', name: window.location.hostname }; }; const createWrapper = (name, originalFunc, isPrivileged = false) => { return function(...args) { const source = getCallSource(); if (isPrivileged) { if (GuardianUI.addLog) GuardianUI.addLog(name, true, 'monitored', source); console.error(`%c[GUARDIAN] 发现高危特权调用: ${name} (来源: ${source.type} - ${source.name})`, 'color: red; font-weight: bold;'); console.trace('[GUARDIAN] 调用堆栈:'); return originalFunc.apply(this, args); } if (isWhitelisted) { return originalFunc.apply(this, args); } const confirmationMessage = `[GUARDIAN] 权限请求 一个名为【${source.name}】的【${source.type}】正请求使用高风险的 " ${name} " 权限来执行代码。 - 点击"确定"以【放行】本次操作,并将【${currentHostname}】自动加入白名单,后续不再提示。 - 点击"取消"以【阻止】本次操作。 您的决定是?`; if (window.confirm(confirmationMessage)) { if (GuardianUI.addLog) GuardianUI.addLog(name, false, 'allowed', source); console.warn(`%c[GUARDIAN] 用户已放行调用: ${name} (来源: ${source.type} - ${source.name})`, 'color: orange;'); autoWhitelistAndNotify(currentHostname); return originalFunc.apply(this, args); } else { const errorMessage = `[GUARDIAN] 用户已阻止高危调用: ${name} (来源: ${source.type} - ${source.name})`; if (GuardianUI.addLog) GuardianUI.addLog(name, true, 'blocked', source); console.error(`%c${errorMessage}`, 'color: red; font-weight: bold;'); throw new Error(errorMessage); } }; }; if (trueWindow.eval) { trueWindow.eval = createWrapper('eval', trueWindow.eval); } if (trueWindow.Function) { const originalFunction = trueWindow.Function; const functionWrapper = createWrapper('new Function', originalFunction); functionWrapper.prototype = originalFunction.prototype; trueWindow.Function = functionWrapper; } if (typeof GM_xmlhttpRequest !== 'undefined') { unsafeWindow.GM_xmlhttpRequest = createWrapper('GM_xmlhttpRequest', GM_xmlhttpRequest, true); } if (typeof GM_setValue !== 'undefined') { unsafeWindow.GM_setValue = createWrapper('GM_setValue', originalGMApis.setValue, true); } if (typeof GM_cookie !== 'undefined' && GM_cookie.list) { GM_cookie.list = createWrapper('GM_cookie.list', GM_cookie.list, true); } const GuardianUI = { createPanel: function() { const styles = ` #guardian-sidebar { position: fixed; top: 0; right: 0; height: 100%; width: 360px; background: #f9f9f9; z-index: 2147483647; transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: -2px 0 15px rgba(0,0,0,0.15); border-left: 1px solid #ccc; transform: translateX(0); font-family: sans-serif; } #guardian-sidebar.collapsed { transform: translateX(100%); } #guardian-toggle-btn { position: absolute; top: 50%; left: -30px; transform: translateY(-50%); width: 30px; height: 60px; line-height: 58px; text-align: center; background: #f9f9f9; font-size: 24px; color: #555; border: 1px solid #ccc; border-right: none; border-top-left-radius: 8px; border-bottom-left-radius: 8px; cursor: pointer; box-shadow: -4px 4px 10px rgba(0,0,0,0.1); user-select: none; } #guardian-content-wrapper { width: 100%; height: 100%; display: flex; flex-direction: column; } #guardian-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 15px; background: #efefef; border-bottom: 1px solid #ddd; flex-shrink: 0; } #guardian-title-area { display: flex; align-items: center; font-weight: bold; } #guardian-add-whitelist-btn { cursor: pointer; font-size: 18px; margin-left: auto; margin-right: 15px; user-select: none; } #guardian-risk-counters { font-size: 12px; } #guardian-risk-counters .warn { color: #856404; margin-right: 5px; } #guardian-risk-counters .error { color: #721c24; } #guardian-risk-counters .blocked { color: #721c24; } .guardian-log-entry { padding: 5px; margin-bottom: 5px; border-radius: 4px; border: 1px solid; word-wrap: break-word; } .guardian-log-warn { background-color: #fff3cd; border-color: #ffeeba; color: #856404; } .guardian-log-error { background-color: #f8d7da; border-color: #f5c6cb; color: #721c24; } .guardian-log-entry strong { margin-right: 5px; } .status-tag { font-weight:bold; margin-left: 5px; padding: 1px 4px; border-radius: 3px; font-size: 10px; color: white; } .status-allowed { background-color: #28a745; } .status-blocked { background-color: #dc3545; } .status-monitored { background-color: #6c757d; } #guardian-logs { flex-grow: 1; overflow-y: auto; padding: 8px; } .guardian-log-info { background-color: #d1ecf1; border-color: #bee5eb; color: #0c5460; } `; const styleSheet = document.createElement("style"); styleSheet.type = "text/css"; styleSheet.innerText = styles; (document.head || document.documentElement).appendChild(styleSheet); uiPanel = document.createElement('div'); uiPanel.id = 'guardian-sidebar'; uiPanel.classList.add('collapsed'); uiPanel.innerHTML = `