OrzLee

这个世界上只有一个问题
那就是时间问题

微信-域名被封监测以及自动更换被封域名

wechat_logo.png

前言

微信已经成为大多数中国人的一种依赖,发展好了自然管理就更加严格,对于不符合微信营销规范的的网站统统不允许访问,就连对手的网址也无法在微信浏览器中打开。

最近公司有个需求,在各个平台大量投放广告,微信内域名频繁被封,需要域名监测和替换的内部系统功能。微信开发做过不少,但是这种明显是找漏子钻。

想不到就上网找吧!把Google翻个底朝天,全TMD是广告,各种出租微信域名防封检测API,真的一条有用的信息都没有。这下真得靠自己了。

那些出售API的到底是用的什么方法?

我琢磨了一会,想不到还能又什么方法能检测域名是否被微信封掉,只有在微信APP或桌面微信内置浏览器才行啊。抓包看看请求域名前有没有什么检测API,也没有发现什么有价值的信息。后来想想也不可能写个程序,人家请求API检测域名就用程序在桌面微信打开链接判断是否被封吧。真的有点悬,这难道有内部接口?

我是如何找到方法的?

在年中的时候我发现一个微信很坑爹的问题,那时候在高铁上改代码历历在目。微信短链接把orzlee坑惨了,在公司内部系统做了个短链接功能,每次手动替换被封域名都会自动生成微信短链接,这下倒好,生成的短链接不到一小时就封,替换了好几个域名,最后用长连接明显存活时间更长。

最坑爹的还不是这个,微信短链接跳转域名被封,短链接在任何浏览器打开都会如下提示

wechat_short_url.png

功能编写思路

Nice,有办法了,我把需要检测的域名都生成一个微信短链接,然后隔段时间访问短链接判断是否重定向正确的域名或者判断重定向域名是否weixin110.qq.com

整套功能编写思路:

  1. 域名都在阿里云,充分利用域名分组功能,总共分三组域名池-正常使用域名-弃用域名整套域名管理使用API处理

    • 域名池 域名池域名无任何指向,完全未使用的正常域名(没有被微信封,有指向也可以,那就再替换域名逻辑要做好处理了)。
    • 正常使用域名 目前正常使用的域名
    • 弃用域名 已经被微信封或者域名备案丢失
  2. 每隔一段时间检测域名是否正常,这个有两种情况:

    • 域名正常 皆大欢喜(^_^)
    • 域名被封(备案丢失) 替换被封域名

说说被封的处理思路,因为公司内部系统已经自动化站点配置,现在加入阿里云API简直如虎添翼。域名替换使用固定短链接,要求使原域名链接依然可以正常访问。这点很好做,302重定向即可。域名使用记录表中记录新域名,旧域名被访问(使用以一个域名专门用作跳转域名,不做其他使用,携带参数能找到数据库中的旧域名。这个域名最好自己注册备案一个),重定向至新域名,固定链接不会变动,使用缓存减轻服务器压力。

  • 移动被封域名至弃用域名分组
  • 域名池取出域名移动至正常使用域名 分组
  • 添加域名解析
  • 修改nginx配置新域名
  • 记录已经替换的新域名(同一条数据多一个new_domain字段,一旦换新域名就替换。也可以直接替换,只是之前配置的旧域名就找不到了,看业务需求吧)

这样整个思路就很清晰了。

核心功能

有了思路就简单多了,准备开搞。 我把监测功能写成laravel command,方便自己使用,任务调度也没问题。 在测试监测代码时发现一个问题,访问频率太快容易403。尝试替换User Agent,client-ip等头部信息没用。最后只有一个办法了,HTTP代理。网上虽然有很多免费的,但是这东西不稳定那就是自己刨坑,公司有几台服务器,全部搭建HTTP代理,都是以前购买的阿里云服务器,就为了备案而买的

  1. 搭建HTTP代理

    1. 安装tinyproxy

       sudo apt-get update
       sudo apt-get install tinyproxy
    2. 编辑配置文件: Port 8888 代理端口可自己修改 Allow 127.0.0.1 填写需要使用代理的服务器IP,只有该IP的服务器才能访问

       nano /etc/tinyproxy.conf 

      Ctrl+o 保存 Enter 确认 Ctrl+x 退出编辑器

    3. 启动重启停止命令

       service tinyproxy start
       service tinyproxy restart
       service tinyproxy stop
  2. 阿里云服务器无备案检测 阿里云服务器配置未备案服务器会返回403状态码,按照状态码判断是否无备案。这里就需要保证域名池中的域名是无解析或者有解析但是配置正确,绝对不能自己搞出个403误判,这点应该是没问题的。

  3. 代码编写

     use GuzzleHttp\Client;
     use GuzzleHttp\Exception\ConnectException;
     use Illuminate\Console\Command;
     use Illuminate\Http\Response;
     use Illuminate\Support\Facades\Event;
     use Lijianmin\DomainDetection\Events\BanDomainEvent;
     use Lijianmin\DomainDetection\Models\Domain;
    
     class DomainMonitorCommand extends Command {
         /**
          * The console command name.
          *
          * @var string
          */
         protected $name = 'domain-detection:monitor';
    
         /**
          * The console command description.
          *
          * @var string
          */
         protected $description = 'monitor domain name is ban.';
    
         const SLEEP_TIME = 2;
         const USER_AGENTS = [
             "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
             "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
             "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
             "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
             "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
             "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
             "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
             "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
             "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
             "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
             "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
             "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
             "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
             "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
             "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
             "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
         ];
    
         const PROXY = [
             '',  //本机
             ... //自己搭建的代理
         ];
    
         /**
          * Execute the console command.
          *
          * @return void
          */
         public function handle(Client $client) {
             //找出正在使用的域名,阿里云域名API获取域名列表,存入数据库。表中'detail'字段我保留了API返回的当前域名原始信息。
             //'short_url'字段为保存时生成的微信短链接 
             $query = Domain::whereNotNull('short_url')
                 ->where(\DB::raw("detail->'$.DomainGroupId'"), config('domain_detection.ali.group_id'))
                 ->where('status', true);
             foreach ($query->cursor() as $index => $domain) {
                 $result = $client->get($domain->short_url, [
                     'allow_redirects' => false,
                     'headers'         => [
                         'USER-AGENT' => array_random(self::USER_AGENTS),  //获取随机'USER-AGENT'
                     ],
                     'proxy'           => array_get(self::PROXY, ($index % count(self::PROXY))), //循环使用使用代理
                     'http_errors'     => false,   //屏蔽4xx 5xx状态码异常
                 ]);
                 //访问微信短链接,获取重定向域名
                 $redirect_domain = $this->extractDomain($result->getHeaderLine('Location'));
                 //如果重定向域名是否与当前数据域名匹配,否则被封了
                 if (strpos($redirect_domain, $domain->domain) !== false) {
                     try {
                         //微信没封再来检测备案
                         if ($client->get($redirect_domain, [
                                 'http_errors' => false, 
                                 'allow_redirects' => false,
                                 //如果无备案域名配置在当前服务器,自己访问是正常的,一定要其他主机访问才会返回403
                                 'proxy'       => last(self::PROXY), 
                             ])->getStatusCode() != Response::HTTP_FORBIDDEN) {
                             $domain->touch();  //更新updated_at 时间戳,刷新最近检测时间
                             sleep(self::SLEEP_TIME);  //HTTP代理比较少,频率低点比较好
                             continue;
                         }
                     } catch (ConnectException $exception) {
                         if ( !$exception->getCode()) {
                             $domain->touch();
                             sleep(self::SLEEP_TIME);
                             continue;
                         };
    
                         throw $exception;
                     }
                 }
                 //被封了或者掉备案了,标记状态
                 $domain->status = false;
                 $domain->save();
                 //这个事件主要处理域名更换操作,移动阿里云域名分组等等
                 Event::fire(new BanDomainEvent($domain));
                 sleep(self::SLEEP_TIME);
             }
         }
         // 提取连接中域名
         public function extractDomain($url) {
             if (empty($url)) {
                 return '';
                 }
    
             return array_get(parse_url($url), 'host');
         }
     }

    阿里云API看文档就好了,阿里云API-PHP-composer,对照文档使用。 提醒下API类: 例如http://domain.aliyuncs.com,看域名前缀找API。找到需要的API类后链式调用就API名称就好了,其实蛮友好的,只是文档没有说明。

    //http://domain.aliyuncs.com
    AlibabaCloud::domain()->v20180129()->updateDomainToDomainGroup()
    //http://domainIntl.aliyuncs.com
    AlibabaCloud::domainIntl()->v20171218()

结语

幸好被微信短链接坑过,才会长记性,遇到其他问题居然把坑给利用起来了。其实核心就一个,用微信短链接来判断域名是否正常是目前想到的最简单的放法了,也不知道那些出售API的是不是有更好的方法,如果需求量大的话就找更多的HTTP代理,不然还是有点担心代理不够用,到时候全是403,不过403解封状态也很快,但是这种硬杠的做法还是不好,代理越多保证万无一失。

本原创文章未经允许不得转载 | 当前页面:OrzLee » 微信-域名被封监测以及自动更换被封域名

评论