209 lines
7.5 KiB
Python
Executable File
209 lines
7.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
UpCloud card verification via API — password login + Braintree tokenize + verify.
|
|
This is the working pipeline for verifying a card on an existing UpCloud account.
|
|
|
|
Usage:
|
|
python upcloud_verify_card.py
|
|
|
|
Prerequisites:
|
|
- Anti-captcha API key with balance
|
|
- UpCloud account already registered (username + password)
|
|
- Email IMAP credentials for OTP fallback
|
|
- Card details (number, exp month/year, CVV)
|
|
|
|
Flow:
|
|
1. Solve hCaptcha (login sitekey)
|
|
2. Password login via /v2/session
|
|
3. Get Braintree client token
|
|
4. Tokenize card via Braintree GraphQL
|
|
5. Verify card via /v2/account/main/verify (tries EUR/DE first, then USD/US)
|
|
6. On success: extract API token, try server creation
|
|
|
|
If card is UA-BIN, Braintree will reject with 422 VERIFICATION_ERROR before 3DS.
|
|
"""
|
|
import requests, json, time, base64, re
|
|
|
|
GATE = 'https://gate.upcloud.com'
|
|
HUB = 'https://hub.upcloud.com'
|
|
AC_KEY = 'YOUR_ANTICAPTCHA_KEY'
|
|
|
|
# === CONFIG ===
|
|
USERNAME = 'upXXXXXXXX'
|
|
PASSWORD = 'your_password'
|
|
EMAIL = '[REDACTED-EMAIL]'
|
|
EMAIL_PASS = 'your_email_password'
|
|
CARD = '0000000000000000'
|
|
EXP_M = 'MM'
|
|
EXP_Y = 'YYYY'
|
|
CVV = 'NNN'
|
|
IMAP_HOST = 'secureimap.t-online.de'
|
|
IMAP_PORT = 993
|
|
UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
|
SITEKEY = '13b63774-5d8c-4b1a-9cfc-8bae5b1abca0'
|
|
|
|
def solve_captcha():
|
|
r = requests.post('https://api.anti-captcha.com/createTask', json={
|
|
'clientKey': AC_KEY,
|
|
'task': {'type': 'HCaptchaTaskProxyless', 'websiteURL': HUB,
|
|
'websiteKey': SITEKEY,
|
|
'isInvisible': True, 'isEnterprise': True}
|
|
}, timeout=30)
|
|
d = r.json()
|
|
tid = d.get('taskId')
|
|
if not tid:
|
|
return None
|
|
for i in range(80):
|
|
time.sleep(2)
|
|
r = requests.post('https://api.anti-captcha.com/getTaskResult',
|
|
json={'clientKey': AC_KEY, 'taskId': tid}, timeout=15)
|
|
d = r.json()
|
|
if d.get('status') == 'ready':
|
|
return d['solution']['gRecaptchaResponse']
|
|
if d.get('errorId'):
|
|
return None
|
|
return None
|
|
|
|
def get_otp(timeout=120):
|
|
import imaplib, email as emaillib
|
|
t0 = time.time()
|
|
while time.time() - t0 < timeout:
|
|
try:
|
|
m = imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT)
|
|
m.login(EMAIL, EMAIL_PASS)
|
|
m.select('INBOX')
|
|
_, data = m.search(None, '(FROM "upcloud" UNSEEN)')
|
|
ids = data[0].split()
|
|
if ids:
|
|
_, msg_data = m.fetch(ids[-1], '(RFC822)')
|
|
msg = emaillib.message_from_bytes(msg_data[0][1])
|
|
body = ''
|
|
if msg.is_multipart():
|
|
for part in msg.walk():
|
|
ct = part.get_content_type()
|
|
if ct in ('text/plain', 'text/html'):
|
|
body = part.get_payload(decode=True).decode(errors='replace')
|
|
break
|
|
else:
|
|
body = msg.get_payload(decode=True).decode(errors='replace')
|
|
match = re.search(r'\*([A-Z0-9]{4,10})\*', body)
|
|
if match:
|
|
m.logout()
|
|
return match.group(1)
|
|
match = re.search(r'\b(\d{6})\b', body)
|
|
if match:
|
|
m.logout()
|
|
return match.group(1)
|
|
m.logout()
|
|
except:
|
|
pass
|
|
time.sleep(5)
|
|
return None
|
|
|
|
def main():
|
|
s = requests.Session()
|
|
s.headers.update({
|
|
'User-Agent': UA,
|
|
'Origin': 'https://hub.upcloud.com',
|
|
'Referer': 'https://hub.upcloud.com/',
|
|
'Content-Type': 'application/json'
|
|
})
|
|
|
|
# 1. Password login
|
|
print('[1] Password login...')
|
|
t1 = solve_captcha()
|
|
if not t1:
|
|
return False
|
|
r = s.post(f'{GATE}/v2/session', json={
|
|
'captcha_token': t1, 'username': USERNAME,
|
|
'password': PASSWORD, 'token': True
|
|
}, timeout=15)
|
|
print(f' Login: {r.status_code}')
|
|
if r.status_code == 422:
|
|
print(' Captcha rejected, retry...')
|
|
t1 = solve_captcha()
|
|
if not t1:
|
|
return False
|
|
r = s.post(f'{GATE}/v2/session', json={
|
|
'captcha_token': t1, 'username': USERNAME,
|
|
'password': PASSWORD, 'token': True
|
|
}, timeout=15)
|
|
print(f' Login retry: {r.status_code}')
|
|
if r.status_code != 200:
|
|
print(f' {r.text[:200]}')
|
|
return False
|
|
|
|
# Token is in cookies, not JSON
|
|
auth_token = [REDACTED]'auth-token', '')
|
|
if auth_token:
|
|
[REDACTED]'Authorization': f'Bearer {auth_token}'})
|
|
|
|
r = s.get(f'{GATE}/v2/account', timeout=15)
|
|
print(f' Account: {r.status_code}')
|
|
if r.status_code != 200:
|
|
return False
|
|
acct = r.json().get('account', {})
|
|
print(f' active={acct.get("active")} credits={acct.get("credits")}')
|
|
|
|
# 2. Braintree
|
|
print('[2] Braintree...')
|
|
r = s.get(f'{GATE}/v2/braintree/client_token/EUR', timeout=15)
|
|
if r.status_code != 200:
|
|
r = s.get(f'{GATE}/v2/braintree/client_token/USD', timeout=15)
|
|
if r.status_code != 200:
|
|
print(f' FAIL: {r.text[:200]}')
|
|
return False
|
|
dec = json.loads(base64.b64decode(r.json()['client_token']).decode())
|
|
afp = dec['authorizationFingerprint']
|
|
gql = dec['graphQL']['url']
|
|
|
|
# 3. Tokenize
|
|
print('[3] Tokenize card...')
|
|
mut = 'mutation T($input: TokenizeCreditCardInput!){tokenizeCreditCard(input:$input){token}}'
|
|
r = requests.post(gql, json={"query": mut, "variables": {
|
|
"input": {"creditCard": {"number": CARD, "expirationMonth": EXP_M,
|
|
"expirationYear": EXP_Y, "cvv": CVV},
|
|
"options": {"validate": False}}
|
|
}}, headers={"Authorization": f"Bearer {afp}",
|
|
"Content-Type": "application/json",
|
|
"Braintree-Version": "2023-08-01"}, timeout=30)
|
|
resp = r.json()
|
|
if 'errors' in resp:
|
|
print(f' ERR: {resp["errors"]}')
|
|
return False
|
|
nonce = resp["data"]["tokenizeCreditCard"]["token"]
|
|
print(f' Nonce: {nonce}')
|
|
|
|
# 4. Verify (rate limit: wait 10s between attempts, max 2-3)
|
|
print('[4] Verify card...')
|
|
for curr, country, city, addr, postal, state in [
|
|
('EUR', 'DEU', 'Berlin', 'Hauptstrasse 22', '10115', ''),
|
|
('USD', 'USA', 'New York', '350 Fifth Avenue', '10118', 'NY'),
|
|
]:
|
|
time.sleep(10)
|
|
print(f' {curr}/{country}...')
|
|
v = {"account": {"address": addr, "city": city, "country": country,
|
|
"first_name": "signup", "last_name": "user",
|
|
"payment_method_nonce": nonce, "postal_code": postal,
|
|
"company": "", "currency": curr,
|
|
"device_data": json.dumps({"correlation_id": str(int(time.time()))}),
|
|
"state": state, "vat_number": ""}}
|
|
r = s.post(f'{GATE}/v2/account/main/verify', json=v, timeout=30)
|
|
print(f' {r.status_code} {r.text[:400]}')
|
|
if r.status_code in [200, 201]:
|
|
print('\n=== VERIFIED! ===')
|
|
r = s.get(f'{GATE}/v2/account', timeout=15)
|
|
a = r.json().get('account', {})
|
|
print(f' active={a.get("active")} trial_started={a.get("trial_started")}')
|
|
r = s.post(f'{GATE}/v2/account/tokens', json={
|
|
"name": "eni-prod", "expires_at": "2027-09-01T00:00:00.00001Z"
|
|
}, timeout=15)
|
|
tok = r.json().get('token', {}).get('token', '') if r.status_code == 201 else ''
|
|
print(f' API token: {tok}')
|
|
return True
|
|
print('\n[FAIL] Card rejected or rate limited')
|
|
return False
|
|
|
|
if __name__ == '__main__':
|
|
main()
|