콘텐츠로 이동

MemOS 내부 인증 우회

MemOS 내부 인증 우회 thumbnail
CVE-2026-75110은 MemOS 인증 미들웨어가 외부 요청을 내부 서비스 요청으로 잘못 판정하는 pre-auth 인증 우회 취약점이다. AUTH_ENABLED=true여도 INTERNAL_SERVICE_SECRET이 설정되지 않았다면, X-Internal-Service 헤더를 생략한 요청이 all scope를 얻는다.

요약

CVE-2026-75110은 MemOS 인증 미들웨어가 외부 요청을 내부 서비스 요청으로 잘못 판정하는 pre-auth 인증 우회 취약점이다. AUTH_ENABLED=true여도 INTERNAL_SERVICE_SECRET이 설정되지 않았다면, X-Internal-Service 헤더를 생략한 요청이 all scope를 얻는다.

NVD 설명에 따르면 공격자는 이 권한으로 보호된 데이터 endpoint와 관리자 API key 관리 기능에 접근할 수 있다. API key 생성·열거·폐기와 master key 생성도 영향 범위에 포함된다. CVE 레코드는 2026-08-17 NVD 데이터셋에 수신됐다.

항목 내용
CVE CVE-2026-75110
취약점 유형 잘못된 비교로 인한 인증 우회
공격 조건 MemOS API에 대한 네트워크 접근
취약한 구성 AUTH_ENABLED=true, INTERNAL_SERVICE_SECRET 미설정
트리거 X-Internal-Service 헤더 생략
획득 권한 내부 principal, all scope
영향 데이터 endpoint 접근, API key 관리, master key 생성
공개 기록 날짜 2026-08-17

인증 활성화만으로 차단되지 않는다

AUTH_ENABLED=true여도 내부 서비스 secret이 비어 있으면 취약 조건이 성립한다. 인증 활성화 여부와 INTERNAL_SERVICE_SECRET의 런타임 설정 상태를 함께 검사해야 한다.

취약 지점

문제는 src/memos/api/middleware/auth.pyis_internal_request()에 있다. 함수는 먼저 요청의 source host가 내부 주소 목록에 포함되는지 검사한 뒤, 요청 헤더와 환경 변수를 직접 비교한다.

def is_internal_request(request: Request) -> bool:
    client_host = request.client.host if request.client else None

    if client_host in INTERNAL_SERVICE_IPS:
        return True

    internal_header = request.headers.get("X-Internal-Service")
    return internal_header == os.getenv("INTERNAL_SERVICE_SECRET")

INTERNAL_SERVICE_SECRET에는 기본값이 없다. 환경 변수가 설정되지 않으면 os.getenv()None을 반환한다. 요청에서 X-Internal-Service 헤더를 생략해도 request.headers.get()의 결과가 None이다.

None == None  # True

이 비교 결과만으로 is_internal_request()True를 반환한다. 이후 verify_api_key()는 일반 API key 검증을 건너뛰고 다음 인증 context를 생성한다.

{
    "user_name": "internal",
    "scopes": ["all"],
    "is_master_key": False,
    "is_internal": True,
}

권한 검사는 require_scope()에서 수행된다. 여기서는 all이 있으면 요구된 scope와 관계없이 요청을 허용한다.

if "all" in scopes or required_scope in scopes:
    return auth

따라서 exploit primitive는 secret 추측이나 API key 위조가 아니다. 내부 인증 헤더를 생략해 내부 principal과 all scope를 획득하는 것이다. 정상적인 API key 형식 검사, 데이터베이스 조회, 만료 여부 검사는 이 분기 뒤에 있어 실행되지 않는다.

요청 처리 흐름

  1. 공격자가 AUTH_ENABLED=true인 MemOS API에 요청한다.
  2. AuthorizationX-Internal-Service 헤더를 모두 생략한다.
  3. is_internal_request()가 누락된 헤더와 미설정 환경 변수를 비교한다.
  4. None == None이 참이 되면서 요청이 내부 서비스로 분류된다.
  5. verify_api_key()internal principal과 all scope를 반환한다.
  6. require_read, require_write, require_admin이 적용된 endpoint를 통과할 수 있다.

요청 형태는 다음과 같이 요약할 수 있다. 실제 경로는 배포 중인 MemOS 라우팅 구성을 기준으로 확인해야 한다.

GET /<protected-endpoint> HTTP/1.1
Host: memos.example
Accept: application/json

이 요청에는 AuthorizationX-Internal-Service가 없다. 취약한 구성에서는 헤더의 부재 자체가 우회 조건이 된다.

NVD는 관리자 API key 관리 endpoint를 통해 임의 사용자의 API key를 생성·열거·폐기하고 master key를 생성할 수 있다고 설명한다. 생성된 key는 최초 우회 이후에도 권한 접근을 이어가는 수단이 될 수 있다. 보호된 데이터 endpoint도 all scope의 영향을 받는다.

영향 대상 식별

영향 여부는 버전 문자열만으로 결정하기 어렵다. 다음 조건을 모두 만족하는 배포가 우선 점검 대상이다.

  • AUTH_ENABLED=true
  • INTERNAL_SERVICE_SECRET이 런타임 환경에 없음
  • 취약한 is_internal_request() 비교 로직 사용
  • MemOS API가 비신뢰 네트워크에서 접근 가능

저장소와 배포 설정은 다음과 같이 검색할 수 있다.

rg -l 'AUTH_ENABLED\s*[:=]\s*["'']?true' .
rg -l 'INTERNAL_SERVICE_SECRET' .
rg -n 'X-Internal-Service|is_internal_request' src

정적 설정에 변수가 없더라도 Kubernetes Secret이나 외부 secret manager가 런타임에 주입할 수 있다. secret 값 자체를 출력하지 말고 컨테이너 내부에서 변수의 존재 여부만 검사한다.

if (Test-Path Env:INTERNAL_SERVICE_SECRET) {
    "INTERNAL_SERVICE_SECRET is set"
} else {
    "INTERNAL_SERVICE_SECRET is missing"
}

빈 문자열도 안전한 설정으로 취급하면 안 된다.

if ([string]::IsNullOrEmpty($env:INTERNAL_SERVICE_SECRET)) {
    "INTERNAL_SERVICE_SECRET is missing or empty"
}

수정 원칙

서버 측 secret이 없으면 내부 요청 인증을 수행하지 않는 fail-closed 동작이 필요하다. 요청 헤더도 명시적으로 존재해야 한다.

import secrets

def is_internal_request(request: Request) -> bool:
    client_host = request.client.host if request.client else None

    if client_host in INTERNAL_SERVICE_IPS:
        return True

    internal_secret = os.getenv("INTERNAL_SERVICE_SECRET")
    internal_header = request.headers.get("X-Internal-Service")

    if not internal_secret or not internal_header:
        return False

    return secrets.compare_digest(internal_header, internal_secret)

내부 헤더 인증이 필수인 배포라면 애플리케이션 시작 단계에서 secret 부재를 오류로 처리하는 방식이 더 명확하다.

INTERNAL_SERVICE_SECRET = os.getenv("INTERNAL_SERVICE_SECRET")

if AUTH_ENABLED and not INTERNAL_SERVICE_SECRET:
    raise RuntimeError(
        "INTERNAL_SERVICE_SECRET must be configured when authentication is enabled"
    )

이 코드는 수정 원칙을 나타낸다. 수집된 자료에서는 upstream 수정 commit이나 안전한 버전, 공식 patch diff가 확인되지 않았다. 배포 중인 auth.py에서 누락된 값끼리 직접 비교하는 로직이 제거됐는지 확인해야 한다.

reverse proxy나 API gateway에서는 외부 클라이언트가 내부 인증 헤더를 주입하지 못하도록 제거한다.

proxy_set_header X-Internal-Service "";

내부 서비스가 같은 gateway를 사용한다면 별도의 내부 listener나 신뢰된 경로에서만 헤더를 다시 설정해야 한다.

대응

  • 비신뢰 네트워크에서 MemOS API로 들어오는 접근을 제한한다.
  • AUTH_ENABLED=true인 인스턴스의 INTERNAL_SERVICE_SECRET 설정 여부를 런타임에서 확인한다.
  • 외부 요청의 X-Internal-Service 헤더를 gateway에서 제거한다.
  • is_internal_request()가 누락된 두 값을 비교하지 않도록 수정한다.
  • API key 생성·열거·폐기 기록과 master key 생성 기록을 점검한다.
  • 운영 변경과 연결되지 않는 key를 폐기하고 관련 secret과 key를 교체한다.
  • 인증 헤더 없이 성공한 보호 endpoint 요청을 조사한다.

탐지 포인트

네트워크

  • 외부 source IP에서 MemOS 관리자 또는 데이터 endpoint로 들어온 요청
  • AuthorizationX-Internal-Service가 모두 없는데 성공 상태가 반환된 요청
  • 한 세션에서 여러 사용자와 관련된 API key 작업이 연속으로 발생한 요청
  • 짧은 시간에 보호된 메모리 데이터를 대량 조회한 요청

header 값은 저장하지 않고 존재 여부만 구조화해 기록한다.

source_ip
request_path
http_method
status_code
authorization_present
internal_service_header_present
authenticated_principal
granted_scopes

엔드포인트

  • API key 생성·열거·폐기 기능에 대한 비정상 접근
  • master key 생성 기능 호출
  • 인증 실패 기록 없이 성공한 보호 데이터 요청
  • internal principal이 외부 source IP와 함께 기록된 요청
  • all scope가 일반 외부 요청에 부여된 기록

실제 endpoint 경로는 배포된 라우터에서 관리자 scope 의존성을 검색해 식별할 수 있다.

rg -n 'require_admin|require_scope\("admin"\)|/admin/|api.?key|master.?key' src

서버

  • AUTH_ENABLED=true와 비어 있는 INTERNAL_SERVICE_SECRET의 조합
  • is_internal_request()에서 요청 헤더와 os.getenv()를 직접 비교하는 코드
  • 외부 요청이 user_name: internal 또는 is_internal: true로 처리된 로그
  • 운영 작업과 연결되지 않는 API key 또는 master key 생성 기록
  • gateway가 외부의 X-Internal-Service 헤더를 그대로 upstream으로 전달하는 구성

애플리케이션의 현재 로깅은 내부 요청의 source host를 debug 수준으로 남긴다. 운영 환경에서 debug 로그를 사용하지 않는다면 principal, scope, endpoint, source IP를 별도 audit log로 기록해야 한다.

참고 자료


Wide editorial cybersecurity blog thumbnail, 16:9 composition, a stylized AI memory service node connected to a protected API gateway, one malformed request bypassing an open authentication checkpoint and reaching glowing memory blocks and API key symbols, dark navy and graphite environment with restrained cyan and amber accents, clean technical diagram aesthetics, subtle depth, polished magazine-quality lighting, no corporate report styling. Include only the small English label “AI SECURITY” and identifier “CVE-2026-75110”, both placed well inside the safe area with generous margins. No other visible text, no non-Latin characters, no oversized title block.