#!/usr/bin/env python3
"""
Comprehensive scanner for unauthenticated RCE vectors in web2py
Focus on finding deserialization or code execution without authentication
"""

import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

TARGET = "https://localhost:5000"

def test_generic_views():
    """Test if generic views (.json, .load, etc) are enabled"""
    print("\n[*] Testing generic views...")
    
    endpoints = [
        "/welcome/default/index.json",
        "/welcome/default/index.xml",
        "/welcome/default/index.load",
        "/examples/default/index.json",
    ]
    
    for ep in endpoints:
        try:
            r = requests.get(f"{TARGET}{ep}", verify=False, timeout=5)
            if r.status_code != 404:
                print(f"[+] {ep}: {r.status_code}")
        except:
            pass

def test_special_functions():
    """Test special web2py functions that might be exposed"""
    print("\n[*] Testing special functions...")
    
    functions = [
        "user",
        "download",
        "call",
        "data",
        "error",
    ]
    
    apps = ["welcome", "examples"]
    controllers = ["default", "api"]
    
    for app in apps:
        for controller in controllers:
            for func in functions:
                try:
                    url = f"{TARGET}/{app}/{controller}/{func}"
                    r = requests.get(url, verify=False, timeout=5, allow_redirects=False)
                    if r.status_code == 200:
                        print(f"[+] {url}: 200 OK")
                        if len(r.text) < 500:
                            print(f"    Response: {r.text[:200]}")
                except:
                    pass

def test_call_service():
    """Test /call endpoint for exposed services"""
    print("\n[*] Testing /call service endpoints...")
    
    services = ["run", "json", "jsonrpc", "jsonrpc2", "xmlrpc", "soap", "amfrpc"]
    apps = ["welcome", "examples"]
    
    for app in apps:
        for service in services:
            try:
                url = f"{TARGET}/{app}/default/call/{service}"
                r = requests.get(url, verify=False, timeout=5, allow_redirects=False)
                if r.status_code != 404:
                    print(f"[+] {url}: {r.status_code}")
                    if r.status_code == 200:
                        print(f"    Response: {r.text[:200]}")
            except:
                pass

def test_data_endpoints():
    """Test /data endpoints that might handle data"""
    print("\n[*] Testing data endpoints...")
    
    endpoints = [
        "/welcome/default/data",
        "/examples/default/data",
        "/welcome/data",
        "/examples/data",
    ]
    
    for ep in endpoints:
        try:
            r = requests.get(f"{TARGET}{ep}", verify=False, timeout=5)
            if r.status_code != 404:
                print(f"[+] {ep}: {r.status_code}")
        except:
            pass

def test_custom_controllers():
    """Find all available controllers"""
    print("\n[*] Testing custom controllers in examples app...")
    
    # From file listing, we know these controllers exist:
    controllers = [
        "ajax_examples",
        "cache_examples",
        "form_examples",
        "global",
        "layout_examples",
        "session_examples",
        "simple_examples",
        "soap_examples",
        "spreadsheet",
        "template_examples",
    ]
    
    for ctrl in controllers:
        for func in ["index", "callback", "test", "data"]:
            try:
                url = f"{TARGET}/examples/{ctrl}/{func}"
                r = requests.get(url, verify=False, timeout=5, allow_redirects=False)
                if r.status_code == 200:
                    print(f"[+] {url}: 200 OK")
            except:
                pass

def test_jsonrpc_methods():
    """Test if JSONRPC endpoints exist and what methods they expose"""
    print("\n[*] Testing JSONRPC2 endpoint...")
    
    try:
        # Try to call a non-existent method to see error
        payload = {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "test_method",
            "params": {}
        }
        
        r = requests.post(
            f"{TARGET}/examples/default/call/jsonrpc2",
            json=payload,
            verify=False,
            timeout=5
        )
        
        print(f"[+] JSONRPC2 endpoint response: {r.status_code}")
        print(f"    {r.text[:300]}")
        
    except Exception as e:
        print(f"[-] Error: {e}")

def test_component_load():
    """Test LOAD component functionality"""
    print("\n[*] Testing LOAD/component endpoints...")
    
    # web2py supports .load extension for AJAX components
    endpoints = [
        "/welcome/default/index.load",
        "/examples/default/index.load",
        "/examples/ajax_examples/index.load",
    ]
    
    for ep in endpoints:
        try:
            r = requests.get(f"{TARGET}{ep}", verify=False, timeout=5)
            if r.status_code == 200:
                print(f"[+] {ep}: 200 OK")
        except:
            pass

if __name__ == "__main__":
    print("="*70)
    print("Web2py Unauthenticated Endpoint Scanner")
    print("="*70)
    
    try:
        r = requests.get(f"{TARGET}/", verify=False, timeout=5)
        print(f"[+] Target reachable: {r.status_code}")
    except Exception as e:
        print(f"[-] Target not reachable: {e}")
        exit(1)
    
    test_generic_views()
    test_special_functions()
    test_call_service()
    test_data_endpoints()
    test_custom_controllers()
    test_jsonrpc_methods()
    test_component_load()
    
    print("\n" + "="*70)
    print("Scan complete")
    print("="*70)
