一个轻量级 PHP Web 代理,在 miniProxy 基础上 增加了 现代网页适配与可选的 CAP 人机验证闸门,用于防止代理被匿名滥用。
原始文件 miniproxy.php 在两个分支上均未做任何修改,作为回退依据完整保留。
所有改造都在新增的入口副本上进行。
| 文件 | 分支 | 人机验证 | 现代网页适配 | 适合谁 |
|---|---|---|---|---|
miniproxy.php |
两分支共有 | ✗ | ✗ | 内网自用、已有限流、需要最简部署 |
miniproxy-plus.php |
main |
✗ | ✓ | 公网部署、需要兼容现代站点 |
miniproxy_captcha.php |
feat/captcha-gate |
✓ | ✓ | 公网部署、既要防刷又要兼容现代站点 |
main 分支 feat/captcha-gate 分支
├─ miniproxy.php ← 未改动 ├─ miniproxy.php ← 未改动
├─ miniproxy-plus.php ← 现代适配 ├─ miniproxy_captcha.php ← 闸门 + 现代适配
└─ test_modern.php ├─ captcha/
│ ├─ gate.php 闸门核心库
│ ├─ config.php 集中配置
│ ├─ verify.php 独立验证页
│ ├─ callback.php token 服务端校验
│ ├─ test_gate.php 闸门单元测试
│ └─ README.md 详细部署说明
└─ test_modern.php
miniproxy-plus.php 与 miniproxy_captcha.php 自 START CONFIGURATION 起的代理逻辑
逐字节一致(MD5 同为 b5ff664f06af264a76ae4a59871f9378),两者不会因重复修改而分叉。
不是凭空加功能,而是用两个现代代理的入口分别抓真实站点,把问题一个个测出来再修。 以下 6 个缺陷均经实测确认:
// 原版
$components = array_map("trim", str_split($source, strrpos($source, " ")));srcset 条目的描述符是可选的,没有空格时 strrpos() 返回 false,
str_split() 直接抛致命错误。访问 wikipedia.org 会得到 500。
改为按逗号切分后单独解析描述符,并正确处理含逗号的 data: URI。
Content-Security-Policy 原样转发,但其中的 default-src 'self'、worker-src 'self'
指向的是真实源站,不是代理。结果是代理页自身的脚本、样式、字体、AJAX 全部被拦。
Strict-Transport-Security 透传会把真实源站钉死为 HTTPS,并影响通过代理访问的
其他子域。
原版只剥离 Content-Length / Transfer-Encoding / Content-Encoding。
现补充:
Content-Security-Policy(含-Report-Only)Strict-Transport-SecurityCross-Origin-Embedder-Policy/-Opener-Policy/-Resource-PolicyAlt-Svc(会通告 HTTP/3 端点,绕过代理)Reporting-Endpoints/Report-ToPermissions-Policy/Feature-PolicyX-Frame-Options
只重写 href / src。现补充:
data-src、data-srcset、data-lazy-src、data-original、data-image、data-poster、data-bg、data-url、data-href等懒加载属性srcset/imagesrcset从仅<img>扩展到所有元素- 移除
integrity(SRI 哈希与代理源不匹配会被浏览器拦) - 移除
nonce(CSP 已被剥离,留着反而让内联脚本失败) - 中和
<base href>(否则所有相对 URL 都解析到真实源站) - 绝对 URL 也强制走代理:原版
rel2abs()对绝对 URL 原样返回, 导致href="https://evil.com"会让浏览器直接离开代理
现代站点早就不用 XHR 了。现补充:
window.fetch(含Request对象形态)navigator.sendBeaconwindow.openblob:/data:/about:等协议直连的例外
- PHP 8.2+ 的
mb_convert_encoding(..., "HTML-ENTITIES", ...)废弃警告, 改为转 UTF-8 后mb_encode_numericentity(),渲染结果等价 - 关闭
display_errors,避免 PHP 警告/弃用提示混进代理 HTML 破坏页面 - cURL 增加连接与总超时(10s / 30s),避免慢站点拖到
max_execution_time返回裸 500
| 项目 | 要求 |
|---|---|
| PHP | miniproxy.php ≥ 5.4.7;两个新版入口 ≥ 5.6(用到 hash_equals) |
| 扩展 | curl、mbstring、xml 三者必装 |
| Session | 仅 feat/captcha-gate 分支需要 PHP Session 可写 |
| 网络 | 仅 feat/captcha-gate 分支需出站可访问 captcha.api.968111.xyz |
检查命令:
php -v
php -m | grep -Ei 'curl|mbstring|xml'git clone https://github.com/Johnsheng1/miniproxy-plus.git
cd miniproxy-plus && git checkout main- 上传
miniproxy-plus.php到站点目录; - 浏览器访问
https://你的域名/miniproxy-plus.php,看到代理首页即成功; - 按需修改文件顶部
/*** START CONFIGURATION ***/区域的配置项。
| 变量 | 默认 | 说明 |
|---|---|---|
$whitelistPatterns |
[] |
允许代理的 URL 正则白名单,空数组 = 允许任意 URL |
$blacklistPatterns |
[] |
禁止代理的 URL 正则黑名单 |
$forceCORS |
true |
给被代理站点输出 CORS 头 |
$disallowLocal |
true |
禁止代理内网地址(防 SSRF,别关) |
$anonymize |
true |
不透传客户端 IP 给上游(X-Forwarded-For) |
$startURL |
"" |
直接访问时的默认代理目标,留空则显示首页 |
$landingExampleURL |
"https://google.com" |
首页表单里的示例 URL |
用法:在 URL 后拼上要代理的地址。
https://你的域名/miniproxy-plus.php?https://example.com
git clone https://github.com/Johnsheng1/miniproxy-plus.git
cd miniproxy-plus && git checkout feat/captcha-gatecaptcha/ 必须与入口副本同级(代码通过 __DIR__ 定位,不可拆分):
站点根目录/
├─ miniproxy.php ← 原文件,可不上传
├─ miniproxy_captcha.php ← 新入口
└─ captcha/
├─ config.php
├─ gate.php
├─ verify.php
├─ callback.php
└─ test_gate.php ← 可不上传,仅本地测试用
PHP 的 curl 若找不到 CA bundle,请求 CAP API 会失败,表现为永远验证不过。
php -r 'var_dump(ini_get("curl.cainfo"));'若输出空字符串,在 php.ini 中补两行并重启 PHP:
; Linux
curl.cainfo = "/etc/ssl/certs/ca-certificates.crt"
openssl.cafile = "/etc/ssl/certs/ca-certificates.crt"
; Windows 需先从 https://curl.se/ca/cacert.pem 下载后指定路径
curl.cainfo = "C:/php/extras/ssl/cacert.pem"
openssl.cafile = "C:/php/extras/ssl/cacert.pem"/miniproxy.php 不在闸门保护范围内,直接暴露等于没装验证。二选一:
方式 A:改用新地址作为对外入口
https://你的域名/miniproxy_captcha.php?https://example.com
方式 B:保持原 URL,用 Web Server 重写(推荐)
Nginx:
location = /miniproxy.php {
rewrite ^ /miniproxy_captcha.php last;
}Apache .htaccess:
RewriteEngine On
RewriteRule ^miniproxy\.php$ /miniproxy_captcha.php [L]两种方式都不需要修改 PHP 原文件。若同时保留原文件可访问,建议用 Nginx 直接挡掉:
location = /miniproxy.php { return 404; }编辑 captcha/config.php:
| 配置项 | 默认 | 何时改 |
|---|---|---|
CAPTCHA_FORCE_HTTPS |
false |
站点上了 HTTPS 就改 true,否则验证 token 可能被抓包重放 |
CAPTCHA_VERIFIED_TTL |
1800 |
验证通过后的有效期(秒),0.5 小时 |
CAPTCHA_FAILURE_LIMIT |
5 |
多少分钟内允许失败几次 |
CAPTCHA_FAILURE_WINDOW |
300 |
失败计数的时间窗口(秒) |
CAPTCHA_IP_REQUEST_LIMIT |
30 |
单 IP 在窗口内的最大提交次数 |
CAPTCHA_TIMEOUT |
8 |
请求 CAP API 的总超时(秒) |
CAP_API_ENDPOINT |
https://captcha.api.968111.xyz/api/ |
自建 CAP 服务时改这里 |
若整套部署在子目录(如 /proxy/)且被反向代理改写导致路径推导失败:
define('CAPTCHA_BASE_PATH', '/proxy'); // 不要以 / 结尾# 语法检查:应全部 No syntax errors
for f in miniproxy_captcha.php captcha/config.php captcha/gate.php \
captcha/verify.php captcha/callback.php; do
php -l $f
done
# 安全逻辑单元测试:期望"通过 51 项,失败 0 项"
php captcha/test_gate.php
# 现代网页适配测试:期望"通过 52 项,失败 0 项"
php test_modern.php
# 未验证访问应返回 302 跳验证页
curl -sI "https://你的域名/miniproxy_captcha.php?https://example.com" | grep -i location
# 期望:Location: https://你的域名/captcha/verify.php?redirect=...然后浏览器打开同一地址,应看到验证页 → 完成验证 → 自动回到代理内容; 0.5 小时内再访问其他代理目标不再要求验证。
访问受保护页 → 闸门检查 Session
├─ 已通过且在有效期内 → 放行,直接代理
└─ 未通过/已过期 → 302 到 captcha/verify.php?redirect=<安全处理后的原URL>
↓
前端 cap-widget 完成 PoW 挑战,触发 solve 事件
↓
POST token 到 captcha/callback.php(带一次性 CSRF)
↓
PHP 用 cURL 请求 CAP /api/validate 做服务端二次校验
↓
仅当 success === true 才写入 Session
↓
session_regenerate_id(true) 防会话固定,跳回原页面
设计要点:
- 只信任服务端结果:前端任何"已通过"标识都不采信,只看 CAP 服务端返回的 JSON。
- 防开放重定向:回调地址必须同源(scheme + host + 端口全匹配),
拒绝
javascript:、data:、//evil.com、http://host@evil.com等, 不合规一律回落到站内首页。 - 双级防刷:会话级 5 分钟失败 5 次、IP 级 5 分钟 30 次,超限返回
429+Retry-After。 - 默认拒绝放行:CAP 服务超时或返回异常时不降级绕过,返回
503。 这是有意为之——代理服务一旦被刷,压力会转移到上游目标站点。
关闭验证(临时):注释掉 miniproxy_captcha.php 里 mp_captcha_gate([...]); 一行即可,
无需删文件。
| 项目 | 结果 |
|---|---|
| 原文件改动 | miniproxy.php 零改动,与仓库历史一致 |
| 语法检查 | 全部 PHP 文件通过 php -l |
| 闸门单元测试 | captcha/test_gate.php 51 项通过 |
| 现代网页单元测试 | test_modern.php 52 项通过 |
| CAP 真实联调 | PoW 50/50 → redeem 成功 → 服务端 validate 返回 success:true |
| 端到端 | 验证通过后成功代理 httpbin.org/get,客户端 IP 已被匿名化 |
| 原版 500 修复 | wikipedia.org 由 500 恢复为 200(149KB),无 PHP 错误污染,CSP 头已剥离 |
| 无回归 | Landing Page、JS / 字体 / SVG 资源、表单 POST 回写功能均正常 |
本项目是 miniProxy(作者 Joshua Dick)的衍生作品,
沿用其 GNU General Public License v3,完整条款见仓库根目录的 LICENSE。
这意味着:
- 你可以自由使用、修改和再分发本项目的代码;
- 分发时必须同时提供 GPLv3 许可证文本并保留版权声明;
- 基于本项目的衍生作品必须以相同许可证开源;
- 本项目不提供任何担保,使用风险由使用者自行承担。
上游 miniProxy 自 2020-04-26 起已停止维护,本项目同样"按现状提供"。