为了检测360浏览器,我可是煞费苦心……

众所周知,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...

如果你对此感兴趣或者有什么内容要补充,欢迎关注此项目~

相关推荐
Ciito2 分钟前
vue项目使用eslint+prettier管理项目格式化
前端·javascript·vue.js
成都被卷死的程序员35 分钟前
响应式网页设计--html
前端·html
fighting ~38 分钟前
react17安装html-react-parser运行报错记录
javascript·react.js·html
老码沉思录44 分钟前
React Native 全栈开发实战班 - 列表与滚动视图
javascript·react native·react.js
abments1 小时前
JavaScript逆向爬虫教程-------基础篇之常用的编码与加密介绍(python和js实现)
javascript·爬虫·python
mon_star°1 小时前
将答题成绩排行榜数据通过前端生成excel的方式实现导出下载功能
前端·excel
Zrf21913184551 小时前
前端笔试中oj算法题的解法模版
前端·readline·oj算法
老码沉思录1 小时前
React Native 全栈开发实战班 - 状态管理入门(Context API)
javascript·react native·react.js
文军的烹饪实验室2 小时前
ValueError: Circular reference detected
开发语言·前端·javascript
Martin -Tang3 小时前
vite和webpack的区别
前端·webpack·node.js·vite