This commit is contained in:
sickle committed 2025-08-07 20:31:07 +08:00
1 parent 137eb03a03
commit 65234d54d9
16 files changed
+1162 -1370

No files matched your search

+45 -23
View File
@@ -6,13 +6,33 @@
**雪瞳** 是一款用于检测和提取网页中敏感信息的 Chrome 浏览器扩展。帮助用户快速获取网页中的敏感信息,并进行分析和处理。
---
## v0.2.7.2更新
1. 添加来源显示(鼠标悬停对应结果显示数据来源,左键点击即可复制)
2. 新增自定义白名单
3. 新增进度条,实时查看扫描进度
4. 优化访问部分网页会卡顿的情况,体验更丝滑
5. 优化扫描处理逻辑,更快的处理,更全面的js搜集
6. 深度扫描新增js文件路径猜解拼接,提高访问命中率
7. 其他细节优化......
下个版本预计更新内容:当前版本问题优化,指纹添加,路径拼接优化,特殊网页处理,AI?,Maybe
注意:
1. 深度扫描在一些情况下会出现很多js误匹配,此功能后续还需要多完善,正常情况下为了维持良好体验不建议使用
2. 关于备案信息的查询建议注册登录接口盒子使用自己的Id和key,因为公开的接口查询有限制
如果您有更好的第三方接口欢迎提issue,当然您愿意分享的话。
firfox版本后续更新,可能延迟一周左右。欢迎提issue,感谢支持!
---
## 功能特点
1. 实现多种敏感信息,指纹等检测分类
2. 支持动态扫描,针对动态渲染的网页信息实时捕获
3. 支持深度扫描,针对某些js文件中嵌套js的网站进行深度扫描
4. 插件运行在浏览器环境中,天然环境优势,请求和用户正常浏览相似,可绕过部分爬虫识别以及部分waf拦截
## 使用详情
@@ -20,8 +40,6 @@
2. 在浏览器扩展中打开开发者工具
3. 点击加载解压的扩展,选中解压的文件夹,即可在插件列表中使用插件
**firefox搜索插件"snoweyes"添加即用**
---
## 界面展示
@@ -32,27 +50,31 @@
### 信息收集页面
<div style="text-align:center">
<img src="icons/souji1.png" alt="信息收集界面" width="80%">
<img src="icons/souji2.png" alt="信息收集界面" width="80%">
<img src="icons/souji3.png" alt="信息收集界面" width="80%">
<img src="icons/souji1.png" alt="信息收集界面" width="40%">
<img src="icons/souji2.png" alt="信息收集界面" width="40%">
</div>
### 指纹嗅探页面
<div style="text-align:center">
<img src="icons/xiutan1.png" alt="指纹嗅探" width="40%">
<img src="icons/xiutan2.png" alt="指纹嗅探" width="40%">
</div>
### 网站解析页面
<div style="text-align:center">
<img src="icons/jiexi1.png" alt="网站解析" width="40%">
<img src="icons/jiexi2.png" alt="网站解析" width="40%">
</div>
### 配置页面
<div style="text-align:center">
<img src="icons/config.png" alt="白名单" width="40%">
</div>
### 白名单页面
<div style="text-align:center">
<img src="icons/white.png" alt="白名单" width="80%">
</div>
### 指纹嗅探页面
<div style="text-align:center">
<img src="icons/xiutan1.png" alt="指纹嗅探" width="80%">
<img src="icons/xiutan2.png" alt="指纹嗅探" width="80%">
</div>
### 网站解析页面
<div style="text-align:center">
<img src="icons/jiexi1.png" alt="网站解析" width="80%">
<img src="icons/jiexi2.png" alt="网站解析" width="80%">
</div>
---
## 第三方接口声明
@@ -66,12 +88,12 @@
程序仍在开发中,一些功能没有完善还请见谅(不定时更新,如果有时间的话)。
1. 大型网页的完整扫描可能需要点时间(极端情况比如一个网站加载上千个js文件,还可能会出现卡顿的情况,不过请耐心等待)。
2. 可能会出现未知bug。(毕竟这事情,谁也说不准,万一呢)
3. 如需修改黑白名单,可前往 `content/config/scanner.config.js` 文件中根据注释找到域名相关配置进行修改。
1. 如遇js文件过多扫描需要时间,会出现卡顿情况。
2. 可能会出现未知bug。(毕竟这事情,谁也说不准,万一呢)。
3. 如遇问题或有新的设计想法,欢迎提issue,看到了就会回复
tips: 扫描出的单个结果右键点击即可复制。
tips: 左键复制来源,右键复制结果
---
**雪瞳——洞悉无形,守护无界。**
**雪瞳——洞悉无形,守护无界。**
+312 -406
View File
@@ -1,76 +1,124 @@
import { FINGERPRINT_CONFIG } from './config/fingerprint.config.js';
// 在文件开头添加一个Map来存储每个标签页的结果计数
const tabCounts = new Map();
const tabCountsCache = new Map();
const tabJsMap = {}
// 修改updateBadge函数
chrome.webNavigation.onCommitted.addListener(details => {
const { tabId, frameId } = details;
if (frameId === 0 && tabJsMap[tabId]) {
tabJsMap[tabId].clear();
}
});
function setTabCount(tabId, count) {
tabCountsCache.set(tabId, count);
chrome.storage.session.set({ [`tab_${tabId}`]: count });
}
function getTabCount(tabId, callback) {
if (tabCountsCache.has(tabId)) {
callback(tabCountsCache.get(tabId));
return;
}
chrome.storage.session.get(`tab_${tabId}`, (data) => {
const count = data[`tab_${tabId}`] || 0;
tabCountsCache.set(tabId, count);
callback(count);
});
}
function setBadgeUI(tabId, count) {
const hasCount = count > 0;
chrome.action.setBadgeText({
text: hasCount ? String(count) : '',
tabId
});
chrome.action.setBadgeBackgroundColor({
color: hasCount ? '#4dabf7' : '#666666',
tabId
});
}
function updateBadge(results, tabId) {
const categories = [
results.domains,
results.absoluteApis,
results.apis,
results.moduleFiles,
results.docFiles,
results.ips,
results.phones,
results.emails,
results.idcards,
results.jwts,
results.imageFiles,
results.jsFiles,
results.vueFiles,
results.urls,
results.githubUrls,
results.companies,
results.credentials,
results.cookies,
results.idKeys,
const fields = [
'domains', 'absoluteApis', 'apis', 'moduleFiles', 'docFiles', 'ips', 'phones',
'emails', 'idcards', 'jwts', 'imageFiles', 'jsFiles', 'vueFiles', 'urls',
'githubUrls', 'companies', 'credentials', 'cookies', 'idKeys'
];
const nonEmptyCategories = categories.filter(category =>
Array.isArray(category) && category.length > 0
).length;
const count = fields.reduce((acc, field) => {
const arr = results[field];
return acc + (Array.isArray(arr) && arr.length > 0 ? 1 : 0);
}, 0);
// 存储该标签页的计数
tabCounts.set(tabId, nonEmptyCategories);
setTabCount(tabId, count);
// 更新当前活动标签页的badge
chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
if (tabs[0]?.id === tabId) {
chrome.action.setBadgeText({
text: nonEmptyCategories > 0 ? nonEmptyCategories.toString() : '',
tabId: tabId
});
chrome.action.setBadgeBackgroundColor({
color: nonEmptyCategories > 0 ? '#4dabf7' : '#666666',
tabId: tabId
});
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const activeTab = tabs?.[0];
if (activeTab?.id === tabId) {
setBadgeUI(tabId, count);
}
});
}
// 添加标签页切换事件监听器
chrome.tabs.onActivated.addListener((activeInfo) => {
const count = tabCounts.get(activeInfo.tabId);
chrome.action.setBadgeText({
text: count > 0 ? count.toString() : '',
tabId: activeInfo.tabId
});
chrome.action.setBadgeBackgroundColor({
color: count > 0 ? '#4dabf7' : '#666666',
tabId: activeInfo.tabId
chrome.tabs.onActivated.addListener(({ tabId }) => {
getTabCount(tabId, (count) => {
setBadgeUI(tabId, count);
});
});
// 添加标签页移除事件监听器
chrome.tabs.onRemoved.addListener((tabId) => {
// 清除该标签页的数据
tabCounts.delete(tabId);
tabCountsCache.delete(tabId);
chrome.storage.session.remove(`tab_${tabId}`);
chrome.storage.session.remove(`analysis_${tabId}`)
if(tabJsMap[tabId]){
tabJsMap[tabId].clear();
}
Object.values(analyticsDetected).forEach(map => map.delete(tabId));
serverFingerprints.delete(tabId);
});
async function tryFetchContent(url) {
const response = await fetch(url, {
headers: {
'Accept': '*/*',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
},
credentials: 'omit'
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.text();
}
async function fallbackFetchContentViaTab(tabId, url) {
const [result] = await chrome.scripting.executeScript({
target: { tabId },
func: (url) => {
return fetch(url, { credentials: 'omit' }).then(res => res.text());
},
args: [url]
});
return result?.result ?? null;
}
async function handleFetchRequest(request, sender, sendResponse) {
try {
const content = await tryFetchContent(request.url);
sendResponse({ content });
} catch (error) {
console.warn('Primary fetch failed:', error.message);
if (sender.tab?.id) {
try {
const fallbackContent = await fallbackFetchContentViaTab(sender.tab.id, request.url);
sendResponse({ content: fallbackContent });
} catch (e2) {
console.warn('Fallback fetch via tab failed:', e2.message);
sendResponse({ content: null });
}
} else {
sendResponse({ content: null });
}
}
}
// 存储服务器指纹信息
let serverFingerprints = new Map();
@@ -81,28 +129,11 @@ const analyticsDetected = {
};
// 统计服务检测处理函数
function handleAnalyticsDetection(details, type) {
// 检查是否已经检测到
if (analyticsDetected[type].get(details.tabId)) {
return;
}
let fingerprints = serverFingerprints.get(details.tabId);
if (!fingerprints) {
fingerprints = {
server: [],
component: [],
technology: [],
security: [],
analytics: [],
builder: [],
framework: [],
os: [],
panel: [],
cdn: [],
nameMap: new Map()
};
serverFingerprints.set(details.tabId, fingerprints);
}
let fingerprints = getFingerprints(details.tabId);
analyticsDetected[type].set(details.tabId, true);
fingerprints.analytics.push({
@@ -115,7 +146,7 @@ function handleAnalyticsDetection(details, type) {
serverFingerprints.set(details.tabId, fingerprints);
}
// 使用单个监听器处理所有统计服务
const analyticsPatterns = Object.entries(FINGERPRINT_CONFIG.ANALYTICS).map(([type, config]) => ({
pattern: config.pattern,
type: type
@@ -129,11 +160,22 @@ chrome.webRequest.onBeforeRequest.addListener(
if (matchedAnalytics) {
handleAnalyticsDetection(details, matchedAnalytics.type);
}
const { tabId, url, type } = details;
if (type !== 'script' || tabId < 0) return;
const initiator_url = new URL(details.initiator);
const current_url = new URL(url);
if (initiator_url.hostname != current_url.hostname) {
return;
}
if(!tabJsMap[tabId]){
tabJsMap[tabId] = new Set();
}
tabJsMap[tabId].add(url);
return;
},
{ urls: ['<all_urls>'] },
[]
);
// 识别Cookie
function identifyTechnologyFromCookie(cookieHeader) {
for (const cookie of FINGERPRINT_CONFIG.COOKIES) {
@@ -149,26 +191,10 @@ function identifyTechnologyFromCookie(cookieHeader) {
}
// 识别Header
function processHeaders(headers, tabId) {
let fingerprints = serverFingerprints.get(tabId);
if (!fingerprints) {
fingerprints = {
server: [],
component: [],
technology: [],
security: [],
analytics: [],
builder: [],
framework: [],
os: [],
panel: [],
cdn: [],
nameMap: new Map()
};
}
let fingerprints = getFingerprints(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);
@@ -207,6 +233,26 @@ function processHeaders(headers, tabId) {
return fingerprints;
}
function getFingerprints(tabId){
if(serverFingerprints.has(tabId)){
return serverFingerprints.get(tabId);
}
let fingerprints = {
server: [],
component: [],
technology: [],
security: [],
analytics: [],
builder: [],
framework: [],
os: [],
panel: [],
cdn: [],
nameMap: new Map()
};
serverFingerprints.set(tabId, fingerprints);
return fingerprints;
}
// 修改监听器的处理方式
chrome.webRequest.onHeadersReceived.addListener(
async (details) => {
@@ -238,158 +284,99 @@ chrome.webRequest.onHeadersReceived.addListener(
['responseHeaders']
);
// 监听器更新指纹
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.to !== 'background') return false;
try {
if (request.type === 'UPDATE_BUILDER') {
let fingerprints = serverFingerprints.get(sender.tab.id);
if (!fingerprints) {
fingerprints = {
server: [],
serverComponents: [],
technology: [],
security: [],
analytics: [],
builder: [],
framework: [],
os: [],
panel: [],
cdn: [],
nameMap: new Map()
};
serverFingerprints.set(sender.tab.id, fingerprints);
}
if(!fingerprints.nameMap.has(request.finger.name)) {
if(request.finger.extType && !fingerprints.nameMap.has(request.finger.extName)){
var extfingerprint = {};
extfingerprint['type'] = request.finger.extType;
extfingerprint['name'] = request.finger.extName;
extfingerprint['header'] = request.finger.name;
extfingerprint['description'] = `通过${extfingerprint.header}识别到网站使用${extfingerprint.name}${FINGERPRINT_CONFIG.DESCRIPTIONS.find(item=>item.name===request.finger.extType)?.description}`;
fingerprints[extfingerprint.type].push(extfingerprint);
fingerprints.nameMap.set(request.finger.extName, true);
switch (request.type) {
case 'UPDATE_BUILDER': {
let fingerprints = getFingerprints(sender.tab.id);
if (!fingerprints.nameMap.has(request.finger.name)) {
if (request.finger.extType && !fingerprints.nameMap.has(request.finger.extName)) {
var extfingerprint = {};
extfingerprint['type'] = request.finger.extType;
extfingerprint['name'] = request.finger.extName;
extfingerprint['header'] = request.finger.name;
extfingerprint['description'] = `通过${extfingerprint.header}识别到网站使用${extfingerprint.name}${FINGERPRINT_CONFIG.DESCRIPTIONS.find(item => item.name === request.finger.extType)?.description}`;
fingerprints[extfingerprint.type].push(extfingerprint);
fingerprints.nameMap.set(request.finger.extName, true);
}
fingerprints.nameMap.set(request.finger.name, true);
fingerprints[request.finger.type].push(request.finger);
serverFingerprints.set(sender.tab.id, fingerprints);
}
fingerprints.nameMap.set(request.finger.name, true); // 记录指纹名称
fingerprints[request.finger.type].push(request.finger);
serverFingerprints.set(sender.tab.id, fingerprints);
}
return true;
}
if (request.type === 'GET_FINGERPRINTS') {
const fingerprints = serverFingerprints.get(request.tabId);
if (fingerprints) {
sendResponse({
server: fingerprints.server,
component: fingerprints.component,
technology: fingerprints.technology,
security: fingerprints.security,
analytics: fingerprints.analytics,
builder: fingerprints.builder,
framework: fingerprints.framework,
os: fingerprints.os,
panel: fingerprints.panel,
cdn: fingerprints.cdn
});
} else {
sendResponse(null);
}
return true;
}
if (request.type === 'FETCH_JS') {
fetch(request.url, {
headers: {
'Accept': '*/*',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
},
credentials: 'omit' // 不发送 cookies
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.text();
})
.then(content => {
sendResponse({ content });
})
.catch(error => {
// 如果 fetch 失败,尝试使用 chrome.scripting.executeScript
if (sender.tab) {
chrome.scripting.executeScript({
target: { tabId: sender.tab.id },
function: (url) => {
return fetch(url, {
credentials: 'omit'
}).then(r => r.text());
},
args: [request.url]
})
.then(results => {
if (results[0]?.result) {
sendResponse({ content: results[0].result });
} else {
sendResponse({ content: null });
}
})
.catch(() => {
sendResponse({ content: null });
});
} else {
sendResponse({ content: null });
}
});
return true;
} else if (request.type === 'UPDATE_BADGE') {
updateBadge(request.results, request.tabId);
} else if (request.type === 'GET_TAB_ID') {
sendResponse({ tabId: sender.tab?.id });
return true;
} else if (request.type === 'GET_SITE_ANALYSIS') {
const domain = request.domain;
if (analysisPending) {
return true;
}
analysisPending = true;
case 'GET_FINGERPRINTS': {
const fingerprints = getFingerprints(request.tabId);
sendResponse(fingerprints);
return true;
}
case 'FETCH_JS': {
handleFetchRequest(request, sender, sendResponse);
return true;
}
case 'REGISTER_CONTENT': {
const tabJs = Array.from(tabJsMap[sender.tab.id] || []);
sendResponse({ tabJs: tabJs });
return true;
}
case 'UPDATE_BADGE': {
updateBadge(request.results, request.tabId);
return true;
}
case 'GET_TAB_ID': {
sendResponse({ tabId: sender.tab?.id });
return true;
}
case 'GET_SITE_ANALYSIS': {
const domain = getRootDomain(request.domain);
const tabId = request.tabId;
// 检查是否是内网IP
if (isPrivateIP(domain)) {
analysisPending = false;
sendResponse({
weight: null,
ip: null,
icp: null,
isComplete: true,
isPrivateIP: true
if (analysisPending) return true;
analysisPending = true;
if (isPrivateIP(domain)) {
analysisPending = false;
sendResponse({
weight: null,
ip: null,
icp: null,
isComplete: true,
isPrivateIP: true
});
return true;
}
getAnalysisFromStorage(tabId).then(cachedData => {
if (cachedData.isComplete) {
analysisPending = false;
sendResponse(cachedData);
return;
}
Promise.all([
cachedData.weight || fetchDomainWeight(domain, tabId),
cachedData.ip || fetchIpInfo(domain, tabId),
cachedData.icp || fetchIcpInfo(domain, tabId)
]).then(([weightData, ipData, icpData]) => {
analysisPending = false;
saveAnalysisToStorage(tabId, weightData, ipData, icpData);
sendResponse({
weight: weightData?.data || null,
ip: ipData?.data || null,
icp: icpData?.data || null,
isComplete: true,
isPrivateIP: false
});
}).catch(error => {
analysisPending = false;
console.error('分析请求失败:', error);
sendResponse(null);
});
});
return true;
}
// 首先检查缓存
const cachedData = getAnalysisFromCache(domain);
if (cachedData.isComplete) {
analysisPending = false;
sendResponse(cachedData);
return true;
}
Promise.all([
cachedData.weight || fetchDomainWeight(domain, sender.tab?.id),
cachedData.ip || fetchIpInfo(domain, sender.tab?.id),
cachedData.icp || fetchIcpInfo(domain, sender.tab?.id)
]).then(([weightData, ipData, icpData]) => {
analysisPending = false;
sendResponse({
weight: weightData,
ip: ipData,
icp: icpData
});
}).catch(error => {
analysisPending = false;
sendResponse(null);
});
return true;
}
} catch (error) {
console.error('消息处理出错:', error);
@@ -398,198 +385,117 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
}
});
// 创建通用缓存实例
const createCache = (expireTime = 30 * 60 * 1000) => ({
cache: new Map(),
get(key) {
const entry = this.cache.get(key);
return entry && Date.now() - entry.timestamp < expireTime ? entry.data : null;
},
set(key, data) {
this.cache.set(key, { data, timestamp: Date.now() });
}
});
function getAnalysisFromStorage(tabId) {
return new Promise(resolve => {
const key = `analysis_${tabId}`;
chrome.storage.session.get(key, res => {
const cache = res[key];
if (!cache) return resolve(emptyCache());
// 初始化各模块缓存
const caches = {
weight: createCache(),
ip: createCache(),
icp: createCache()
};
async function fetchWithCache(url, cacheInstance, tabId) {
try {
const cached = cacheInstance.get(url);
if (cached) {
return cached;
}
console.log('发起新请求:', url);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP错误! 状态码: ${response.status}`);
}
const data = await response.json();
if (data.code == 200 || data.code == 404) {
// 统一缓存数据结构
const cacheData = {
data: data,
timestamp: Date.now()
};
cacheInstance.set(url, cacheData);
return cacheData;
}
console.error('API返回异常:', data);
return null;
} catch (error) {
console.error(`请求失败: ${url}`, error);
return null;
}
resolve({
weight: cache.weight?.data || null,
ip: cache.ip?.data || null,
icp: cache.icp?.data || null,
isComplete: !!(cache.weight && cache.ip && cache.icp)
});
});
});
}
// 获取根域名的函数
function getRootDomain(domain) {
const commonTlds = [
'com.cn', 'org.cn', 'net.cn', 'gov.cn',
'co.jp', 'co.uk', 'co.kr', 'com.hk'
];
// 分割域名
const parts = domain.split('.');
if (parts.length <= 2) {
return domain; // 已经是根域名
}
// 检查是否包含特殊的顶级域名后缀
for (const tld of commonTlds) {
const tldParts = tld.split('.');
if (domain.endsWith('.' + tld)) {
return parts.slice(-(tldParts.length + 1)).join('.');
function saveAnalysisToStorage(tabId, weight, ip, icp) {
const key = `analysis_${tabId}`;
chrome.storage.session.set({
[key]: {
weight: weight ? { data: weight.data } : null,
ip: ip ? { data: ip.data } : null,
icp: icp ? { data: icp.data } : null
}
}
// 默认返回最后两部分
return parts.slice(-2).join('.');
});
}
// 修改网站解析相关函数中的域名处理
async function fetchDomainWeight(domain, tabId) {
try {
const apiUrl = `https://api.mir6.com/api/bdqz?domain=${encodeURIComponent(domain)}&type=json`;
const weightData = await fetchWithCache(
apiUrl,
caches.weight,
tabId
);
if (weightData) {
return weightData;
}
return null;
} catch (error) {
console.error('获取域名权重失败:', error);
return null;
}
}
async function fetchIpInfo(domain, tabId) {
try {
const ipData = await fetchWithCache(
`https://api.mir6.com/api/ip_json?ip=${domain}`,
caches.ip,
tabId
);
if (ipData) {
return ipData;
}
return null;
} catch (error) {
console.error('获取IP信息失败:', error);
return null;
}
}
// 添加检查缓存的函数
function getAnalysisFromCache(domain) {
const weightData = caches.weight.get(domain);
const ipData = caches.ip.get(domain);
const icpData = caches.icp.get(domain);
const now = Date.now();
// 检查数据是否存在且有效
const weightValid = weightData?.data && weightData.timestamp && (now - weightData.timestamp < caches.weight.expireTime);
const ipValid = ipData?.data && ipData.timestamp && (now - ipData.timestamp < caches.ip.expireTime);
const icpValid = icpData?.data && icpData.timestamp && (now - icpData.timestamp < caches.icp.expireTime);
function emptyCache() {
return {
weight: weightValid ? weightData.data.data : null,
ip: ipValid ? ipData.data.data : null,
icp: icpValid ? icpData.data.data : null,
isComplete: weightValid && ipValid && icpValid
weight: null,
ip: null,
icp: null,
isComplete: false
};
}
// 添加IP地址检查函数
function isPrivateIP(domain) {
// 检查是否是IP地址
const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/;
// 检查是否是内网IP
const ipv4Pattern = /^\d{1,3}(\.\d{1,3}){3}$/;
if (ipv4Pattern.test(domain)) {
const parts = domain.split('.');
const firstOctet = parseInt(parts[0]);
const first = parseInt(parts[0]), second = parseInt(parts[1]);
return (
firstOctet === 10 || // 10.0.0.0 - 10.255.255.255
(firstOctet === 172 && parseInt(parts[1]) >= 16 && parseInt(parts[1]) <= 31) || // 172.16.0.0 - 172.31.255.255
(firstOctet === 192 && parseInt(parts[1]) === 168) || // 192.168.0.0 - 192.168.255.255
domain === '127.0.0.1' // localhost
first === 10 ||
(first === 172 && second >= 16 && second <= 31) ||
(first === 192 && second === 168) ||
domain === '127.0.0.1'
);
}
return false;
}
// 添加备案信息查询函数
async function fetchIcpInfo(domain, tabId) {
// 如果是IP地址,直接返回不适用
const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/;
if (ipv4Pattern.test(domain)) {
return {
data: {
icp: 'IP地址不适用',
unit: 'IP地址不适用',
time: 'IP地址不适用'
}
};
function getRootDomain(domain) {
const specialTlds = ['com.cn', 'edu.cn', 'gov.cn', 'org.cn', 'net.cn', 'co.jp', 'co.uk', 'co.kr', 'com.hk'];
const parts = domain.split('.');
if (parts.length <= 2) return domain;
for (const tld of specialTlds) {
if (domain.endsWith(`.${tld}`)) {
return parts.slice(-(tld.split('.').length + 1)).join('.');
}
}
const rootDomain = getRootDomain(domain);
return parts.slice(-2).join('.');
}
async function fetchWithCache(url, tabId) {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
return { data };
}
async function fetchDomainWeight(domain, tabId) {
const apiUrl = `https://api.mir6.com/api/bdqz?myKey=84fbd322b048f19626e861932ec7d572&domain=${domain}&type=json`;
try {
const icpData = await fetchWithCache(
`https://cn.apihz.cn/api/wangzhan/icp.php?id=88888888&key=88888888&domain=${rootDomain}`,
caches.icp,
tabId
);
// 如果返回404,说明没有查询到备案信息
if (icpData && icpData.code === 404) {
return {
data: {
icp: '未查询到备案信息',
unit: '未知',
time: '未知'
}
};
}
if (icpData) {
return icpData;
}
return null;
} catch (error) {
console.error('获取备案信息失败:', error);
return await fetchWithCache(apiUrl, tabId);
} catch (e) {
console.error('域名权重查询失败:', e);
return null;
}
}
let analysisPending = false; // 在文件顶部添加模块级变量
async function fetchIpInfo(domain, tabId) {
const apiUrl = `https://api.mir6.com/api/ip_json?myKey=7f5860bc55587662c37cf678a7871ad0&ip=${domain}`;
try {
return await fetchWithCache(apiUrl, tabId);
} catch (e) {
console.error('IP 查询失败:', e);
return null;
}
}
async function fetchIcpInfo(domain, tabId) {
const ipv4Pattern = /^\d{1,3}(\.\d{1,3}){3}$/;
if (ipv4Pattern.test(domain)) {
return {
data: { icp: 'IP地址不适用', unit: 'IP地址不适用', time: 'IP地址不适用' }
};
}
const apiUrl = `https://cn.apihz.cn/api/wangzhan/icp.php?id=88888888&key=88888888&domain=${domain}`;
try {
const icp = await fetchWithCache(apiUrl, tabId);
if (icp?.data?.code === 404) {
return {
data: { icp: '未查询到备案信息', unit: '未知', time: '未知' }
};
}
return icp;
} catch (e) {
console.error('备案查询失败:', e);
return null;
}
}
let analysisPending = false;
+6 -29
View File
@@ -25,7 +25,7 @@ const SCANNER_CONFIG = {
// 图表相关
/^(echarts|chart|highcharts)[.-]?\d*\.?\d*\.?\d*(?:\.min)?\.js$/i,
// 工具库相关
/^(lodash|moment|tableexport|axios|plupload|pqgrid|md5)[.-]?\d*\.?\d*\.?\d*(?:\.full)?(?:\.min)?\.js$/i,
/^(lodash|moment|katex|tableexport|axios|plupload|pqgrid|md5)[.-]?\d*\.?\d*\.?\d*(?:\.full)?(?:\.min)?\.js$/i,
// 其他常用库
/^(polyfill|modernizr|device|less|isotope.pkgd|lhgdialog|kendo.web|dataTables|editor|seajs-style|seajs-text|tinymce|jsencrypt|backbone|select2|underscore|ext-all|ext-unigui-min|exporter|buttons|v5_float_4)[.-]?\d*\.?\d*\.?\d*(?:[\.-]dev)?(?:[\.-]html5|bootstrap|print|full)?(?:[\.-]min)?\.js$/i,
// 日期选择器
@@ -58,28 +58,7 @@ const SCANNER_CONFIG = {
'zrender/vml/vml'
]
},
// 域名相关配置
DOMAIN: {
// 域名白名单: 不会扫描以下域名
WHITELIST: [
'github.com',
'360.net',
'bing.com',
'csdn.net',
'bilibili.com',
'google.com',
'youtube.com',
'microsoft.com',
'apple.com',
'mozilla.org',
'aliyun.com',
'feishu.cn',
'mklab.cn',
'cnnvd.org.cn',
'qq.com',
// 'baidu.com'
],
// 域名黑名单:不会展示以下域名
BLACKLIST: [
'el.datepicker.today',
@@ -94,7 +73,7 @@ const SCANNER_CONFIG = {
]
},
// IP 相关配置
// IP 相关配置
IP: {
// 特殊 IP 范围(保留地址和特殊用途地址)
SPECIAL_RANGES: [
@@ -102,10 +81,8 @@ const SCANNER_CONFIG = {
/^255\.255\.255\.255$/ // 广播地址
]
},
// 正则表达式模式
PATTERNS: {
// 域名匹配 - HTML页面
// 域名匹配 - HTML页面
DOMAIN: /\b(?:(?!this)[a-z0-9%-]+\.)*?(?:(?!this)[a-z0-9%-]{2,}\.)(?:wang|club|xyz|vip|top|beer|work|ren|technology|fashion|luxe|yoga|red|love|online|ltd|chat|group|pub|run|city|live|kim|pet|space|site|tech|host|fun|store|pink|ski|design|ink|wiki|video|email|company|plus|center|cool|fund|gold|guru|life|team|today|world|zone|social|bio|black|blue|green|lotto|organic|poker|promo|vote|archi|voto|fit|cn|website|press|icu|art|law|shop|band|media|cab|cash|cafe|games|link|fan|net|cc|com|fans|cloud|info|pro|mobi|asia|studio|biz|vin|news|fyi|tax|tv|market|shopping|mba|sale|co|org)(?:\:\d{1,5})?(?![a-zA-Z0-9._=>\(\);!}-])\b/g,
// 域名匹配 - 资源文件
DOMAIN_RESOURCE: /["'](?:(?:[a-z0-9]+:)?\/\/)?(?:(?!this)[a-z0-9%-]+\.)*?(?:[a-z0-9%-]{2,}\.)(?:wang|club|xyz|vip|top|beer|work|ren|technology|fashion|luxe|yoga|red|love|online|ltd|chat|group|pub|run|city|live|kim|pet|space|site|tech|host|fun|store|pink|ski|design|ink|wiki|video|email|company|plus|center|cool|fund|gold|guru|life|team|today|world|zone|social|bio|black|blue|green|lotto|organic|poker|promo|vote|archi|voto|fit|cn|website|press|icu|art|law|shop|band|media|cab|cash|cafe|games|link|fan|net|cc|com|fans|cloud|info|pro|mobi|asia|studio|biz|vin|news|fyi|tax|tv|market|shopping|mba|sale|co|org)(?![a-zA-Z0-9.])(?:\:\d{1,5})?\S*?["']/g,
@@ -200,9 +177,9 @@ const SCANNER_CONFIG = {
'作弊',' '
])
},
// ID密钥相关配置
// ID密钥相关配置
ID_KEY: {
PATTERNS: [
{name: '微信开放平台密钥', pattern: /wx[a-z0-9]{15,18}/g},
@@ -251,4 +228,4 @@ const SCANNER_CONFIG = {
window.SCANNER_CONFIG = SCANNER_CONFIG;
window.API_CONFIG = SCANNER_CONFIG.API;
window.DOMAIN_CONFIG = SCANNER_CONFIG.DOMAIN;
window.IP_CONFIG = SCANNER_CONFIG.IP;
window.IP_CONFIG = SCANNER_CONFIG.IP;
+470 -491
View File
File diff suppressed because it is too large. Load diff
+44 -69
View File
@@ -5,68 +5,51 @@ const regexCache = {
chinesePattern: /^[\u4e00-\u9fa5]+$/,
camelCasePattern: /\b[_a-z]+(?:[A-Z][a-z]+)+\b/,
};
// 统一的扫描过滤器
const SCANNER_FILTER = {
// API 过滤器
api: (function() {
return function(match, resultsSet) {
return function(match, url, resultsSet) {
match = match.slice(1, -1);
//如果是css字体文件则丢弃
if (SCANNER_CONFIG.API.FONT_PATTERN.test(match)) {
return false;
}
// 检查是否是Vue文件
if (match.endsWith('.vue')) {
resultsSet?.vueFiles?.add(match);
resultsSet?.vueFiles?.set(match, url);
return true;
}
if (SCANNER_CONFIG.API.IMAGE_PATTERN.test(match)) {
resultsSet?.imageFiles?.add(match);
resultsSet?.imageFiles?.set(match, url);
return true;
}
// 检查是否是JS文件
if (SCANNER_CONFIG.API.JS_PATTERN.test(match)) {
resultsSet?.jsFiles?.add(match);
resultsSet?.jsFiles?.set(match, url);
return true;
}
// 检查是否是文档文件
if (SCANNER_CONFIG.API.DOC_PATTERN.test(match)) {
resultsSet?.docFiles?.add(match);
resultsSet?.docFiles?.set(match, url);
return true;
}
// 检查是否包含被过滤的内容类型
const lcMatch = match.toLowerCase();
const shouldFilter = SCANNER_CONFIG.API.FILTERED_CONTENT_TYPES.some(type =>
lcMatch==type.toLowerCase()
);
// 如果是被过滤的内容类型,直接跳过
if (shouldFilter) {
return false;
}
// 检查是否是模块路径(以./开头)
if (match.startsWith('./')) {
resultsSet?.moduleFiles?.add(match);
resultsSet?.moduleFiles?.set(match, url);
return true;
}
// 区分绝对路径和相对路径
if (match.startsWith('/')) {
// 绝对路径
if(match.length<=4&&/[A-Z\.\/\#\+\?23]/.test(match.slice(1))) return false;
resultsSet?.absoluteApis?.add(match);
resultsSet?.absoluteApis?.set(match, url);
} else {
// 相对路径
if (/^(audio|blots|core|ace|icon|css|formats|image|js|modules|text|themes|ui|video|static|attributors|application)/.test(match)) return false;
if(match.length<=4) return false;
resultsSet?.apis?.add(match);
resultsSet?.apis?.set(match, url);
}
return true;
};
@@ -76,7 +59,6 @@ const SCANNER_FILTER = {
domain: (function() {
// URL解码缓存
const decodeCache = new Map();
const validate = {
// 清理和标准化域名
clean(domain) {
@@ -120,7 +102,7 @@ const SCANNER_FILTER = {
}
};
return function(match, resultsSet) {
return function(match, url, resultsSet) {
// 清理和标准化域名
match = validate.clean(match);
if (!match) return false;
@@ -131,7 +113,7 @@ const SCANNER_FILTER = {
}
// 添加到结果集
resultsSet?.domains?.add(match);
resultsSet?.domains?.set(match, url);
return true;
};
})(),
@@ -144,70 +126,63 @@ const SCANNER_FILTER = {
}
};
return function(match, resultsSet) {
return function(match, url, resultsSet) {
// 提取纯IP地址(带端口)
match = match.replace(/^[`'"]|[`'"]$/g, '');
const ipMatch = match.match(SCANNER_CONFIG.PATTERNS.IP);
if (ipMatch) {
const extractedIp = ipMatch[0];
if (!validate.notSpecial(extractedIp)) return false;
resultsSet?.ips?.add(extractedIp);
resultsSet?.ips?.set(extractedIp, url);
}
return true;
};
})(),
// 其他敏感信息过滤器
phone: (match, resultsSet) => {
resultsSet?.phones?.add(match);
phone: (match, url, resultsSet) => {
resultsSet?.phones?.set(match, url);
return true;
},
email: (match, resultsSet) => {
resultsSet?.emails?.add(match);
email: (match, url, resultsSet) => {
resultsSet?.emails?.set(match, url);
return true;
},
idcard: (match, resultsSet) => {
resultsSet?.idcards?.add(match);
idcard: (match, url, resultsSet) => {
resultsSet?.idcards?.set(match, url);
return true;
},
url: (match, resultsSet) => {
url: (match, url, resultsSet) => {
try {
// 检查是否是GitHub URL
if (match.toLowerCase().includes('github.com/')) {
resultsSet?.githubUrls?.add(match);
if (match.toLowerCase().includes('github.com/')) {
resultsSet?.githubUrls?.set(match, url);
return true;
}
resultsSet?.urls?.add(match);
resultsSet?.urls?.set(match, url);
// 解析URL
const url = new URL(match);
const matchUrl = new URL(match);
const currentHost = window.location.host;
// 检查是否是当前域名或IP
if (url.host === currentHost) {
if (matchUrl.host === currentHost) {
// 获取路径部分
const path = url.pathname;
//如果是css字体文件则丢弃
const path = matchUrl.pathname;
if (SCANNER_CONFIG.API.FONT_PATTERN.test(path)) {
return false;
}
// 检查是否是图片文件
if (SCANNER_CONFIG.API.IMAGE_PATTERN.test(path)) {
resultsSet?.imageFiles?.add(path);
resultsSet?.imageFiles?.set(path, url);
return true;
}
// 检查是否是JS文件
if (SCANNER_CONFIG.API.JS_PATTERN.test(path)) {
resultsSet?.jsFiles?.add(path);
resultsSet?.jsFiles?.set(path, url);
return true;
}
// 检查是否是文档文件
if (SCANNER_CONFIG.API.DOC_PATTERN.test(path)) {
resultsSet?.docFiles?.add(path);
resultsSet?.docFiles?.set(path, url);
return true;
}
@@ -215,9 +190,9 @@ const SCANNER_FILTER = {
if (!path.match(/\.[a-zA-Z0-9]+$/)) {
// 区分绝对路径和相对路径
if (path.startsWith('/')) {
resultsSet?.absoluteApis?.add(path);
resultsSet?.absoluteApis?.set(path, url);
} else {
resultsSet?.apis?.add(path);
resultsSet?.apis?.set(path, url);
}
return true;
}
@@ -229,24 +204,24 @@ const SCANNER_FILTER = {
return true;
},
jwt: (match, resultsSet) => {
resultsSet?.jwts?.add(match);
jwt: (match, url, resultsSet) => {
resultsSet?.jwts?.set(match, url);
return true;
},
aws_key: (match, resultsSet) => {
resultsSet?.awsKeys?.add(match);
aws_key: (match, url, resultsSet) => {
resultsSet?.awsKeys?.set(match, url);
return true;
},
company: (match, resultsSet) => {
company: (match, url, resultsSet) => {
if(/[()]/.test(match)&&!match.match(/(\S*)/)) return false;
if (Array.from(SCANNER_CONFIG.BLACKLIST.CHINESE_BLACKLIST).some(blackWord=>match.includes(blackWord))) return false;
resultsSet?.companies?.add(match);
resultsSet?.companies?.set(match, url);
return true;
},
credentials: (match, resultsSet) => {
credentials: (match, url, resultsSet) => {
// 检查是否是空值
const valueMatch = match.replace(/\s+/g,'').split(/[:=]/);
var key = valueMatch[0].replace(/['"]/g,'').toLowerCase();
@@ -257,11 +232,11 @@ const SCANNER_FILTER = {
if (regexCache.coordPattern.test(key) || regexCache.valuePattern.test(value) || value.length<=1) return false;
if (regexCache.chinesePattern.test(value)) return false;
resultsSet?.credentials?.add(match);
resultsSet?.credentials?.set(match, url);
return true;
},
cookie: (match, resultsSet) => {
cookie: (match, url, resultsSet) => {
// 检查是否是空值
const valueMatch = match.replace(/\s+/g,'').split(/[:=]/);
if (valueMatch[1].replace(/['"]/g,'').length<4) {
@@ -281,11 +256,11 @@ const SCANNER_FILTER = {
return false;
}
}
resultsSet?.cookies?.add(match);
resultsSet?.cookies?.set(match, url);
return true;
},
id_key: (match, resultsSet) => {
id_key: (match, url, resultsSet) => {
// 先检查是否包含分隔符
const hasDelimiter = match.match(/[:=]/);
@@ -341,14 +316,14 @@ const SCANNER_FILTER = {
return false;
}
}
resultsSet?.idKeys?.add(match);
resultsSet?.idKeys?.set(match, url);
return true;
}
return false;
},
// 构建工具检测过滤器
finger: (fingerName, fingerClass, fingerType, fingerDescription, resultsSet, fingerExtType, fingerExtName) => {
finger: (fingerName, fingerClass, fingerType, fingerDescription, url, resultsSet, fingerExtType, fingerExtName) => {
var fingerprint = {};
fingerprint.type = fingerType;
fingerprint.name = fingerClass;
@@ -362,7 +337,7 @@ const SCANNER_FILTER = {
type: 'UPDATE_BUILDER',
finger: fingerprint
});
resultsSet?.fingers?.add(fingerClass);
resultsSet?.fingers?.set(fingerClass, url);
return true;
}
};
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 40 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 75 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 69 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 14 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 56 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 67 KiB

+4 -2
View File
@@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "雪瞳",
"version": "0.2.6.0",
"version": "0.2.7.2",
"description": "洞悉无形,守护无界",
"icons": {
@@ -16,7 +16,9 @@
"scripting",
"storage",
"webRequest",
"cookies"
"cookies",
"tabs",
"webNavigation"
],
"host_permissions": [
+104 -11
View File
@@ -1,14 +1,18 @@
/* 基础样式 */
body {
width: 400px;
height: 600px;
margin: 0;
padding: 0;
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
background: #fff;
color: #333;
display: flex;
flex-direction: column;
font-size: 14px;
line-height: 1.5;
background: #fff;
overflow: hidden;
}
/* 导航栏样式 */
.nav-bar {
display: flex;
@@ -25,6 +29,36 @@ body {
display: flex;
}
.nav-right {
display: flex;
align-items: center;
padding-right: 12px;
}
/* 进度显示样式 */
.progress-container {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #495057;
background: #f8f9fa;
padding: 4px 8px;
border-radius: 12px;
border: 1px solid #e9ecef;
}
.progress-text {
color: #6c757d;
}
.progress-percent {
font-weight: 600;
color: #228be6;
min-width: 28px;
text-align: right;
}
.nav-tab {
padding: 10px 20px;
border: none;
@@ -50,6 +84,16 @@ body {
background: #228be6;
}
.progress-tab {
padding: 10px 0px;
border: none;
background: none;
color: #495057;
font-size: 14px;
cursor: pointer;
position: relative;
}
/* 页面容器样式 */
.page {
height: calc(100vh - 40px);
@@ -57,6 +101,7 @@ body {
padding: 10px 10px 10px 12px;
width: 400px;
box-sizing: border-box;
flex: 1;
}
.page.active {
@@ -67,6 +112,7 @@ body {
.container {
height: 100%;
overflow: hidden;
overflow-y: auto;
width: 100%;
}
@@ -152,14 +198,6 @@ body {
margin: 12px 0 8px 0;
font-weight: 500;
}
.config-content {
font-size: 13px;
color: #495057;
line-height: 1.6;
margin-left: 8px;
}
/* 配置项样式 */
.config-item {
margin-bottom: 8px;
@@ -173,6 +211,7 @@ body {
.config-value,
.whitelist-input {
display: block;
width: 100%;
min-height: 80px;
height: 120px;
@@ -603,6 +642,8 @@ body {
color: #868e96;
font-size: 13px;
margin-bottom: 8px;
line-height: 1.6;
margin-left: 8px;
}
/* Web服务器指纹样式 */
@@ -870,7 +911,9 @@ body {
.info-item {
display: flex;
justify-content: space-between;
flex-direction: column;
margin: 8px 0;
padding: 8px;
background: #f8f9fa;
border-radius: 6px;
@@ -887,7 +930,15 @@ body {
color: #495057;
word-break: break-all;
}
/* 添加图片加载过渡效果 */
.weight-img {
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.weight-img.loaded {
opacity: 1;
}
/* 基本信息和Whois信息网格布局 */
.basic-info,
.whois-info {
@@ -942,4 +993,46 @@ body {
margin: 10px;
font-size: 14px;
}
/* 白名单输入框样式 */
#whitelistInput {
width: 100%;
min-height: 120px;
padding: 8px;
font-size: 13px;
font-family: monospace;
border: 1px solid #dee2e6;
border-radius: 4px;
background: #f8f9fa;
resize: vertical;
box-sizing: border-box;
}
#whitelistInput:focus {
background: #fff;
border-color: #228be6;
outline: none;
box-shadow: 0 0 0 2px rgba(34,139,230,0.1);
}
/* 配置按钮样式 */
.config-buttons {
margin-top: 8px;
display: flex;
gap: 8px;
}
.save-btn {
padding: 6px 12px;
background: #228be6;
color: white;
border: none;
border-radius: 4px;
font-size: 13px;
cursor: pointer;
transition: background 0.2s;
}
.save-btn:hover {
background: #1c7ed6;
}
+40 -198
View File
@@ -3,61 +3,6 @@
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="highlight.css">
<style>
body {
width: 400px;
height: 600px;
margin: 0;
padding: 0;
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
background: #fff;
color: #333;
display: flex;
flex-direction: column;
}
/* 页面容器样式 */
.page {
display: none;
flex: 1;
overflow-y: auto;
}
.page.active {
display: block;
}
/* 内容容器样式 */
.container {
height: 100%;
overflow-y: auto;
}
/* 用户名密码结果的颜色标识 */
.credentials-list .item::before {
background: #e64980; /* 粉红色 - 用户名密码 */
}
/* 统一信息项样式 */
.info-item {
display: flex;
justify-content: space-between;
margin: 8px 0;
padding: 8px;
background: #f8f9fa;
border-radius: 4px;
}
/* 添加图片加载过渡效果 */
.weight-img {
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.weight-img.loaded {
opacity: 1;
}
</style>
</head>
<body>
<!-- 导航栏 -->
@@ -67,6 +12,7 @@
<button class="nav-tab" data-page="fingerprint">指纹嗅探</button>
<button class="nav-tab" data-page="analysis">网站解析</button>
<button class="nav-tab" data-page="config">配置</button>
<button class="progress-tab">0%</button>
</div>
</div>
@@ -84,6 +30,41 @@
</div>
</div>
<!-- 添加网站解析页面 -->
<div class="page analysis-page">
<div class="container">
<div class="analysis-section">
<!-- 基本信息 -->
<div class="analysis-group basic-group">
<h3>基本信息</h3>
<div class="basic-info">
</div>
</div>
<!-- 域名权重信息 -->
<div class="analysis-group weight-group">
<h3>搜索引擎权重</h3>
<div class="weight-grid">
</div>
</div>
<!-- IP地址信息 -->
<div class="analysis-group ip-group">
<h3>IP信息</h3>
<div class="ip-info">
</div>
</div>
<!-- Whois信息 -->
<div class="analysis-group whois-group">
<h3>Whois信息</h3>
<div class="whois-info">
</div>
</div>
</div>
</div>
</div>
<!-- 配置页面 -->
<div class="page config-page">
<div class="container">
@@ -91,10 +72,11 @@
<div class="config-group">
<h3>扫描白名单</h3>
<div class="config-content">
以下域名将不会被扫描:
以下域名网站(包括子域名)不会被扫描:
</div>
<div class="whitelist-domains">
<!-- 白名单域名将在这里动态显示 -->
<textarea id="whitelistInput" placeholder="输入域名,每行一个,如:&#10;example.com&#10;test.org&#10;localhost" rows="6"></textarea>
<div class="config-buttons">
<button id="saveWhitelist" class="save-btn">保存白名单</button>
</div>
</div>
@@ -123,146 +105,6 @@
</div>
</div>
</div>
<!-- 添加网站解析页面 -->
<div class="page analysis-page">
<div class="container">
<div class="analysis-section">
<!-- 基本信息 -->
<div class="analysis-group basic-group">
<h3>基本信息</h3>
<div class="basic-info">
<div class="info-item">
<span class="info-label">域名</span>
<span class="info-value domain-value"></span>
</div>
<div class="info-item">
<span class="info-label">备案号</span>
<span class="info-value icp-value"></span>
</div>
<div class="info-item">
<span class="info-label">注册商</span>
<span class="info-value">腾讯云</span>
</div>
<div class="info-item">
<span class="info-label">到期时间</span>
<span class="info-value">2024-03-17</span>
</div>
</div>
</div>
<!-- 域名权重信息 -->
<div class="analysis-group weight-group">
<h3>搜索引擎权重</h3>
<div class="weight-grid">
<div class="weight-item">
<img class="weight-img"
data-engine="baidupc"
data-value="4"
alt="百度PC">
<span class="weight-label">百度PC</span>
<span class="weight-value">4</span>
</div>
<div class="weight-item">
<img class="weight-img"
data-engine="google"
data-value="8"
alt="Google">
<span class="weight-label">Google</span>
<span class="weight-value">8</span>
</div>
<div class="weight-item">
<img class="weight-img"
data-engine="360"
data-value="6"
alt="360">
<span class="weight-label">360</span>
<span class="weight-value">6</span>
</div>
<div class="weight-item">
<img class="weight-img"
data-engine="baidum"
data-value="4"
alt="百度移动">
<span class="weight-label">百度移动</span>
<span class="weight-value">4</span>
</div>
<div class="weight-item">
<img class="weight-img"
data-engine="sougou"
data-value="2"
alt="搜狗">
<span class="weight-label">搜狗</span>
<span class="weight-value">2</span>
</div>
<div class="weight-item">
<img class="weight-img"
data-engine="shenma"
data-value="0"
alt="神马">
<span class="weight-label">神马</span>
<span class="weight-value">0</span>
</div>
</div>
</div>
<!-- IP地址信息 -->
<div class="analysis-group ip-group">
<h3>IP信息</h3>
<div class="ip-info">
<div class="info-item">
<span class="info-label">IPv6</span>
<span class="info-value">240e:97c:2f:2::4c</span>
</div>
<div class="info-item">
<span class="info-label">IPv4</span>
<span class="info-value">39.155.167.178</span>
</div>
<div class="info-item">
<span class="info-label">地理位置</span>
<span class="info-value">中国 广东省 广州市</span>
</div>
<div class="info-item">
<span class="info-label">运营商</span>
<span class="info-value">中国电信</span>
</div>
<div class="info-item">
<span class="info-label">协议</span>
<span class="info-value">IPv6</span>
</div>
<div class="info-item">
<span class="info-label">网络类型</span>
<span class="info-value">数据中心</span>
</div>
</div>
</div>
<!-- Whois信息 -->
<div class="analysis-group whois-group">
<h3>Whois信息</h3>
<div class="whois-info">
<div class="info-item">
<span class="info-label">注册时间</span>
<span class="info-value">1995-05-04</span>
</div>
<div class="info-item">
<span class="info-label">更新时间</span>
<span class="info-value">2023-03-17</span>
</div>
<div class="info-item">
<span class="info-label">DNS服务器</span>
<span class="info-value">ns1.qq.com</span>
</div>
<div class="info-item">
<span class="info-label">状态</span>
<span class="info-value">clientDeleteProhibited</span>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="popup.js"></script>
</body>
</html>
+137 -141
View File
@@ -6,8 +6,6 @@ const searchEngines = [
{ id: 'sougou', name: '搜狗' },
{ id: 'shenma', name: '神马' }
];
let currentActiveTabId = null;
const imageCache = new WeakMap();
// 页面切换功能
@@ -43,25 +41,59 @@ function initializePage(pageName) {
initFingerprintPage();
break;
case 'analysis':
currentPageCleanup = initAnalysisPage();
initAnalysisPage();
break;
}
}
// 显示配置信息
function displayConfig(config) {
const whitelistDomains = document.querySelector('.whitelist-domains');
if (!config) {
whitelistDomains.innerHTML = '<div class="error">无法获取配置信息</div>';
return;
}
// 保存白名单
function saveWhitelist() {
const whitelistInput = document.getElementById('whitelistInput');
if (!whitelistInput) return;
// 获取输入的域名,过滤空行
const domains = whitelistInput.value
.split('\n')
.map(domain => domain.trim())
.filter(domain => domain && domain.length > 0);
// 保存到存储
chrome.storage.local.set({ customWhitelist: domains }, () => {
showSaveTooltip('保存成功');
});
}
// 显示白名单域名
whitelistDomains.innerHTML = config.WHITELIST ? config.WHITELIST.map(domain => `
<div class="domain-item">
<span class="domain-text">${domain}</span>
</div>
`).join('') : '';
// 显示保存提示
function showSaveTooltip(text) {
const saveBtn = document.getElementById('saveWhitelist');
if (!saveBtn) return;
const rect = saveBtn.getBoundingClientRect();
const tooltip = document.createElement('div');
tooltip.className = 'copy-tooltip';
tooltip.textContent = text;
tooltip.style.left = `${rect.left + rect.width / 2}px`;
tooltip.style.top = `${rect.top - 30}px`;
document.body.appendChild(tooltip);
setTimeout(() => tooltip.remove(), 1500);
}
// 检查当前域名是否在白名单中
function checkIfWhitelisted(hostname, callback) {
chrome.storage.local.get(['customWhitelist'], (result) => {
if (!result.customWhitelist || result.customWhitelist.length === 0) {
callback(false);
return;
}
const customWhitelist = result.customWhitelist.map(domain => domain.toLowerCase());
const isWhitelisted = customWhitelist.some(domain =>
hostname === domain || hostname.endsWith(`.${domain}`)
);
callback(isWhitelisted);
});
}
// 显示扫描结果的函数
@@ -86,7 +118,7 @@ function displayResults(results) {
{ id: 'github-list', data: results.githubUrls, title: 'GitHub链接' },
{ id: 'vue-list', data: results.vueFiles, title: 'Vue文件' },
{ id: 'js-list', data: results.jsFiles, title: 'JS文件' },
{ id: 'url-list', data: results.urls, title: 'URL' },
{ id: 'url-list', data: results.urls, title: 'URL' }
];
// 获取容器
@@ -120,8 +152,8 @@ function displayResults(results) {
<div class="section-content">
<div class="content-wrapper ${id}">
${Array.from(data).map(item => `
<div class="item">
${item}
<div class="item" title="来源: ${item[1]}" data-source="${item[1]}">
${item[0]}
</div>
`).join('')}
</div>
@@ -148,8 +180,8 @@ function displayResults(results) {
copyToClipboard(text, e.clientX, e.clientY);
});
});
// 添加复制URL事件监听
// 添加复制URL事件监听
container.querySelectorAll('.copy-url-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const section = btn.closest('.section');
@@ -186,16 +218,31 @@ function displayResults(results) {
});
});
// 添加右键复制事件监听
// 添加悬浮提示和点击复制来源事件监听
container.querySelectorAll('.item').forEach(item => {
item.addEventListener('click', (e) => {
const source = item.dataset.source;
if (source) {
copyToClipboard(source, e.clientX, e.clientY);
}
});
item.addEventListener('contextmenu', (e) => {
e.preventDefault();
copyToClipboard(item.textContent.trim(), e.clientX, e.clientY);
});
});
}
// 处理长文本
handleLongText();
// 复制文本到剪贴板
async function copyToClipboard(text, x, y) {
try {
await navigator.clipboard.writeText(text);
showCopyTooltip('复制成功', x, y);
} catch (err) {
showCopyTooltip('复制失败', x, y);
}
}
// 显示复制成功提示
@@ -210,16 +257,6 @@ function showCopyTooltip(text, x, y) {
setTimeout(() => tooltip.remove(), 1500);
}
// 复制文本到剪贴板
async function copyToClipboard(text, x, y) {
try {
await navigator.clipboard.writeText(text);
showCopyTooltip('复制成功', x, y);
} catch (err) {
showCopyTooltip('复制失败', x, y);
}
}
// 页面加载完成时的初始化
document.addEventListener('DOMContentLoaded', () => {
const activePage = document.querySelector('.nav-tab.active').dataset.page;
@@ -231,14 +268,24 @@ document.addEventListener('DOMContentLoaded', () => {
getCurrentTab().then(tab => {
if (tab) {
chrome.tabs.sendMessage(tab.id, {type: 'GET_RESULTS', tabId: tab.id}, response => {
if (chrome.runtime.lastError) {
container.innerHTML = '<div class="error">无法连接到页面,尝试刷新页面后再试吧</div>';
} else if (response === 'WHITELISTED') {
const hostname = new URL(tab.url).hostname.toLowerCase();
// 先检查自定义白名单
checkIfWhitelisted(hostname, (isWhitelisted) => {
if (isWhitelisted) {
container.innerHTML = '<div class="whitelisted">当前域名在白名单中,已跳过扫描</div>';
} else if (response) {
displayResults(response);
updateProgress(100);
return;
}
// 如果不在自定义白名单中,再发送消息获取结果
chrome.tabs.sendMessage(tab.id, {type: 'GET_RESULTS', tabId: tab.id, from: 'popup'}, response => {
if (chrome.runtime.lastError) {
container.innerHTML = '<div class="error">无法连接到页面,尝试刷新页面后再试吧</div>';
}else{
displayResults(response);
updateProgress(response.progress[0][1]);
}
});
});
}
});
@@ -247,53 +294,42 @@ document.addEventListener('DOMContentLoaded', () => {
initEventListeners();
});
// 更新进度显示
function updateProgress(percent) {
const progressTab = document.querySelector('.progress-tab');
if (progressTab) {
progressTab.textContent = `${percent}%`;
}
}
// 监听来自 content script 的消息
chrome.runtime.onMessage.addListener((message) => {
if (message.type === 'SCAN_UPDATE' && message.results && message.tabId===currentActiveTabId) {
displayResults(message.results);
if (message.type === 'SCAN_UPDATE') {
if (message.results) {
displayResults(message.results);
}
updateProgress(message.results.progress[0][1]);
}
});
// 监听标签页切换事件
chrome.tabs.onActivated.addListener((activeInfo) => {
// 更新当前活动标签页ID
currentActiveTabId = activeInfo.tabId;
});
// 处理长文本的显示
function handleLongText() {
const items = document.querySelectorAll('.item');
items.forEach(item => {
// 检查内容是否被截断
if (item.offsetWidth < item.scrollWidth) {
item.classList.add('truncated');
}
});
}
// 初始化配置页面
function initConfigPage() {
getCurrentTab().then(tab => {
if (tab) {
// 获取配置信息
chrome.tabs.sendMessage(tab.id, {type: 'GET_CONFIG'}, response => {
if (chrome.runtime.lastError || !response || !response.config) {
const container = document.querySelector('.config-page .container');
container.innerHTML = '<div class="error">请刷新页面后重试</div>';
return;
}
displayConfig(response.config);
// 获取动态扫描设置
chrome.storage.local.get(['dynamicScan', 'deepScan'], (result) => {
const dynamicScanCheckbox = document.getElementById('dynamicScan');
const deepScanCheckbox = document.getElementById('deepScan');
if (dynamicScanCheckbox) {
dynamicScanCheckbox.checked = result.dynamicScan === true;
}
if (deepScanCheckbox) {
deepScanCheckbox.checked = result.deepScan === true;
}
});
});
// 获取扫描设置和自定义白名单
chrome.storage.local.get(['dynamicScan', 'deepScan', 'customWhitelist'], (result) => {
const dynamicScanCheckbox = document.getElementById('dynamicScan');
const deepScanCheckbox = document.getElementById('deepScan');
const whitelistInput = document.getElementById('whitelistInput');
if (dynamicScanCheckbox) {
dynamicScanCheckbox.checked = result.dynamicScan === true;
}
if (deepScanCheckbox) {
deepScanCheckbox.checked = result.deepScan === true;
}
// 显示自定义白名单
if (whitelistInput && result.customWhitelist) {
whitelistInput.value = result.customWhitelist.join('\n');
}
});
}
@@ -324,17 +360,15 @@ function updateServerFingerprints(fingerprints) {
// 遍历所有指纹类型
for (const [type, fingerprintData] of Object.entries(fingerprints)) {
// 如果是数组,遍历数组中的每个指纹
if (fingerprintData.length > 0) {
for (const fingerprint of fingerprintData) {
addFingerprint(fingerprintSection, {
type: type,
name: fingerprint.name,
description: fingerprint.description,
value: fingerprint.version || fingerprint.name
});
}
}
if(fingerprintData.length === 0 || type === 'nameMap') continue;
for (const fingerprint of fingerprintData) {
addFingerprint(fingerprintSection, {
type: type,
name: fingerprint.name,
description: fingerprint.description,
value: fingerprint.version || fingerprint.name
});
}
}
}
@@ -362,7 +396,9 @@ function initFingerprintPage() {
console.log('Requesting fingerprints for tab:', tab.id);
chrome.runtime.sendMessage({
type: 'GET_FINGERPRINTS',
tabId: tab.id
tabId: tab.id,
from: 'popup',
to: 'background'
}, response => {
console.log('Received response:', response);
if (response) {
@@ -372,25 +408,7 @@ function initFingerprintPage() {
}
});
}
// 添加消息监听器处理 popup 的请求
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === 'GET_FINGERPRINTS') {
const fingerprints = serverFingerprints.get(request.tabId);
if (fingerprints) {
sendResponse({
server: fingerprints.server,
headers: Object.fromEntries(fingerprints.headers),
technology: fingerprints.technology
});
} else {
sendResponse(null);
}
return true;
}
// ... 其他代码
});
// 修改初始化分析页面的逻辑
// 初始化网站解析页面
function initAnalysisPage() {
const container = document.querySelector('.analysis-section');
container.innerHTML = '<div class="loading">正在获取网站信息...</div>';
@@ -400,38 +418,30 @@ function initAnalysisPage() {
getCurrentTab().then(tab => {
if (tab) {
const domain = new URL(tab.url).hostname;
let isFetching = false;
if (isFetching) return;
isFetching = true;
timeoutId = setTimeout(() => {
isFetching = false;
container.innerHTML = '<div class="error">请求超时,请重试</div>';
}, 10000);
chrome.runtime.sendMessage({
type: 'GET_SITE_ANALYSIS',
domain: domain
domain: domain,
tabId: tab.id,
from: 'popup',
to: 'background'
}, (response) => {
clearTimeout(timeoutId);
isFetching = false;
if (!response) {
container.innerHTML = '<div class="error">获取网站信息失败</div>';
return;
}
if (response.isPrivateIP) {
container.innerHTML = '<div class="notice">内网地址无需解析</div>';
return;
}
updateAnalysisPage(response, domain);
});
}
});
// 添加页面切换时的清理逻辑
return () => {
if (timeoutId) clearTimeout(timeoutId);
};
@@ -440,7 +450,6 @@ function initAnalysisPage() {
// 更新网站解析页面内容
function updateAnalysisPage(data, domain) {
const container = document.querySelector('.analysis-section');
// 解构数据,确保正确访问
const icpData = data.icp?.data;
container.innerHTML = `
<!-- 基本信息 -->
@@ -514,7 +523,7 @@ function updateWeightInfo(weightData) {
element.className = 'weight-item';
// 直接使用原始值
const rawValue = weightData.data?.[engine.id] || 'n';
const rawValue = weightData[engine.id] || 'n';
const displayValue = rawValue;
const imgValue = rawValue;
@@ -558,7 +567,7 @@ function updateWeightInfo(weightData) {
// 更新IP信息
function updateIpInfo(ipData) {
const ipInfo = document.querySelector('.ip-info');
const data = ipData.data;
const data = ipData;
ipInfo.innerHTML = `
<div class="info-item">
<span class="info-label">IPv4/6</span>
@@ -592,7 +601,8 @@ const eventListeners = {
'click .nav-tab': handleNavClick,
'change #dynamicScan': handleDynamicScan,
'change #deepScan': handleDeepScan,
'error .weight-img': handleImageError
'error .weight-img': handleImageError,
'click #saveWhitelist': saveWhitelist
};
function initEventListeners() {
@@ -609,20 +619,6 @@ function initEventListeners() {
});
}
// 在图片卸载时自动清理
function trackImageElement(img) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 触发图片加载
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
observer.observe(img);
}
// 统一使用handleNavClick处理页面切换
function handleNavClick(e) {
const tab = e.target.closest('.nav-tab');