简介:一套面向QQ、微信及360安全检测场景的PHP防拦截源码,旨在解决正常链接被误判、误拦截或报毒的问题,适合需要批量发送链接的营销推广人员、站长及初级PHP开发者学习参考。压缩包共7个文件,大小仅113KB,包含两个PHP核心文件、操作说明TXT、CSS样式表以及三张界面演示图,结构清晰,便于对照说明文件快速部署与本地测试。已有117人学习,读者可从教程文件中理解绕过安全检测的基本原理,并利用源码进行功能验证;需要留意的是,使用此类绕过机制应确保内容合法合规,不得用于发送恶意链接或非法营销,同时应关注源码自身的数据安全与隐私保护。整体而言,这份源码为研究即时通讯平台外链安全策略和防误报方案提供了一份紧凑的参考实例。
1. “防封防红不报毒源码”值不值得下载,取决于你看懂了多少
这份源码包的实用价值不在“防封”三个字上,而在于它拆解出来的几个零件:UA识别跳转、多域名轮换、落地页静态化、可用性探测。真正导致链接被拦的,多数时候不是代码写得不好,而是域名信誉、分享行为、页面内容三个变量出了问题。文章先用一章拆微信、QQ、360各自的判定逻辑,再给出可运行的PHP跳转层和监控脚本,最后讲怎么用浏览器自带工具定位拦截发生在哪一跳。适合手里有合法业务、链接却频繁被误拦截的站长,也适合做活动落地页时对链接可用性不放心的一线研发。
2. 微信/QQ/360各自看什么:拦截判定逻辑拆解
2.1 微信/QQ判定的三个输入:域名信誉、URL特征与页面内容
微信和QQ的内置浏览器在打开链接前,会先对链接做一次安全判定,判定的输入可以简化成三个维度。
第一个是域名信誉。这是权重最大的一项。域名如果没备案、注册时间极短、或者同一IP段下批量注册过大量域名,初始信任值就低。域名被用户举报过一次,信任值会明显下降;达到一定阈值后,整个域名下的所有路径都会进入“谨慎打开”状态。这里的判断发生在你打开链接之前,和落地页本身写得是否规范无关。
第二个是URL特征。链接路径里的中文关键词、中文拼音、参数名都会被拆出来做文本匹配。路径里带“get”、“money”、“zhuanqian”,参数里带“=1”这类短特征,触发的概率会高一些。这不是说这些词不能用,而是说你应该把内容目标放到落地页里,URL本身保持简短、干净、无参数。
第三个是落地页内容。打开链接之后,安全组件会抓取页面标题、meta描述和首屏可见文本,重点看有没有诱导分享话术,比如“转发三个群解锁全文”、“分享后领取”;再看页面实际内容和你域名备案主体是不是匹配,一个介绍工业设备的域名下挂着抽奖页面,大概率会被标记。这个抓取是可观测的:你用curl模拟微信UA请求,返回的HTML里如果被插入了安全脚本,说明页面已经进了复核队列。
提示:判断链接是否被标记,常见做法是换一个从未登录过的浏览器直接访问,看是否出现安全提示页。出现提示后先别急着改代码,先去查同一个域名下其他路径是否也被同样的提示拦住。
2.2 360“报毒”的判定逻辑与PHP代码特征
“报毒”在360安全体系里和微信的“拦截”是两套逻辑。360对PHP源码做静态扫描时,不需要运行代码,直接按特征库匹配文件内容。最常见的误报特征组合是:base64_decode配合eval配合gzinflate,只要一个PHP文件里同时出现这三个函数,大概率会被标注为“恶意加密脚本”。
如果你自己写跳转层,注意这几点:不要用eval去执行动态拼出来的字符串;不要用base64_decode去藏真实跳转地址;不要用混淆后的变量名加一段无明显业务含义的长字符串。以上三点在源码包里的出现频率极其高。写法上做一点调整,就能降低被静态扫描命中的概率。
一个相对正常的写法是把跳转目标放到常量或配置里,代码里只做分支判断,不做任何解码动作。这也是后面3.1节里给到的写法。你可以对比一下源码包里常见的$a=str_replace(...);eval($a);这串结构,一旦看到连着三层以上字符串拼接和反转,就可以直接放弃那个文件。
2.3 为什么“任意链接都能打开”是伪命题:域名信誉不可瞬时变更
任何源码zip都不能把已经进入黑名单的域名洗白。域名信誉在安全厂商侧是滞后的,申诉之后,少则几小时,多则几天,期间所有代码层面的跳转都不会改变拦截结果。所谓“打开任意链接”的真实可行方案,是准备一批域名,把流量分摊到不同域名上,并针对每个域名单独观察拦截率。
这里要区分两个概念:短链跳转和域名轮换不是一回事。短链只是把长链接缩短,安全组件会递归展开短链,最终落到的目标域名还是会被检测。域名轮换是指每次分享或打开时,从一组域名里随机分配一个作为入口,哪个域名被拦截了,就把流量切到其他域名上。这套方案对正常业务的收益也很直接:当一个落地页某一天的分享量突然增大,轮换机制可以帮你稀释单点风险。
所以源码再好,也只是整套机制里的一层。域名资源池、监控脚本和失效切换逻辑才决定了这套东西真正能扛多久。下文给出的代码,就是在域名池这个前提下写的最小实现。
3. 跳转层实现:用PHP做UA识别、域名轮换与CSS落地页
3.1 最小可用的UA识别跳转代码
目标很明确:用户在微信里点链接,先到达一个PHP文件,程序根据User-Agent判断客户端类型,再302跳转到对应落地页。这样做的好处是把不同客户端的访问链路分开,方便在第4章的监控环节按客户端维度统计拦截情况。
<?php // jump.php —— UA识别 + 302跳转 $ua = $_SERVER['HTTP_USER_AGENT'] ?? ''; $target = 'https://www.example.com/'; // 默认跳转目标 if (strpos($ua, 'MicroMessenger') !== false) { // 微信内置浏览器的UA固定包含MicroMessenger $target = 'https://wx.example.com/landing'; } elseif (strpos($ua, 'QQ/') !== false || strpos($ua, 'QQBrowser') !== false) { // QQ内置浏览器UA包含QQ/,QQ浏览器包含QQBrowser $target = 'https://qq.example.com/landing'; } // 302临时跳转,301会被浏览器缓存,调整配置后难以生效 header('Location: ' . $target, true, 302); exit; ?>逻辑说明:用strpos判断UA子串,命中即跳转。跳转用302而不是301,因为301会被浏览器和CDN缓存,后续改配置用户还会被送到旧地址。exit一定要写,否则后续代码可能继续执行,输出多余内容导致header报错。
参数说明:$target按需替换成自己的落地页。这段代码里的微信和QQ分支指向不同域名,是刻意安排的——微信和QQ的拦截记录相互独立,分开便于在第4章的监控中定位具体是哪个客户端出了问题。
3.2 多域名轮换池与失效标记
为了让跳转代码不写死域名,需要维护一个域名池。常见做法是在MySQL里建一张表,记录每个域名的状态、最近检查时间和失败次数。跳转时只从状态为正常的域名里随机选一个。
CREATE TABLE dns_pool ( id INT AUTO_INCREMENT PRIMARY KEY, domain VARCHAR(100) NOT NULL, status TINYINT DEFAULT 1 COMMENT '1=正常 0=暂停', last_checked DATETIME, fail_count INT DEFAULT 0, INDEX idx_status (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;<?php // 从域名池随机取一个可用域名 $pdo = new PDO('mysql:host=localhost;dbname=link_monitor', 'user', 'pass'); $stmt = $pdo->query( "SELECT domain FROM dns_pool WHERE status = 1 ORDER BY RAND() LIMIT 1" ); $domain = $stmt->fetchColumn(); if ($domain) { header('Location: https://' . $domain . '/landing', true, 302); exit; } // 所有域名都不可用时的兜底入口 header('Location: https://www.example.com/', true, 302); exit; ?>逻辑说明:status=1过滤掉异常域名,ORDER BY RAND()在表数据量几千行以内完全够用。如果域名数量超过一万行,RAND()排序会让数据库产生临时文件排序,改成ORDER BY last_checked ASC优先使用最久未检查的域名会更合理。
参数说明:last_checked由第4章的探测脚本写入。你可以在跳转接口里顺带做一个记录:把当前命中的域名、客户端类型写入访问日志,这样后续做拦截率分析时有数据可用。
3.3 落地页CSS优化:减少外部请求、处理常见样式需求
落地页会被安全组件抓取内容,同时也会被真实用户打开,因此页面的CSS要尽量轻、不依赖外部资源。外部字体、公共CDN的JS库、统计脚本,这些请求在页面打开时如果加载失败或延迟,会导致页面长时间白屏,安全组件也会把加载超时的页面标记为低质量。
CSS这里常见做法是把首屏样式内联进HTML,避免额外一次CSS文件请求。几个使用频率很高的样式需求可以直接照着写:input居中、多行文本省略、优惠券锯齿边缘。
/* landing.css —— 页面核心样式,全部内联使用 */ html, body { margin: 0; padding: 0; font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; background: #f7f8fa; } .form-item { display: flex; align-items: center; /* 垂直居中 */ justify-content: center; /* 水平居中 */ } /* 两行超出省略 */ .ellipsis-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } /* 优惠券锯齿:使用径向渐变实现,不引入图片资源 */ .coupon { background: radial-gradient(circle at 0 50%, transparent 6px, #fff 6px); filter: drop-shadow(0 2px 4px rgba(0,0,0,0.06)); }逻辑说明:input居中的关键是父容器用flex加justify-content: center,不用给input设置margin。两行省略依赖-webkit-line-clamp,Safari和微信内置浏览器均支持。优惠券的锯齿用radial-gradient模拟,省掉一张图片请求,也避免了锯齿图片在缩放时变糊的问题。
提示:页面里不写外部字体栈,统一使用系统字体栈,是降低落地页“技术债务”成本最低的一步。微信内置浏览器的字体渲染和桌面浏览器不同,系统字体栈能保证两端观感一致。
4. 监控与预警:主动探测、被动回传、阈值告警
4.1 主动探测:curl模拟微信UA检测链接状态
有了跳转层和域名池,接下来要做的是定时检查每个域名对应的落地页是否真的能打开。curl命令里带上微信内置浏览器的UA,模拟真实用户从微信内打开的请求链路。
#!/bin/bash # check_links.sh —— 定时探测域名可用性 domains=("wx.example.com" "qq.example.com" "www.example.com") ua="Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Mobile Safari/537.36 MicroMessenger/8.0.49" for d in "${domains[@]}"; do code=$(curl -s -o /dev/null -w "%{http_code}" -L --max-time 10 -A "$ua" "https://$d/landing") echo "$(date '+%Y-%m-%d %H:%M:%S') $d -> HTTP $code" done逻辑说明:-L跟随重定向链路,--max-time 10防止请求长时间挂起,-o /dev/null丢弃响应体,-w "%{http_code}"只输出状态码。这里请求的/landing路径要和跳转落地路径保持一致,否则测出来的状态码没有参考意义。
把脚本加入crontab,每10分钟执行一次:
*/10 * * * * /usr/local/bin/check_links.sh >> /var/log/link_check.log 2>&1返回码的判断标准参考下表:
| 返回码 | 含义 | 处置建议 |
|---|---|---|
| 200 | 页面正常打开 | 维持监控 |
| 302 | 跳转发生但链尾未返回200 | 沿跳转链逐个检查目标站状态 |
| 403/404 | 页面或服务器异常 | 检查域名解析、目录权限、源站配置 |
| 0 | 超时或连接被重置 | 优先怀疑域名被运营商或安全组件拦截 |
4.2 被动回传:落地页埋像素与PHP回调接口
主动探测只能发现“完全打不开”的情况。多数场景是页面能打开,但被安全组件注入了风险提示浮层,真实用户看到的是警告页。这个问题靠curl检查不出来,需要靠真实用户的访问数据做被动感知。
做法是在落地页底部放一个1像素的透明图片,指向监控接口。图片能加载成功,说明用户看到了完整页面;接口没有请求进来,说明页面在用户端被中断了。
<!-- 放到落地页底部,用户看到页面才会触发请求 --> <img src="https://monitor.example.com/p.gif?domain=wx&page=landing" width="1" height="1" alt="" /><?php // p.gif —— 像素回传接口,记录一条访问记录 // CORS跨域不影响img标签发起GET请求,这里不需要额外处理跨域配置 $pdo = new PDO('mysql:host=localhost;dbname=link_monitor', 'user', 'pass'); $stmt = $pdo->prepare( "INSERT INTO access_log (domain, page, created_at) VALUES (?, ?, NOW())" ); $stmt->execute([ $_GET['domain'] ?? 'unknown', $_GET['page'] ?? 'unknown' ]); // 输出1x1透明GIF header('Content-Type: image/gif'); echo base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'); exit; ?>逻辑说明:img标签的GET请求不受同源策略限制,所以不需要配CORS。接口返回固定格式的GIF内容,浏览器不会报错,用户无感知。
参数说明:domain和page是两个必传参数,也可以额外带上client标记微信、QQ、普通浏览器。记录到数据库后,按域名和时间维度做聚合查询——如果某个域名在之前正常的时间段内访问量突然跌到0,大概率是链接在用户端被拦截了。
4.3 预警阈值与通知方式
监控数据落库后,需要设阈值让告警自动触发。有几个场景比较典型:单个域名10分钟访问量为0、域名池内可用域名少于2个、单域名错误率连续两次探测超过30%。
| 监控项 | 阈值 | 触发动作 |
|---|---|---|
| 访问量断崖 | 10分钟访问量为0 | 钉钉/飞书Webhook推送 |
| 可用域名数 | 池内status=1的域名少于2个 | 短信通知管理员 |
| 探测错误率 | 单域名连续2次非200 | 自动将域名置为status=0 |
Webhook通知用curl即可完成,不用引入新的SDK,一个简单的脚本示例:
# 发送飞书Webhook告警 curl -s -X POST "https://open.feishu.cn/open-apis/bot/v2/hook/your-webhook-id" \ -H "Content-Type: application/json" \ -d '{"msg_type":"text","content":{"text":"[链接监控] wx.example.com 10分钟访问量为0"}}'5. 验证与排错:从链接被拦到定位问题只查这4处
5.1 查UA判断是否命中
打开浏览器开发者工具切换到Network面板,刷新页面后点第一个请求,查看Request Headers里的User-Agent。如果UA里包含了MicroMessenger或QQ/标识而你的跳转代码没生效,说明strpos匹配的字符串和实际UA不一致。建议临时写一个只打印$_SERVER['HTTP_USER_AGENT']的PHP文件放在目标域名下,用手机实际打开一次再核对。
5.2 查跳转链路状态码
在开发者工具Network面板里勾选Preserve log,刷新页面,能看到完整的跳转链。正常情况是302→302→200。如果跳转链里出现了301且Location指向一个不认识的域名,说明跳转配置被改过或入口文件被替换。如果第一跳就返回4xx,先检查域名解析和CDN回源配置。
5.3 查域名在安全组件侧的状态
直接访问域名根路径,看有没有安全确认页。再用微信官方的链接申诉入口查询域名状态。这一步的目的是区分问题出在代码还是域名信誉上。域名被标记时,无论跳转代码怎么写,打开都会先看到安全提示。
5.4 查落地页的外部请求清单
用开发者工具的Network面板看落地页的全部请求,域名逐个确认是自己配置的CDN、统计工具还是其他业务资源。如果出现自己都不认识的域名,页面可能被植入了脚本。外部请求越少,被安全组件误判的变量越少。排查到最后一步时,用无痕窗口重新打开页面,确认无痕模式下请求数和不无痕模式下一致,避免浏览器缓存掩盖注入问题。
本文还有配套的精品资源,点击获取