众所周知,360浏览器一向是特立独行的存在,为了防止用户识别它,隐藏了自己的用户代理(User-Agent)。只有在自己域名下访问,用户代理(User-Agent)才暴露出自己的特征。显然,这样对于开发者想要识别它,造成了不少麻烦。
非360官方网站访问
360官方网站访问
为了识别出360,只能通过Javascript检测360的特殊属性,找和其他浏览器的区别。最常见的方式就是找navigator.mimeTypes
或者navigator.plugins
有哪些不一样的值。为此,我在各个版本的360浏览器(360安全浏览器、360极速浏览器)也找到了各种可以利用的特征。
比如,无论 360安全浏览器 还是 360极速浏览器 的navigator.mimeTypes
都可能存在 application/360softmgrplugin
, application/mozilla-npqihooquicklogin
, application/npjlgplayer3-chrome-jlp
,application/vnd.chromium.remoting-viewer
这几种类型,我们可以通过判断这几个值是否存在识别 360浏览器。不仅如此,在早期的360浏览器中,明明是chrome内核,还保留着IE时代有了showModalDialog
方法,这些都可以用来做识别的依据。
js
import getMime from '../method/getMime.js';
import _globalThis from '../runtime/globalThis.js';
export default {
name: '360',
match(ua) {
let isMatch = false;
if (_globalThis?.chrome) {
let chrome_version = ua.replace(/^.*Chrome\/([\d]+).*$/, '$1');
if (getMime("type", "application/360softmgrplugin") || getMime("type", "application/mozilla-npqihooquicklogin") || getMime("type", "application/npjlgplayer3-chrome-jlp")) {
isMatch = true;
} else if (chrome_version > 36 && _globalThis?.showModalDialog) {
isMatch = true;
} else if (chrome_version > 45) {
isMatch = getMime("type", "application/vnd.chromium.remoting-viewer");
if (!isMatch && chrome_version >= 69) {
isMatch = getMime("type", "application/asx");
}
}
}
return ua.includes('QihooBrowser')
||ua.includes('QHBrowser')
||ua.includes(' 360 ')
||isMatch;
},
version(ua) {
return ua.match(/QihooBrowser(HD)?\/([\d.]+)/)?.[1]
||ua.match(/Browser \(v([\d.]+)/)?.[1]
||'';
}
};
然而,360并不是只有1种浏览器,还包含了 360安全浏览器, 360极速浏览器,360 AI浏览器等。我们又怎么区分呢,这时候还要寻找它们之间的差别。360AI浏览器较为简单,用户代理(User-Agent)中直接暴露相关信息。
我发现有一个值是360安全浏览器独有的,那就是application/gameplugin
,应该是浏览器内置的游戏插件。可是在后续的版本中也消失了,我又发现navigator.userAgentData.brands
里的值也有细微区别。于是就可以如下处理:
js
import getMime from '../method/getMime.js';
import _Chrome from './Chrome.js';
import _360 from './360.js';
import _globalThis from '../runtime/globalThis.js';
export default {
name:'360SE',
match(ua,isAsync=false){
let isMatch = false;
if(_360.match(ua)){
if(getMime("type", "application/gameplugin")){
isMatch = true;
}else if(_globalThis?.navigator?.userAgentData?.brands.filter(item=>item.brand=='Not.A/Brand').length){
isMatch = true;
}
}
return ua.includes('360SE')||isMatch;
},
version(ua){
let hash = {
'122':'15.3',
'114':'15.0',
'108':'14.0',
'86':'13.0',
'78':'12.0',
'69':'11.0',
'63':'10.0',
'55':'9.1',
'45':'8.1',
'42':'8.0',
'31':'7.0',
'21':'6.3'
};
let chrome_version = parseInt(_Chrome.version(ua));
return hash[chrome_version]||'';
}
};
而 360极速浏览器的识别依据就相对较多了!各种身份验证的插件都能在里面找到。
js
import getMime from '../method/getMime.js';
import _Chrome from './Chrome.js';
import _360 from './360.js';
import _globalThis from '../runtime/globalThis.js';
export default {
name:'360EE',
match(ua){
let isMatch = false;
if(getMime('type','application/cenroll.cenroll.version.1')||getMime('type','application/hwepass2001.installepass2001')){
isMatch = true;
}else if(_360.match(ua)){
if(_globalThis?.navigator?.userAgentData?.brands.find(item=>item.brand=='Not A(Brand'||item.brand=='Not?A_Brand')){
isMatch = true;
}
}
return ua.includes('360EE')||isMatch;
},
version(ua){
let hash = {
'122':'22.3', // 360极速X
'119':'22.0', // 360极速X
'108':'14.0', // 360极速
'95':'21.0', // 360极速X
'86':'13.0',
'78':'12.0',
'69':'11.0',
'63':'9.5',
'55':'9.0',
'50':'8.7',
'30':'7.5'
};
let chrome_version = parseInt(_Chrome.version(ua));
return ua.match(/Browser \(v([\d.]+)/)?.[1]
||hash[chrome_version]
||'';
}
};
可惜的是在Mac系统中的情况复杂点,这些插件的方法都不存在,这下又失去了判断的依据了。还有,在一次无意打开网络连接一次的时候,发现了360浏览器在请求一个奇怪的地址,居然返回了浏览器版本信息。
js
import _globalThis from '../runtime/globalThis.js';
const GetDeviceInfo = () => {
return new Promise((resolve) => {
const randomCv = `cv_${new Date().getTime() % 100000}${Math.floor(Math.random()) * 100}`
const params = { key: 'GetDeviceInfo', data: {}, callback: randomCv }
const Data = JSON.stringify(params)
if(_globalThis?.webkit?.messageHandlers){
_globalThis.webkit.messageHandlers['excuteCmd'].postMessage(Data)
_globalThis[randomCv] = function (response) {
delete _globalThis[randomCv];
resolve(JSON.parse(response||'{}'));
}
}else{
return resolve({});
}
})
};
export default {
name: '360EE',
match(ua) {
return GetDeviceInfo().then(function(response){
return response?.pid=='360csexm'||false;
});
},
version(ua) {
return GetDeviceInfo().then(function(response){
return response?.module_version||'';
});
}
};
原本觉得一切应该就这么顺利了,然后当我从Windows10迁移到windows11的时候,发现原来的浏览器中的插件特征识别已经失效了,我找不到360安全浏览器的识别特征。
于是我疯了......我开始一个个属性对比差异,就是找不到有什么特征是可以区分开的。就在我一筹莫展的时候,我无意间发现,我自己的网站在360安全浏览器中,莫默其妙多了一个奇怪的节点,看样子是一个AI组件。我敢确定,这个节点并不是我写的,于是我断言是360做了什么特殊处理。
经过分析,我发现这是360安全浏览器内置的一个"扩展程序 - 360智脑" ,是默认安装的而且无法卸载。我脑子一下子亮起来了,心想着我可以根据这个节点判断啊,只要检测它是否加载就能判断出来。于是,我写了以下代码:
js
// 根据检测文档中是否被插入"360智脑"组件,判断是否为360安全浏览器
if(!document?.querySelector('#ai-assist-root')){
return new Promise(function(resolve){
let hander = setTimeout(function(){
resolve(false);
},1500);
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(function($item){
if($item.id=='ai-assist-root'){
hander&&clearTimeout(hander);
resolve(true);
}
});
}
});
});
observer.observe(document,{childList: true, subtree: true});
});
}
但确实也有不足之处,我只能判断出什么时候加载了节点,但是无法对非360浏览器不加载进行判断。只能通过定时器去做超时处理,这就意味着非360浏览器判断需要一定耗时,这样太不友好了。
就在这时候,我发现这个"扩展程序"是需要加载资源的,而这个资源在浏览器本地。也就意味着,我可以直接直接对资源进行判断,这样并不需要等插件加载超时判断,非360安全浏览器可以较快地判断出"非他"的条件。
js
// 根据判断扩展程序CSS是否加载成功判断是否为360安全浏览器
return new Promise(function(resolve){
fetch('chrome-extension://fjbbmgamncjadhlpmffehlmmkdnkiadk/css/content.css').then(function(){
resolve(true);
}).catch(function(){
resolve(false);
});
});
于是我终于可以判断出这烦人的"小妖精"了!而这也是浏览器嗅探的其中一项工作,为此我还做了诸如:操作系统、屏幕、处理器架构、GPU、CPU、IP地址、时区、语言、网络等浏览器信息的判断和识别。
浏览器在线检测: passer-by.com/browser/
开源项目仓库地址:github.com/mumuy/brows...
如果你对此感兴趣或者有什么内容要补充,欢迎关注此项目~