🎮ArcadeLab

安全实验场 Pro

by EpicCoder88
584 lines19.1 KB
▶ Play
<!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 => ({
      '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'
    }[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.