175 lines
5.9 KiB
Python
175 lines
5.9 KiB
Python
import uuid
|
||
import base64
|
||
from curl_cffi import requests # 用于模拟指纹
|
||
|
||
from config import CLAUDE_URL, get_proxy
|
||
from core.models import ClaudeAccount
|
||
from core.identity import random_ua
|
||
|
||
|
||
def attack_claude(target_email):
|
||
"""伪装成浏览器发起攻击"""
|
||
device_id = str(uuid.uuid4())
|
||
ua = random_ua()
|
||
|
||
headers = {
|
||
"Host": "claude.ai",
|
||
"Anthropic-Anonymous-Id": f"claudeai.v1.{uuid.uuid4()}",
|
||
"Sec-Ch-Ua-Full-Version-List": '"Google Chrome";v="143.0.7499.105", "Chromium";v="143.0.7499.105", "Not A(Brand";v="24.0.0.0"',
|
||
"Sec-Ch-Ua-Platform": '"Linux"',
|
||
"Sec-Ch-Ua": '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
|
||
"Baggage": "sentry-environment=production,sentry-release=ce5600af514463a166f4cd356a6afbc46ee5cd3d,sentry-public_key=58e9b9d0fc244061a1b54fe288b0e483,sentry-trace_id=7bdc872994f347d6b5a610a520f40401,sentry-org_id=1158394",
|
||
"Anthropic-Client-Sha": "ce5600af514463a166f4cd356a6afbc46ee5cd3d",
|
||
"Content-Type": "application/json",
|
||
"Anthropic-Client-Platform": "web_claude_ai",
|
||
"Anthropic-Device-Id": device_id,
|
||
"Anthropic-Client-Version": "1.0.0",
|
||
"User-Agent": ua,
|
||
"Origin": "https://claude.ai",
|
||
"Referer": "https://claude.ai/login?returnTo=%2Fonboarding",
|
||
"Accept-Encoding": "gzip, deflate, br",
|
||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||
"Priority": "u=1, i"
|
||
}
|
||
|
||
payload = {
|
||
"utc_offset": -480,
|
||
"email_address": target_email,
|
||
"login_intent": None,
|
||
"locale": "en-US",
|
||
"oauth_client_id": None,
|
||
"source": "claude"
|
||
}
|
||
|
||
try:
|
||
print(f"[*] 正在向 Claude 发送 Magic Link 请求: {target_email}")
|
||
session = requests.Session()
|
||
response = session.post(
|
||
CLAUDE_URL,
|
||
json=payload,
|
||
headers=headers,
|
||
impersonate="chrome124",
|
||
proxies=get_proxy(),
|
||
)
|
||
|
||
if response.status_code == 200:
|
||
print("[+] 请求成功,Claude 已发信。")
|
||
return True
|
||
elif response.status_code == 429:
|
||
print("[-] 被限流了 (Rate Limited)。")
|
||
else:
|
||
print(f"[-] 请求失败 Code: {response.status_code}, Body: {response.text}")
|
||
|
||
except Exception as e:
|
||
print(f"[-] 发生异常: {e}")
|
||
|
||
return False
|
||
|
||
|
||
def finalize_login(magic_link_fragment):
|
||
"""
|
||
完成最后一步:无需浏览器,直接交换 sessionKey。
|
||
magic_link_fragment 格式: https://claude.ai/magic-link#token:base64_email
|
||
返回 ClaudeAccount 对象
|
||
"""
|
||
|
||
# 1. 外科手术式拆解 Hash
|
||
if '#' in magic_link_fragment:
|
||
fragment = magic_link_fragment.split('#')[1]
|
||
else:
|
||
fragment = magic_link_fragment
|
||
|
||
if ':' not in fragment:
|
||
print("[-] 链接格式错误: 找不到 token 和 email 的分隔符")
|
||
return None
|
||
|
||
# 分割 nonce 和 base64_email
|
||
nonce, encoded_email = fragment.split(':', 1)
|
||
|
||
try:
|
||
decoded_email = base64.b64decode(encoded_email).decode('utf-8')
|
||
print(f"[*] 解析成功 -> Email: {decoded_email} | Nonce: {nonce[:8]}...")
|
||
except:
|
||
print(f"[*] 解析成功 -> Nonce: {nonce[:8]}...")
|
||
|
||
# 2. 构造最终 payload
|
||
verify_url = "https://claude.ai/api/auth/verify_magic_link"
|
||
|
||
payload = {
|
||
"credentials": {
|
||
"method": "nonce",
|
||
"nonce": nonce,
|
||
"encoded_email_address": encoded_email
|
||
},
|
||
"locale": "en-US",
|
||
"oauth_client_id": None,
|
||
"source": "claude"
|
||
}
|
||
|
||
# 3. 伪造指纹头
|
||
device_id = str(uuid.uuid4())
|
||
ua = random_ua()
|
||
headers = {
|
||
"Host": "claude.ai",
|
||
"Content-Type": "application/json",
|
||
"Origin": "https://claude.ai",
|
||
"Referer": "https://claude.ai/magic-link",
|
||
"User-Agent": ua,
|
||
"Accept": "*/*",
|
||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||
"Anthropic-Client-Version": "1.0.0",
|
||
"Anthropic-Client-Platform": "web_claude_ai",
|
||
"Anthropic-Device-Id": device_id,
|
||
"Sec-Fetch-Dest": "empty",
|
||
"Sec-Fetch-Mode": "cors",
|
||
"Sec-Fetch-Site": "same-origin",
|
||
"Priority": "u=1, i"
|
||
}
|
||
|
||
try:
|
||
print(f"[*] 正在交换 SessionKey...")
|
||
session = requests.Session()
|
||
response = session.post(
|
||
verify_url,
|
||
json=payload,
|
||
headers=headers,
|
||
impersonate="chrome124",
|
||
proxies=get_proxy(),
|
||
)
|
||
|
||
if response.status_code == 200:
|
||
data = response.json()
|
||
# 1. 提取 SessionKey
|
||
session_key = response.cookies.get("sessionKey")
|
||
|
||
if not session_key:
|
||
for name, value in response.cookies.items():
|
||
if name == "sessionKey":
|
||
session_key = value
|
||
break
|
||
|
||
if not session_key:
|
||
print("[-] 请求成功 (200),但未在 Cookie 中找到 sessionKey。")
|
||
print(f"Response: {str(data)[:200]}...")
|
||
return None
|
||
|
||
# 2. 提取 Org UUID
|
||
try:
|
||
org_uuid = data['account']['memberships'][0]['organization']['uuid']
|
||
email = data['account']['email_address']
|
||
print(f"[+] 登录成功。OrgID: {org_uuid}")
|
||
return ClaudeAccount(email, session_key, org_uuid, ua)
|
||
except KeyError as e:
|
||
print(f"[-] 无法提取 Organization UUID,结构可能变了: {e}")
|
||
print(f"Response keys: {data.keys() if isinstance(data, dict) else type(data)}")
|
||
return None
|
||
|
||
else:
|
||
print(f"[-] 交换失败 Code: {response.status_code}")
|
||
print(f"Body: {response.text}")
|
||
return None
|
||
|
||
except Exception as e:
|
||
print(f"[-] 发生异常: {e}")
|
||
return None
|