配套视频 https://www.bilibili.com/video/BV1w7GTzMEy7
获取ITDog等拨测服务的IP 因为Vercel不支持IPv6,所以我们只需要获取v4IP
如果你有VPS,直接写一个Py脚本创建一个HTTP服务器记录IP去重即可
如果你只有家里云,可以使用Cloudflare Tunnel,然后获取 CF-Connecting-IP 来曲线救国
结论,你已经获得了你要屏蔽的拨测网站的IP
创建Vercel API Token 前往 https://vercel.com/account/settings/tokens 创建一个Token
抓取防火墙创建/更新接口 前往 https://vercel.com/your-projects/fuwari/firewall
新增规则
随便写点东西然后抓包
PATCH https://vercel.com/api/v1/security/firewall/config/draft?projectId=prj_UfvbpIvawjL2eAETAiZT7hPLR8W2&teamId=team_lemndzHQNJAcTipIF6elB5Md 将主机名 vercel.com 改为 api.vercel.com 。并携带请求头 Authorization ,值为刚才获取的Token
复制刚才的响应并且稍作修改进行测试,看是否能更新成功
可以看到已经200 OK
使用Python脚本创建大批量IP拒绝规则 根据本人测试,Vercel虽然在创建规则的时候有一个 is any of 支持填入多个IP,但是单规则最多只能填写75个,所以我们需要一个Python脚本批量帮我们规划。脚本已经写好
使用: python app.py ip.txt
作用:自动获取指定TXT中的内容并将其中的所有IP添加到拒绝规则
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Vercel防火墙规则更新脚本 用法: python vercelnoitdog.py xxx.txt """ import sys import json import requests import ipaddress from typing import List, Dict, Any # Vercel API配置 API_BASE_URL = "https://api.vercel.com/v1/security/firewall/config/draft" PROJECT_ID = "prj_UfvbpIvawjL2eAETAiZT7hPLR8W2" TEAM_ID = "team_lemndzHQNJAcTipIF6elB5Md" AUTH_TOKEN = "你的Token" RULE_ID = "rule_noitdog_eGxdcK" # 每组最大IP数量 MAX_IPS_PER_GROUP = 75 def validate_ip_or_cidr(ip_str: str) -> bool: """ 验证IP地址或CIDR格式是否有效 """ try: # 尝试解析为IP地址或网络 ipaddress.ip_address(ip_str) return True except ValueError: try: # 尝试解析为CIDR网络 ipaddress.ip_network(ip_str, strict=False) return True except ValueError: return False def read_ips_from_file(file_path: str) -> List[str]: """ 从文件中读取IP地址和CIDR网段 """ ips = [] invalid_entries = [] try: with open(file_path, 'r', encoding='utf-8') as f: for line_num, line in enumerate(f, 1): ip = line.strip() if ip and not ip.startswith('#'): # 忽略空行和注释 if validate_ip_or_cidr(ip): ips.append(ip) else: invalid_entries.append(f"第{line_num}行: {ip}") print(f"从文件 {file_path} 读取到 {len(ips)} 个有效的IP地址/CIDR网段") if invalid_entries: print(f"⚠️ 发现 {len(invalid_entries)} 个无效条目:") for entry in invalid_entries[:5]: # 只显示前5个 print(f" {entry}") if len(invalid_entries) > 5: print(f" ... 还有 {len(invalid_entries) - 5} 个无效条目") return ips except FileNotFoundError: print(f"错误: 文件 {file_path} 不存在") sys.exit(1) except Exception as e: print(f"读取文件时出错: {e}") sys.exit(1) def chunk_ips(ips: List[str], chunk_size: int = MAX_IPS_PER_GROUP) -> List[List[str]]: """ 将IP列表分组,每组最多包含指定数量的IP """ chunks = [] for i in range(0, len(ips), chunk_size): chunks.append(ips[i:i + chunk_size]) return chunks def create_condition_groups(ip_chunks: List[List[str]]) -> List[Dict[str, Any]]: """ 创建条件组,每个组包含一个IP列表 """ condition_groups = [] for ip_chunk in ip_chunks: condition_group = { "conditions": [ { "op": "inc", "type": "ip_address", "value": ip_chunk } ] } condition_groups.append(condition_group) return condition_groups def create_request_payload(condition_groups: List[Dict[str, Any]]) -> Dict[str, Any]: """ 创建请求负载 """ payload = { "action": "rules.update", "id": RULE_ID, "value": { "name": "noitdog", "active": True, "description": "", "conditionGroup": condition_groups, "action": { "mitigate": { "action": "deny", } } } } return payload def send_request(payload: Dict[str, Any]) -> bool: """ 发送PATCH请求到Vercel API """ url = f"{API_BASE_URL}?projectId={PROJECT_ID}&teamId={TEAM_ID}" headers = { "Authorization": f"Bearer {AUTH_TOKEN}", "Content-Type": "application/json" } try: print(f"发送请求到: {url}") print(f"请求数据: {json.dumps(payload, indent=2, ensure_ascii=False)}") response = requests.patch(url, headers=headers, json=payload) print(f"响应状态码: {response.status_code}") print(f"响应内容: {response.text}") if response.status_code == 200: print("✅ 请求成功") return True else: print(f"❌ 请求失败: {response.status_code} - {response.text}") return False except requests.exceptions.RequestException as e: print(f"❌ 网络请求错误: {e}") return False except Exception as e: print(f"❌ 发送请求时出错: {e}") return False def main(): """ 主函数 """ if len(sys.argv) != 2: print("用法: python vercelnoitdog.py <ip_file.txt>") print("示例: python vercelnoitdog.py ips.txt") sys.exit(1) ip_file = sys.argv[1] # 读取IP地址 ips = read_ips_from_file(ip_file) if not ips: print("❌ 没有找到有效的IP地址或CIDR网段") sys.exit(1) # 去重 unique_ips = list(set(ips)) print(f"去重后共有 {len(unique_ips)} 个唯一IP地址/CIDR网段") # 分组 ip_chunks = chunk_ips(unique_ips) print(f"IP地址被分为 {len(ip_chunks)} 组") for i, chunk in enumerate(ip_chunks, 1): print(f"第 {i} 组: {len(chunk)} 个IP/CIDR") # 创建条件组 condition_groups = create_condition_groups(ip_chunks) # 创建请求负载 payload = create_request_payload(condition_groups) # 发送请求 success = send_request(payload) if success: print("\n🎉 防火墙规则更新成功!") else: print("\n💥 防火墙规则更新失败!") sys.exit(1) if __name__ == "__main__": main() 示例ip.txt
...