한 호출자, 두 명의 승인자: 나의 승인 코드에서 발견된 우회 경로
요약
본 기사는 aine-control-plane의 인증 취약점을 분석하고, 한 호출자가 두 명의 승인자 역할을 위장하여 시스템을 우회할 수 있는 경로를 발견한 과정을 다룹니다. 핵심은 `X-AINE-Actor` 헤더가 검증되지 않은 신원을 통해 승인을 결정하는 방식에 있었습니다. 이를 해결하기 위해 레이블링 및 소스 검증 로직이 추가되었습니다.
핵심 포인트
- 단일 호출자가 두 명의 가짜 액터로 위장하여 승인 우회 가능성 발견
- 취약점은 인증되지 않은 헤더를 통해 신원을 결정하는 데서 기인
- 수정 사항으로 `actor_source` 포함 및 소스 검증 로직 강화
The last piece는 수정 사항으로 끝났다. aine-control-plane의 HTTP 경로는 인증 계층에서 오는 필드로 설명했던 부분이 아무것도 검증하지 않는 X-AINE-Actor 헤더에서 온 것이었다. 나는 issue #8을 열고 계획을 세웠다: 먼저 각 액터 값이 어디서 왔는지 레이블링하고, 그 다음 레이블이 불일치하는 행의 수를 계산하는 것이다.
10월 2일에 한 독자 @_firelinks가 해당 게시물 아래에 답글을 달았고 순서를 변경해 달라고 요청했다. 그들의 댓글은 동일한 헤더가 단순히 기록에 어떤 이름이 들어갈지 결정하는 것 이상을 한다는 점을 지적했다. 그것은 또한 누가 승인할 수 있는지를 결정한다는 것이다. 그들은 코드 경로를 추적하여 한 호출자가 두 가지 승인 요구 사항을 어떻게 무효화할 수 있는지 설명했고, 이에 대한 테스트도 제안했다. 그들은 또한 이것이 아직 중요하지 않았던 이유에 대해서도 제안했는데: 서버는 기본적으로 127.0.0.1에 바인딩되며, 이는 팀이 공유하기 위해 프록시 뒤에 놓일 때까지 참으로 유지된다.
그들은 순서에 대해 옳았다. 그들의 댓글은 이미 역할 경로와 쿼럼 경로를 모두 명명하고 있었다. 코드를 확인하면서 추가된 것은 '거부(reject)'가 동일한 방식으로 열렸다는 것이다: 수정되기 전에는 호출자의 모든 헤더 거부가 승인 상태를
정족수(The quorum). 승인 결정이 그 승인 결정들 간의 고유한 actor_id 값의 개수가 required_approvals에 도달하는 순간, 하나의 승인으로 간주됩니다. 이 actor_id는 X-AINE-Actor에서 가져옵니다. 한 호출자가 두 개의 다른 이름으로 두 개의 승인 결정을 보내면, 이는 두 명의 고유한 액터(actor)를 제공한 것입니다.
종합적으로 볼 때, 하나의 프로세스가 두 명의 승인이 필요한 요청을 생성하고, 이를 두 명의 가짜 사람으로 두 번 승인할 수 있으며, 기록에는 두 명의 정족수에 의해 승인된 요청이 표시될 것입니다.
솔직하게 규모를 유지하자면: aine-control-plane은 참고 구현체(reference implementation)이며, 이 시스템의 SECURITY.md 파일에 이미 인증을 제공하지 않으며 소비자가 노출하기 전에 인증된 액터(actor)를 공급해야 한다고 명시되어 있습니다. CVE는 없습니다. 제가 아는 한 이 취약점이 누구에게도 사용된 배포 사례는 없습니다. 결함은 참고 서버가 확인할 수 없는 신원으로부터 승인 결정 자체를 받아들였고, 문서에는 승인이 검증되지 않은 헤더(header)의 신원을 통해 결정될 수 있다는 내용이 없었다는 것입니다.
수정 사항 (The fix)
이는 두 개의 풀 리퀘스트(pull requests)로 진행되었으며, 10월 9일에 병합되었습니다.
PR #9는 이슈 #8의 단계 1과 2를 다룹니다: 레이블(label) 추가와 일일 기여도 카운트(GET /v1/audit/actor-attribution)입니다. 기록에는 이제 actor_source가 포함되며, 참고 HTTP 전송 계층은 이를 header로 설정합니다. 신원을 검증하는 소비자(consumer)는 이를 token과 같이 다른 것으로 설정합니다.
PR #10는 이 레이블을 사용합니다. ApprovalWorkflow.decide()는 이제 무언가를 조회하기 전에 소스(source)를 확인합니다. 만약 소스가 누락되었거나, 비어 있거나, 또는 header라면, 이는 approval_identity_unverified를 반환하고 아무것도 기록하지 않습니다. 이는 거부(reject)에도 마찬가지로 적용됩니다. 코어에서 신원을 검증하고 소스를 설정하는 소비자는 이전과 같이 작동합니다.
PR은 tests/test_approval_identity.py에 네 개의 테스트를 추가하며, 그중 세 개는 실제 HTTP 서버(self-grant, quorum, reject)를 거칩니다. 나머지 하나는 워크플로우를 직접 호출하며, 누락되었거나 header 출처가 거부되는 경우와 검증된 출처가 여전히 작동하는 경우를 다룹니다. 그중 하나는 @_firelinks가 제안한 테스트입니다: required_approvals: 2로 요청을 생성하고, 서로 다른 X-AINE-Actor 값을 가진 두 개의 승인 결정과 승인자 역할을 게시하여 대기 상태를 유지하는지 확인합니다. 나머지 테스트들은 호출자가 스스로를 승인자로 지칭하는 경우와 거부되는 것이 거부되는 경우를 다룹니다. 이 글을 위해 저는 네 가지 테스트를 수정 전 커밋에 대해 실행했는데, 모두 실패했고, 병합된 커밋에 대해서는 모두 통과했습니다.
@_firelinks는 좀 더 완화된 버전을 제안했습니다: 헤더 출처의 결정을 계속 기록하되, 이를 required_approvals 계산에는 포함하지 않는 것입니다. 대신 PR #10은 해당 결정들을 거부하고 아무것도 저장하지 않습니다. 기록된 미검증 결정은 여전히 _status()와 출처별 필터링을 하지 않는 감사 리더에 의해 읽힐 것이며, 이를 필터링하려면 스키마 변경이 필요합니다. 따라서 당분간은 문 앞에서 거부하는 방식입니다.
비용과 아직 해결되지 않은 문제들
비용은 직접적입니다. 참조 서버는 더 이상 자체적으로 승인을 결정할 수 없습니다. 배포된 상태로 이를 실행하는 누구나 승인 요청을 생성하고 읽을 수 있지만, 결정을 내리려면 누가 호출하는지 검증하고 출처를 설정하는 계층이 필요합니다. 저는 이것이 참조 서버가 멈춰야 할 올바른 장소라고 생각합니다. 왜냐하면 참조 서버는 애초에 승인이 요구하는 질문에 답할 수 없었기 때문입니다.
두 개의 풀 리퀘스트 이후로 네 가지 문제가 아직 해결되지 않았습니다.
- PR #10 이전의 결정은 출처를 저장하지 않으므로, 상태 계산 시 여전히 이를 포함합니다. 필터링하려면 스키마 변경이 필요합니다.
- 승인 요청을 생성할 때도 헤더 액터(header actor)가 허용됩니다. 누구나 요청할 수 있지만, 실제로 결정하는 행위만 제한됩니다.
- @_firelinks에서도 요청자가 승인자일 수 있다는 점을 지적했는데, 이는 단일 승인이 필요한 경우 중요합니다. 이 검사는 토큰이 필요하지 않으며 독립적으로 배포될 수 있습니다. 아직 배포되지 않았습니다.
- Issue #8은 열려 있습니다. 각 호출의 토큰 ID를 아이덴티티 프로바이더(identity provider)의 발급 기록과 연결하는 마지막 단계는 실제 토큰 검증을 기다리고 있습니다.
왜 파일럿의 중단 조건이 이것에 의존하는가
최근 작성된 첫 AI 파일럿 선택 관련 글에서 저는 네 가지 중단 조건을 나열했습니다. 세 번째는 다음과 같습니다: "우회 승인(Bypassed approval). 인간의 승인 없이 출력이 전송되거나 실행됨."
이 조건은 승인 기록을 신뢰하여 사람이 승인했는지 여부를 판단할 수 있다고 가정합니다. 하지만 위에서 언급된 우회는 이 조건을 발동시키지 못합니다. 기록에는 두 명의 지정된 승인자와 필요한 역할이 포함되어 승인이 이루어진 것으로 보일 것이며, 모든 필드가 올바르게 보입니다. 이 조건을 확인하는 사람은 막을 만한 것을 찾지 못할 것입니다.
승인에 관한 중단 조건은 시스템이 누가 승인했는지 알려주는 능력만큼만 유효합니다. 이를 신뢰하기 전에, 사용하는 어떤 승인 도구에서도 두 가지를 확인할 수 있습니다:
- 결정 시점에 승인자의 아이덴티티는 어디에서 오는가? 만약 답이 호출자가 보내는 값이라면, 기록은 호출자 자신의 계정이 됩니다.
- 한 명의 호출자가 두 개의 승인을 생성할 수 있는가? 한 세션에서 두 개의 이름으로 시도해 보고 요청이 진행되는지 확인해 보세요.
만약 첫 번째 답변이 "호출자로부터"이거나 두 번째 테스트가 성공한다면, 중단 조건은 자체 승인된 출력을 포착할 수 없습니다(승인 기록 자체가 아예 없는 경우에도 여전히 발동됨). 따라서 파일럿은 해당 조건이 필요하기 전에 승인 전 단계에서 검증된 아이덴티티를 필요로 합니다.
코드에 충분히 주의 깊게 읽어 이 경로를 발견해 준 @_firelinks님과, 계속해서 의견을 제시해 주신 분께 감사드립니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기