This commit is contained in:
sickle committed 2025-08-18 11:50:00 +08:00
1 parent 9762c4fc94
commit 146019143a
4 files changed
+156 -120

No files matched your search

+15 -4
View File
@@ -6,6 +6,17 @@
**雪瞳** 是一款用于检测和提取网页中敏感信息的 Chrome 浏览器扩展。帮助用户快速获取网页中的敏感信息,并进行分析和处理。
---
## 一些使用技巧(方便好用)
对于单个结果,鼠标悬浮即可看到来源,单击左键复制来源,ctrl+左键跳转来源链接,单击右键复制结果
---
## v0.2.7.3更新
1. 大幅优化搜索时的网页卡顿问题,体验更流畅
2. 适配webpack网页js搜集,绝不放过每一处可能存在的js
和其他同类插件不同,雪瞳致力于更全面的js信息搜集,因此搜索时请多给雪瞳一些等待的时间,如果您发现了一些bug或是有更好的想法,欢迎提issue。
---
## v0.2.7.2更新
@@ -17,17 +28,17 @@
6. 深度扫描新增js文件路径猜解拼接,提高访问命中率
7. 其他细节优化......
下个版本预计更新内容:当前版本问题优化,指纹添加,路径拼接优化,特殊网页处理,AI?,Maybe
注意:
1. 深度扫描在一些情况下会出现很多js误匹配,此功能后续还需要多完善,正常情况下为了维持良好体验不建议使用
2. 关于备案信息的查询建议注册登录接口盒子使用自己的Id和key,因为公开的接口查询有限制
---
下个版本预计更新内容:当前版本问题优化,指纹添加,路径拼接优化,特殊网页处理,AI?,Maybe
如果您有更好的第三方接口欢迎提issue,当然您愿意分享的话。
firfox版本后续更新,可能延迟一周左右。欢迎提issue,感谢支持!
---
## 功能特点
1. 实现多种敏感信息,指纹等检测分类
@@ -37,7 +48,7 @@ firfox版本后续更新,可能延迟一周左右。欢迎提issue,感谢支
## 使用详情
1. 将源码下载为压缩包文件后解压或直接clone
2. 在浏览器扩展中打开开发者工具
2. 在浏览器扩展中打开开发者模式
3. 点击加载解压的扩展,选中解压的文件夹,即可在插件列表中使用插件
---
+48 -13
View File
@@ -118,16 +118,12 @@ async function handleFetchRequest(request, sender, sendResponse) {
}
}
}
// 存储服务器指纹信息
let serverFingerprints = new Map();
const analyticsDetected = {
baidu: new Map(),
yahoo: new Map(),
google: new Map(),
};
// 统计服务检测处理函数
function handleAnalyticsDetection(details, type) {
if (analyticsDetected[type].get(details.tabId)) {
return;
@@ -141,8 +137,6 @@ function handleAnalyticsDetection(details, type) {
description: FINGERPRINT_CONFIG.ANALYTICS[type].description,
version: FINGERPRINT_CONFIG.ANALYTICS[type].version
});
// 更新存储
serverFingerprints.set(details.tabId, fingerprints);
}
@@ -195,7 +189,6 @@ function processHeaders(headers, tabId) {
const headerMap = new Map(
headers.map(h => [h.name.toLowerCase(), h.value])
);
// 遍历所有指纹配置进行匹配
for (const config of FINGERPRINT_CONFIG.HEADERS) {
const headerValue = headerMap.get(config.header);
if (headerValue) {
@@ -253,19 +246,14 @@ function getFingerprints(tabId){
serverFingerprints.set(tabId, fingerprints);
return fingerprints;
}
// 修改监听器的处理方式
chrome.webRequest.onHeadersReceived.addListener(
async (details) => {
// 只处理主文档请求,并且立即返回响应头
if (details.type !== 'main_frame') return { responseHeaders: details.responseHeaders };
if (!details.responseHeaders) return { responseHeaders: details.responseHeaders };
// 使用setTimeout将指纹处理放到下一个事件循环
setTimeout(() => {
const fingerprints = processHeaders(details.responseHeaders, details.tabId);
serverFingerprints.set(details.tabId, fingerprints);
// 延迟处理cookies
chrome.cookies.getAll({ url: details.url }, (cookies) => {
if (cookies.length > 0) {
const cookieNames = cookies.map(cookie => cookie.name).join(';');
@@ -283,6 +271,47 @@ chrome.webRequest.onHeadersReceived.addListener(
{ urls: ['<all_urls>'] },
['responseHeaders']
);
function performRegexMatching(chunk, patterns, patternType) {
const matches = [];
let maxIterations = 10000;
try {
for (const patternInfo of patterns) {
const { pattern: patternString} = patternInfo;
let regex;
try {
const match = patternString.match(/^\/(.+)\/([gimuy]*)$/);
if (match) {
regex = new RegExp(match[1], match[2]);
}
} catch (e) {
console.error(`无效的正则表达式: ${patternString}`, e);
continue;
}
let patternLastIndex = 0;
let match;
while ((match = regex.exec(chunk)) !== null) {
if (regex.lastIndex <= patternLastIndex) {
console.warn(`检测到可能的无限循环: ${patternType} Pattern - ${patternName}`);
break;
}
patternLastIndex = regex.lastIndex;
if (--maxIterations <= 0) {
console.warn(`达到最大迭代次数: ${patternType}`);
break;
}
matches.push({
match: match[0],
});
}
regex.lastIndex = 0;
}
} catch (e) {
console.error(`${patternType} 匹配出错:`, e);
}
return matches;
}
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.to !== 'background') return false;
@@ -317,7 +346,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
}
case 'REGISTER_CONTENT': {
const tabJs = Array.from(tabJsMap[sender.tab.id] || []);
sendResponse({ tabJs: tabJs });
sendResponse({ tabJs: tabJs, tabId: sender.tab.id });
return true;
}
case 'UPDATE_BADGE': {
@@ -328,6 +357,12 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
sendResponse({ tabId: sender.tab?.id });
return true;
}
case 'REGEX_MATCH': {
const { chunk, patterns, patternType } = request;
const matches = performRegexMatching(chunk, patterns, patternType);
sendResponse({ matches });
return true;
}
case 'GET_SITE_ANALYSIS': {
const domain = getRootDomain(request.domain);
const tabId = request.tabId;
+92 -102
View File
@@ -7,7 +7,7 @@ let maxDepth = 3;
let tabJs = new Set();
let isWhitelisted = false;
let hostname = null;
let isUseWebpack = false;
const tree = {};
const jsQueue = [];
const queueSet = new Set();
@@ -27,6 +27,8 @@ async function initSettings() {
chrome.runtime.sendMessage({ type: 'REGISTER_CONTENT', from: 'content', to: 'background' }, (response) => {
if(isWhitelisted) return;
tabJs = response.tabJs;
currentTabId = response.tabId;
getTabResults(currentTabId);
tabJs.forEach(url => {
enqueueJsUrl(url, 'background');
});
@@ -47,7 +49,6 @@ const waitForDependencies = () => {
})();
});
};
// 获取tabId
const getTabId = () => {
return new Promise(resolve => {
chrome.runtime.sendMessage({ type: 'GET_TAB_ID', from: 'content', to: 'background'}, response => {
@@ -109,10 +110,10 @@ function enqueueJsUrl(url, source='page', basePath = '') {
jsFileMap.set(fileName, filePath);
queueSet.add(url);
jsQueue.push(url);
tabResults.get(currentTabId).jsFiles.set(url, "暂无法展示来源");
processJsQueue();
}
}
//分块处理
function* splitIntoChunks(text) {
if (text.length <= MAX_CHUNK_SIZE) {
yield text;
@@ -131,8 +132,6 @@ function* splitIntoChunks(text) {
currentLines = [];
currentSize = 0;
}
// 对超长单行进行切割
if (line.length > MAX_CHUNK_SIZE) {
for (let i = 0; i < line.length; i += MAX_CHUNK_SIZE) {
yield line.slice(i, i + MAX_CHUNK_SIZE);
@@ -152,7 +151,7 @@ function* splitIntoChunks(text) {
}
}
//匹配函数
const matchPatterns = (chunk, isHtmlContent = false, url) => {
const matchPatterns = async (chunk, isHtmlContent = false, url) => {
const patterns = Object.entries(SCANNER_CONFIG.PATTERNS);
const resultsSet = tabResults.get(currentTabId);
let update = false;
@@ -166,7 +165,6 @@ const matchPatterns = (chunk, isHtmlContent = false, url) => {
try {
if (key === 'FINGER') {
// 对每个模式进行匹配
for (const {pattern: fingerPattern, name: fingerName, class: fingerClass, type: fingerType, description: fingerDescription, extType: fingerExtType, extName: fingerExtName} of pattern.patterns) {
if (resultsSet.fingers.has(fingerClass)) continue;
const matches = chunk.match(fingerPattern);
@@ -188,7 +186,6 @@ const matchPatterns = (chunk, isHtmlContent = false, url) => {
}
continue;
}
// 域名使用不同的匹配模式
if (key === 'DOMAIN') {
const domainPattern = isHtmlContent ? pattern : SCANNER_CONFIG.PATTERNS.DOMAIN_RESOURCE;
while ((match = domainPattern.exec(chunk)) !== null) {
@@ -209,7 +206,6 @@ const matchPatterns = (chunk, isHtmlContent = false, url) => {
}
continue;
}
// API模式特殊处理
if (key === 'API') {
const apiPattern = SCANNER_CONFIG.API.PATTERN;
apiPattern.lastIndex = 0;
@@ -230,58 +226,39 @@ const matchPatterns = (chunk, isHtmlContent = false, url) => {
}
continue;
}
if (key === 'CREDENTIALS') {
// 对每个模式进行匹配
for (const {pattern: credentialsPattern} of pattern.patterns) {
let patternLastIndex = 0; // 每个模式独立的 lastIndex
while ((match = credentialsPattern.exec(chunk)) !== null) {
if (credentialsPattern.lastIndex <= patternLastIndex) {
window.logger.warn(`检测到可能的无限循环: CREDENTIALS Pattern`);
break;
}
patternLastIndex = credentialsPattern.lastIndex;
if (key === 'CREDENTIALS' || key === 'ID_KEY' || key === 'EMAIL') {
let patterns = [];
if (key === 'EMAIL') {
patterns = [{"pattern": SCANNER_CONFIG.PATTERNS.EMAIL.toString()}];
}else{
patterns = pattern.patterns.map(p => ({
pattern: p.pattern.toString(),
}));
}
try {
const response = await new Promise((resolve) => {
chrome.runtime.sendMessage({
type: 'REGEX_MATCH',
from: 'content',
to: 'background',
chunk: chunk,
patterns: patterns,
patternType: key
}, resolve);
});
if (--maxIterations <= 0) {
window.logger.warn(`达到最大迭代次数: CREDENTIALS`);
break;
}
if (filter(match[0], url, resultsSet)) {
update = true;
}
if (response && response.matches) {
response.matches.forEach(({match}) => {
if (filter(match, url, resultsSet)) {
update = true;
}
});
}
// 重置正则表达式的 lastIndex
credentialsPattern.lastIndex = 0;
} catch (e) {
window.logger.error('CREDENTIALS匹配出错:', e);
}
continue;
}
// ID_KEY模式特殊处理
if (key === 'ID_KEY') {
// 对每个模式进行匹配
for (const {pattern: idKeyPattern} of pattern.patterns) {
let patternLastIndex = 0; // 每个模式独立的 lastIndex
while ((match = idKeyPattern.exec(chunk)) !== null) {
if (idKeyPattern.lastIndex <= patternLastIndex) {
window.logger.warn(`检测到可能的无限循环: ID_KEY Pattern`);
break;
}
patternLastIndex = idKeyPattern.lastIndex;
if (--maxIterations <= 0) {
window.logger.warn(`达到最大迭代次数: ID_KEY`);
break;
}
if (filter(match[0], url, resultsSet)) {
update = true;
}
}
// 重置正则表达式的 lastIndex
idKeyPattern.lastIndex = 0;
}
continue;
}
// 其他模式使用exec
while ((match = pattern.exec(chunk)) !== null) {
if (pattern.lastIndex <= lastIndex) {
window.logger.warn(`检测到可能的无限循环: ${pattern}`);
@@ -305,12 +282,34 @@ const matchPatterns = (chunk, isHtmlContent = false, url) => {
}
return update;
};
// 收集页面的js
const collectJsUrls = (content) => {
const jsUrls = new Set();
const baseUrl = window.location.origin;
const jsPattern = /['"](?:[^'"]+\.(?:js)(?:\?[^\s'"]*)?)['"]/g;
const matches = Array.from(content.matchAll(jsPattern))
const chunkCodePattern = /("[a-z/]*")[+|()\[\]\{\}a-z]*\+"."\+{(?:"?[0-9a-z-]*"?:"?[0-9a-z-]{1,}"?,?){1,}}\[[a-z]\]\+".js"/i;
const chunkJsPattern = /"?[0-9a-z-]*"?\s*:\s*"?[0-9a-z-]*"?/g;
const chunkMatch = content.match(chunkCodePattern);
if (chunkMatch) {
isUseWebpack = true;
let chunkBasePath = chunkMatch[1].slice(1, -1);
if (!chunkBasePath.startsWith('/')) {
chunkBasePath = '/' + chunkBasePath;
}
Array.from(chunkMatch[0].matchAll(chunkJsPattern)).forEach(chunkJsMatch => {
let chunkId = chunkJsMatch[0].split(":")[0];
if(chunkId.includes("\"")||chunkId.includes("\'")){
chunkId = chunkId.slice(1, -1);
}
let chunkHash = chunkJsMatch[0].split(":")[1];
if(chunkHash.includes("\"")||chunkHash.includes("\'")){
chunkHash = chunkHash.slice(1, -1);
}
const chunkUrl = baseUrl + chunkBasePath + chunkId + '.' + chunkHash + '.js';
jsUrls.add(chunkUrl);
});
}
if (!isUseWebpack && deepScanEnabled) {
const matches = Array.from(content.matchAll(jsPattern))
.map(match => {
const path = match[0].slice(1, -1);
let url = null
@@ -334,8 +333,8 @@ const collectJsUrls = (content) => {
}
})
.filter(url => url !== null);
matches.forEach(url => jsUrls.add(url));
matches.forEach(url => jsUrls.add(url));
}
return jsUrls;
};
//扫描函数
@@ -344,7 +343,7 @@ async function scanSources(sources, isHtmlContent = false, url) {
for (const source of sources) {
if (!source) continue;
for (const chunk of splitIntoChunks(source)) {
let update = matchPatterns(chunk, isHtmlContent, url);
let update = await matchPatterns(chunk, isHtmlContent, url);
if (update) sendUpdate();
await new Promise(r => setTimeout(r, 0));
}
@@ -365,9 +364,8 @@ const debounceScan = () => {
if (htmlContent) {
scanSources([htmlContent], true, document.location.href);
}
}, 1000); // 2秒内的变化会被合并为一次扫描
}, 1000);
};
//监听 DOM 变化
const observer = new MutationObserver((mutations) => {
if (!dynamicScanEnabled) return;
@@ -383,38 +381,38 @@ const observer = new MutationObserver((mutations) => {
debounceScan();
}
});
// 初始化扫描
function getTabResults(currentTabId){
if (!tabResults.has(currentTabId)) {
tabResults.set(currentTabId, {
domains: new Map(),
absoluteApis: new Map(),
apis: new Map(),
moduleFiles: new Map(),
docFiles: new Map(),
ips: new Map(),
phones: new Map(),
emails: new Map(),
idcards: new Map(),
jwts: new Map(),
imageFiles: new Map(),
jsFiles: new Map(),
vueFiles: new Map(),
urls: new Map(),
githubUrls: new Map(),
companies: new Map(),
credentials: new Map(),
cookies: new Map(),
idKeys: new Map(),
fingers: new Map(),
progress: new Map()
});
}
}
async function initScan() {
try {
await waitForDependencies();
if (!currentTabId) await getTabId();
if (!tabResults.has(currentTabId)) {
tabResults.set(currentTabId, {
domains: new Map(),
absoluteApis: new Map(),
apis: new Map(),
moduleFiles: new Map(),
docFiles: new Map(),
ips: new Map(),
phones: new Map(),
emails: new Map(),
idcards: new Map(),
jwts: new Map(),
imageFiles: new Map(),
jsFiles: new Map(),
vueFiles: new Map(),
urls: new Map(),
githubUrls: new Map(),
companies: new Map(),
credentials: new Map(),
cookies: new Map(),
idKeys: new Map(),
fingers: new Map(),
progress: new Map()
});
}
getTabResults(currentTabId);
window.logger.info('开始扫描...');
Object.keys(tabResults.get(currentTabId)).forEach(key => {
tabResults.get(currentTabId)[key].clear();
@@ -463,7 +461,6 @@ const sendUpdate = () => {
results: results,
tabId: currentTabId,
}).catch(() => {
// 忽略消息发送失败的错误
});
chrome.runtime.sendMessage({
@@ -473,7 +470,6 @@ const sendUpdate = () => {
results: results,
tabId: currentTabId,
}).catch(() => {
// 忽略消息发送失败的错误
});
} catch (e) {
if (e.message !== 'Extension context invalidated.') {
@@ -504,17 +500,11 @@ async function handleJsTask(url) {
const response = await new Promise(resolve => {
chrome.runtime.sendMessage({ type: 'FETCH_JS', url: url, from: 'content', to: 'background'}, resolve);
});
if (response?.content) {
//扫描js
await scanSources([response.content], false, url);
if(deepScanEnabled){
// 收集新的js
const newJsUrls = collectJsUrls(response.content);
const basePath = getBasePath(url);
if(newJsUrls){
newJsUrls.forEach(jsUrl => enqueueJsUrl(jsUrl, 'page', basePath));
}
const newJsUrls = collectJsUrls(response.content);
if(newJsUrls){
newJsUrls.forEach(jsUrl => enqueueJsUrl(jsUrl, 'page', getBasePath(url)));
}
}
} catch (e) {
+1 -1
View File
@@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "雪瞳",
"version": "0.2.7.2",
"version": "0.2.7.3",
"description": "洞悉无形,守护无界",
"icons": {