v0.2.7.2
No files matched your search
@@ -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: 左键复制来源,右键复制结果
|
||||
|
||||
---
|
||||
|
||||
**雪瞳——洞悉无形,守护无界。**
|
||||
**雪瞳——洞悉无形,守护无界。**
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 67 KiB |
@@ -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": [
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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="输入域名,每行一个,如: example.com test.org 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>
|
||||
@@ -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');
|
||||
|
||||