安全实验场 Pro
by EpicCoder88584 lines19.1 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>安全实验场 Pro</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0d1117;
--panel: #161b22;
--panel-2: #1c2128;
--border: #30363d;
--text: #e6edf3;
--text-dim: #8b949e;
--accent: #58a6ff;
--accent-2: #3fb950;
--danger: #f85149;
--purple: #bc8cff;
--orange: #d29922;
--radius: 12px;
--radius-sm: 8px;
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}
html, body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
background: var(--bg);
color: var(--text);
overflow: hidden;
font-size: 14px;
}
/* ===== 布局 ===== */
.app {
display: grid;
grid-template-rows: auto 1fr auto;
grid-template-columns: 1fr 340px;
grid-template-areas:
"header header"
"stage logs"
"editor editor";
height: 100vh;
gap: 10px;
padding: 10px;
}
/* ===== 顶栏 ===== */
.header {
grid-area: header;
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
flex-wrap: wrap;
}
.brand {
display: flex;
align-items: center;
gap: 8px;
font-weight: 700;
font-size: 15px;
color: var(--text);
margin-right: 6px;
white-space: nowrap;
}
.brand .dot {
width: 10px; height: 10px;
border-radius: 50%;
background: var(--accent-2);
box-shadow: 0 0 10px var(--accent-2);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: .45; }
}
.divider {
width: 1px;
height: 22px;
background: var(--border);
margin: 0 4px;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 13px;
font-size: 13px;
font-weight: 600;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--panel-2);
color: var(--text);
cursor: pointer;
transition: all .15s ease;
white-space: nowrap;
user-select: none;
}
.btn:hover {
background: #22272e;
border-color: #484f58;
transform: translateY(-1px);
}
.btn:active { transform: translateY(0); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.primary:hover { background: #4a97e8; }
.btn.green { background: var(--accent-2); border-color: var(--accent-2); color: #fff; }
.btn.green:hover { background: #36a347; }
.btn.red { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn.red:hover { background: #e04038; }
.btn.purple { background: var(--purple); border-color: var(--purple); color: #1a1a1a; }
.btn.purple:hover { background: #a97ef0; }
.btn.ghost { background: transparent; }
/* ===== 沙箱舞台 ===== */
.stage {
grid-area: stage;
position: relative;
border-radius: var(--radius);
overflow: hidden;
background: #f6f8fa;
border: 1px solid var(--border);
box-shadow: 0 0 0 1px rgba(88,166,255,.05), 0 8px 30px rgba(0,0,0,.35);
min-height: 0;
}
.stage::before {
content: "沙箱运行区";
position: absolute;
top: 8px; left: 12px;
z-index: 2;
font-size: 11px;
font-weight: 600;
letter-spacing: .5px;
color: #57606a;
background: rgba(255,255,255,.75);
padding: 3px 8px;
border-radius: 999px;
backdrop-filter: blur(4px);
pointer-events: none;
}
.stage iframe {
width: 100%;
height: 100%;
border: 0;
display: block;
background: #fff;
}
/* ===== 日志面板 ===== */
.logs {
grid-area: logs;
display: flex;
flex-direction: column;
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
min-height: 0;
}
.logs-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
border-bottom: 1px solid var(--border);
font-size: 12px;
font-weight: 600;
color: var(--text-dim);
letter-spacing: .5px;
text-transform: uppercase;
}
.logs-head .count {
font-family: var(--mono);
font-size: 11px;
color: var(--text-dim);
background: var(--panel-2);
padding: 2px 8px;
border-radius: 999px;
}
.logs-body {
flex: 1;
overflow-y: auto;
padding: 8px;
font-family: var(--mono);
font-size: 12px;
line-height: 1.6;
}
.logs-body::-webkit-scrollbar { width: 8px; }
.logs-body::-webkit-scrollbar-thumb { background: #30363d; border-radius: 4px; }
.logs-body::-webkit-scrollbar-track { background: transparent; }
.log-item {
display: flex;
gap: 8px;
padding: 5px 8px;
border-radius: 6px;
margin-bottom: 3px;
animation: slideIn .2s ease;
}
@keyframes slideIn {
from { opacity: 0; transform: translateX(-6px); }
to { opacity: 1; transform: translateX(0); }
}
.log-item:hover { background: var(--panel-2); }
.log-time {
color: #484f58;
flex-shrink: 0;
font-size: 11px;
padding-top: 1px;
}
.log-tag {
flex-shrink: 0;
font-size: 10px;
font-weight: 700;
padding: 1px 6px;
border-radius: 4px;
height: fit-content;
margin-top: 1px;
}
.log-tag.info { background: rgba(88,166,255,.15); color: var(--accent); }
.log-tag.ok { background: rgba(63,185,80,.15); color: var(--accent-2); }
.log-tag.warn { background: rgba(210,153,34,.15); color: var(--orange); }
.log-tag.error { background: rgba(248,81,73,.15); color: var(--danger); }
.log-msg { color: var(--text); word-break: break-word; }
/* ===== 编辑器 ===== */
.editor {
grid-area: editor;
display: flex;
flex-direction: column;
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 10px;
gap: 8px;
height: 200px;
}
.editor-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
flex-wrap: wrap;
}
.editor-title {
font-size: 12px;
font-weight: 600;
color: var(--text-dim);
letter-spacing: .5px;
text-transform: uppercase;
}
.badge {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 11px;
font-weight: 600;
padding: 3px 9px;
border-radius: 999px;
background: var(--panel-2);
color: var(--text-dim);
border: 1px solid var(--border);
}
.badge .dot {
width: 6px; height: 6px; border-radius: 50%;
background: var(--accent-2);
}
.editor textarea {
flex: 1;
width: 100%;
resize: none;
background: var(--bg);
color: var(--text);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 10px 12px;
font-family: var(--mono);
font-size: 12.5px;
line-height: 1.6;
outline: none;
transition: border-color .15s;
}
.editor textarea:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(88,166,255,.12);
}
.editor textarea::placeholder { color: #484f58; }
.editor-actions {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
/* ===== 响应式 ===== */
@media (max-width: 860px) {
.app {
grid-template-columns: 1fr;
grid-template-rows: auto 1fr auto auto;
grid-template-areas:
"header"
"stage"
"logs"
"editor";
}
.logs { max-height: 180px; }
.editor { height: auto; min-height: 160px; }
.brand span.full { display: none; }
}
</style>
</head>
<body>
<div class="app">
<!-- 顶栏 -->
<header class="header">
<div class="brand">
<span class="dot"></span>
<span class="full">安全实验场</span>
<span style="font-size:11px;color:var(--text-dim);font-weight:500;">Pro</span>
</div>
<div class="divider"></div>
<button class="btn primary" id="btnReload">⟳ 重载沙箱</button>
<div class="divider"></div>
<button class="btn red" id="btnTest1">测试1 · 原始弹窗</button>
<button class="btn red" id="btnTest2">测试2 · iframe 绕过</button>
<button class="btn purple" id="btnTest3">测试3 · Shadow DOM</button>
<div class="divider"></div>
<button class="btn ghost" id="btnClearLog">清空日志</button>
</header>
<!-- 沙箱 -->
<main class="stage">
<iframe id="sandboxFrame" sandbox="allow-scripts allow-same-origin"></iframe>
</main>
<!-- 日志 -->
<aside class="logs">
<div class="logs-head">
<span>运行日志</span>
<span class="count" id="logCount">0</span>
</div>
<div class="logs-body" id="console"></div>
</aside>
<!-- 编辑器 -->
<section class="editor">
<div class="editor-head">
<span class="editor-title">代码注入</span>
<span class="badge"><span class="dot"></span><span id="statusBadge">沙箱就绪</span></span>
</div>
<textarea id="codeInput" placeholder="在此粘贴 HTML / JS,点击「注入运行」在沙箱中执行。"></textarea>
<div class="editor-actions">
<button class="btn green" id="btnInject">▶ 注入运行</button>
<button class="btn ghost" id="btnWrapDoc">包裹为完整文档</button>
<span style="font-size:11px;color:var(--text-dim);margin-left:auto;">所有代码仅在本地沙箱中运行</span>
</div>
</section>
</div>
<script>
// ================== 日志系统 ==================
const consoleEl = document.getElementById('console');
const logCountEl = document.getElementById('logCount');
let logCount = 0;
function log(msg, type = 'info') {
const now = new Date();
const t = now.toTimeString().slice(0, 8) + '.' + String(now.getMilliseconds()).padStart(3, '0');
const item = document.createElement('div');
item.className = 'log-item';
const tagMap = { info: '信息', ok: '成功', warn: '警告', error: '错误' };
item.innerHTML =
`<span class="log-time">${t}</span>` +
`<span class="log-tag ${type}">${tagMap[type] || '信息'}</span>` +
`<span class="log-msg">${escapeHtml(msg)}</span>`;
consoleEl.appendChild(item);
consoleEl.scrollTop = consoleEl.scrollHeight;
logCount++;
logCountEl.textContent = logCount;
while (consoleEl.children.length > 300) {
consoleEl.removeChild(consoleEl.firstChild);
logCount--;
}
logCountEl.textContent = logCount;
}
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, c => ({
'&':'&','<':'<','>':'>','"':'"',"'":'''
}[c]));
}
// ================== 沙箱 ==================
const frame = document.getElementById('sandboxFrame');
const statusBadge = document.getElementById('statusBadge');
function resetSandbox() {
frame.srcdoc = `<!DOCTYPE html>
<html><head><meta charset="UTF-8"><style>
html,body{margin:0;height:100%;font-family:-apple-system,sans-serif;background:#f6f8fa;color:#57606a;}
.tip{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:10px;font-size:14px;}
.tip .icon{font-size:38px;opacity:.35;}
</style></head><body>
<div class="tip">
<div class="icon">◻</div>
<div>沙箱待命中</div>
<div style="font-size:12px;color:#8b949e;">点击上方按钮运行测试,或在下方注入代码</div>
</div>
</body></html>`;
statusBadge.textContent = '沙箱已重载';
log('沙箱已重载', 'ok');
}
function injectToSandbox(code) {
const doc = frame.contentDocument;
if (!doc) { log('无法访问沙箱文档', 'error'); return; }
doc.open();
doc.write(code);
doc.close();
statusBadge.textContent = '已注入 · ' + new Date().toTimeString().slice(0, 8);
log('代码已注入沙箱', 'ok');
}
// ================== 测试用例 ==================
// 通用:拦截器 v2 简化版代码片段
function interceptorCode() {
return `
(function(){
var NOTICE='block-notice', t=null;
function notice(m){var e=document.getElementById(NOTICE);if(!e)return;e.textContent=m;e.classList.add('show');clearTimeout(t);t=setTimeout(function(){e.classList.remove('show');},2000);}
var KW=['fake-virus','无害弹窗','绕过演示','overlay'];
function has(txt){if(!txt)return false;var s=String(txt).toLowerCase();return KW.some(function(k){return s.indexOf(k.toLowerCase())>=0;});}
function susp(n){
if(!n||n.nodeType!==1)return false;
if(n.id&&has(n.id))return true;
if(n.className&&typeof n.className==='string'&&has(n.className))return true;
try{if(n.textContent&&has(n.textContent))return true;}catch(e){}
if(n.tagName==='IFRAME'){var sd=n.getAttribute&&n.getAttribute('srcdoc');if(sd&&has(sd))return true;}
if(n.querySelector&&n.querySelector('#fake-virus'))return true;
return false;
}
function scan(root,d){
if(!root||d>8)return null;
if(susp(root))return root;
if(root.children){for(var i=0;i<root.children.length;i++){var h=scan(root.children[i],d+1);if(h)return h;}}
if(root.shadowRoot){var h2=scan(root.shadowRoot,d+1);if(h2)return h2;}
return null;
}
function kill(n,m){if(!n)return;try{if(n.parentNode)n.parentNode.removeChild(n);}catch(e){}notice(m||'已阻止');}
var oa=Node.prototype.appendChild, oi=Node.prototype.insertBefore;
Node.prototype.appendChild=function(n){if(susp(n)){notice('已阻止:检测到可疑弹窗');return n;}return oa.call(this,n);};
Node.prototype.insertBefore=function(n,r){if(susp(n)){notice('已阻止:检测到可疑弹窗');return n;}return oi.call(this,n,r);};
new MutationObserver(function(ms){ms.forEach(function(m){m.addedNodes.forEach(function(n){if(susp(n))kill(n,'已阻止:检测到可疑弹窗');});});}).observe(document.documentElement,{childList:true,subtree:true,attributes:true,attributeFilter:['srcdoc','src','style','class','id']});
setInterval(function(){var h=scan(document.documentElement,0);if(h&&h!==document.documentElement&&h.parentNode)kill(h,'已阻止:检测到可疑弹窗');},400);
})();`;
}
function shellHead(title) {
return `<!DOCTYPE html>
<html><head><meta charset="UTF-8"><style>
body{margin:0;font-family:-apple-system,sans-serif;background:#f6f8fa;color:#333;min-height:100vh;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:10px;}
h1{font-size:18px;color:#57606a;font-weight:600;}
#block-notice{position:fixed;top:20px;left:50%;transform:translateX(-50%);background:#3fb950;color:#fff;padding:10px 22px;border-radius:999px;font-weight:600;opacity:0;transition:opacity .3s;z-index:2147483647;font-size:13px;box-shadow:0 4px 18px rgba(63,185,80,.35);}
#block-notice.show{opacity:1;}
</style></head><body>
<h1>${title}</h1>
<div id="block-notice">已阻止</div>`;
}
// 测试1
function test1() {
const code = shellHead('测试1 · 原始弹窗') + `
<script>${interceptorCode()}<\/script>
<script>
setTimeout(function(){
var o=document.createElement('div');
o.id='fake-virus';
o.innerHTML='<div style="position:fixed;inset:0;background:rgba(0,0,0,.78);display:flex;align-items:center;justify-content:center;z-index:999999;"><div style="background:#101010;color:#fff;padding:26px;border-radius:18px;text-align:center;"><div style="color:#00ff88;font-weight:700;font-size:20px;">⚠ 无害弹窗演示</div><div style="font-size:56px;font-weight:800;margin:10px 0;">3</div></div></div>';
document.body.appendChild(o);
}, 500);
<\/script>
</body></html>`;
injectToSandbox(code);
log('运行测试1:原始弹窗', 'info');
}
// 测试2
function test2() {
const code = shellHead('测试2 · iframe 绕过') + `
<script>${interceptorCode()}<\/script>
<script>
setTimeout(function(){
var f=document.createElement('iframe');
f.style.cssText='position:fixed;inset:0;width:100%;height:100%;border:0;z-index:999999;';
f.srcdoc='<!DOCTYPE html><html><head><style>html,body{margin:0;height:100%;background:transparent;}.overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.78);}.box{padding:26px;border-radius:18px;background:#101010;color:#fff;text-align:center;font-family:sans-serif;}.title{color:#00ff88;font-weight:700;font-size:20px;}.count{font-size:56px;font-weight:800;}.tip{color:#aaa;font-size:13px;}<\/style><\/head><body><div class="overlay"><div class="box"><div class="title">⚠ 绕过演示</div><div class="count" id="c">3</div><div class="tip">iframe 隔离</div></div></div><script>(function(){var e=document.getElementById("c"),n=3,t=setInterval(function(){n--;if(n>0){e.textContent=n;}else{clearInterval(t);}},1000);})();<\/script></body></html>';
document.body.appendChild(f);
}, 500);
<\/script>
</body></html>`;
injectToSandbox(code);
log('运行测试2:iframe 绕过', 'info');
}
// 测试3
function test3() {
const code = shellHead('测试3 · Shadow DOM 绕过') + `
<script>${interceptorCode()}<\/script>
<script>
setTimeout(function(){
var host=document.createElement('div');
host.id='shadow-host';
document.body.appendChild(host);
var sh=host.attachShadow({mode:'open'});
sh.innerHTML='<style>.overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.78);z-index:999999;}.box{padding:26px;border-radius:18px;background:#101010;color:#fff;text-align:center;font-family:sans-serif;}.title{color:#af52de;font-weight:700;font-size:20px;}.count{font-size:56px;font-weight:800;}.tip{color:#aaa;font-size:13px;}<\/style><div class="overlay"><div class="box"><div class="title">⚠ Shadow DOM 绕过</div><div class="count">3</div><div class="tip">隔离样式与 DOM</div></div></div>';
}, 500);
<\/script>
</body></html>`;
injectToSandbox(code);
log('运行测试3:Shadow DOM 绕过', 'info');
}
// ================== 事件 ==================
document.getElementById('btnReload').onclick = resetSandbox;
document.getElementById('btnTest1').onclick = test1;
document.getElementById('btnTest2').onclick = test2;
document.getElementById('btnTest3').onclick = test3;
document.getElementById('btnClearLog').onclick = function () {
consoleEl.innerHTML = '';
logCount = 0;
logCountEl.textContent = '0';
log('日志已清空', 'info');
};
document.getElementById('btnInject').onclick = function () {
const code = document.getElementById('codeInput').value;
if (!code.trim()) { log('代码为空,无法注入', 'warn'); return; }
injectToSandbox(code);
};
document.getElementById('btnWrapDoc').onclick = function () {
const raw = document.getElementById('codeInput').value;
const wrapped = `<!DOCTYPE html>
<html><head><meta charset="UTF-8"><style>body{margin:0;font-family:-apple-system,sans-serif;background:#f6f8fa;color:#333;min-height:100vh;display:flex;align-items:center;justify-content:center;}</style></head>
<body>
${raw}
</body></html>`;
document.getElementById('codeInput').value = wrapped;
log('已包裹为完整文档', 'info');
};
// 初始化
resetSandbox();
log('实验场 Pro 初始化完成', 'ok');
log('提示:点击上方测试按钮,或在下方注入代码', 'info');
</script>
</body>
</html>Game Source: 安全实验场 Pro
Creator: EpicCoder88
Libraries: none
Complexity: complex (584 lines, 19.1 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: pro-epiccoder88" to link back to the original. Then publish at arcadelab.ai/publish.