연결 하나가 다른 회사 데이터로 이어졌다: 클라우드 권한 검사가 놓친 경계

내 연결의 권한 검사를 통과한 요청이 다른 조직의 비밀 저장소와 데이터베이스로 이어졌습니다. Azure 연결 기능에서 발견돼 수정된 다섯 취약점을 통해 입력 차단과 최종 대상의 권한 확인이 어떻게 다른지 살펴봅니다.

자기 연결에서 승인된 작업이 경계 너머 다른 회사 자료를 가져오는 설명용 장면. Azure 공식 심볼.

업무 자동화에 연결해 둔 비밀 저장소와 데이터베이스를 다른 회사의 작업 흐름에서 불러올 수 있었다. 노르웨이 보안업체 Binary Security의 연구자 호콘 비크 굴브란스루드는 10월 2일 공개한 글에서, 마이크로소프트 Azure의 서비스 연결 기능에서 조직 간 경계를 넘는 중대 권한 상승 취약점 다섯 건을 발견했다고 밝혔다. 보고한 취약점은 모두 수정됐으며, 보상금은 합계 20만 달러였다.

이 사례는 고객 데이터 유출 사고를 발표한 보고서가 아니라 취약점 발견과 수정 과정을 설명한 연구다. 눈여겨볼 부분은 다섯 번의 성공보다 그 사이에 있었던 수정이다. 한 입력을 막자 비슷한 기능의 다른 호출에서 문제가 나타났고, 주소 검사를 보강하자 주소를 해석하는 순서가 새로운 우회 지점이 됐다.

내 계정의 연결을 확인했는데 왜 다른 회사 자료가 나왔나

Azure Logic Apps는 여러 서비스를 이어 업무를 자동으로 처리하는 도구다. 예를 들어 비밀 저장소인 Key Vault에서 값을 읽거나, Azure SQL 데이터베이스에 기록을 남기는 작업을 묶을 수 있다. 이때 서비스 접속 정보와 인증 수단을 보관하는 리소스가 API Connection이다. 여러 고객 조직이 함께 쓰는 클라우드에서는 각 조직의 자원과 권한을 나눈 구역을 테넌트라고 부른다.

연결을 실행할 때는 중간 서비스인 Azure API Management가 요청을 받고, 저장된 연결 정보와 자격증명을 사용해 목적지 서비스를 호출한다. Microsoft의 공식 문서는 이 구조를 연결의 인증 정보 저장소와 실제 대상 서비스로 나눠 설명한다. 요청한 사용자가 누구인지 확인하는 일과, 그 요청에 어느 연결의 권한을 붙일지 정하는 일은 서로 다른 단계다.

연구자는 자기 연결을 향한 관리 API 요청의 경로를 바꿔 다른 테넌트의 연결을 사용했다. 그러면 중간 서비스가 피해 연결에 저장된 권한으로 대상 서비스에 접근했다. 공개된 재현에는 다른 테넌트의 Key Vault 값을 읽는 예와 SQL 데이터베이스에 데이터를 쓰는 예가 포함돼 있다. 접근 가능한 범위는 해당 연결에 부여된 대상 서비스 권한에 달려 있다.

막힌 호출 옆에 같은 일을 하는 호출이 남았다

첫 경로 조작이 신고된 뒤에는 특정 경로를 허용하지 않는 오류가 반환됐다. 연구자가 살펴본 코드에는 상위 경로로 이동하는 표현을 검사하는 부분이 있었다. 하지만 조사 중 같은 위치에서 비슷한 역할을 하는 다른 함수들도 발견했다. 처음 수정한 DynamicInvoke와 별도로 DynamicList를 사용하자 다른 테넌트의 SQL에 쓰기 작업을 수행할 수 있었다. 이후 관련 엔드포인트들은 차단됐다.

이 단계에서 드러난 문제는 한 주소의 이상한 문자열을 걸러 내는 것으로는 같은 권한을 쓰는 모든 진입점을 보호할 수 없다는 것이다. 개발팀이 확인해야 할 범위도 실패한 요청 하나에 머물지 않는다. 같은 중간 서비스를 거쳐 같은 자격증명을 사용하는 호출들이 함께 검증 대상이 된다.

연결 생성 방식의 차이가 새 진입점이 됐다

연구는 Logic Apps의 두 실행 방식으로 이어졌다. 공유 환경에서 실행하는 Consumption 방식의 V1 연결과, 전용 호스트를 사용하는 Standard 방식의 V2 연결은 생성 흐름이 달랐다. 연구자의 비교도에서 V2에는 특정 Logic App이 연결을 사용할 수 있도록 승인하는 단계가 있지만 V1에는 그 단계가 없다.

V1과 V2 연결 생성 절차 비교. V2에는 연결 생성과 로그인 뒤 Logic App 사용 승인 단계가 있고 V1 비교 흐름에는 없다.
원문 기반 한글 번역. 출처: Binary Security의 V1·V2 연결 생성 비교도

여기서 구분해야 할 것은 ‘연결을 사용할 권한’과 ‘연결된 대상 서비스에 접근할 권한’이다. 공식 문서도 Standard의 관리형 연결에서 내부 토큰 저장소 인증과 대상 자원 인증을 구분한다. Consumption에서도 관리 ID로 대상 자원에 인증할 수 있으므로, 이번 비교를 ‘Consumption에는 관리 ID가 없다’는 뜻으로 읽으면 잘못이다.

보통 다른 회사 연결을 직접 지정하면 소유권 검사가 막는다. 그러나 연구자는 코드 보기에서 실행 주소를 덮어쓰는 host.api.RuntimeUrl 입력을 찾았다. 참조한 연결의 소유권 검사를 통과한 뒤, 실제 요청은 다른 연결의 실행 주소로 보낼 수 있었다. 자신에게 허용된 연결을 검사했다는 사실이 최종 목적지의 허가까지 보장하지 못한 것이다.

연구자가 공개한 Logic Apps 실행 화면과 응답 영역. 응답 목록에 victimsecret 이름이 나타난다.
출처: Binary Security

검사한 주소와 실행한 주소가 같아야 한다

다음 수정은 실행 주소가 참조한 연결의 예상 주소와 일치하는지 확인했다. 연구자는 여기서도 우회했다. 검사가 끝난 뒤에 경로의 상위 이동 같은 표현을 정리하는 정규화가 이뤄졌기 때문이다. 검사 시점에는 자기 연결로 보였던 주소를, 뒤쪽 서비스는 다른 연결로 해석했다.

보고된 문제는 주소를 검사한 뒤 정규화해 목적지가 달라진 것이다. 방어 설계는 주소를 최종 해석한 뒤 그 대상의 권한을 확인하고 같은 대상으로 실행해야 한다.
출처: Binary Security

경로 우회를 수정하는 과정에서는 앞서 차단했던 원래의 실행 주소 덮어쓰기가 다시 가능해지는 회귀도 있었다고 연구자는 설명한다. 마지막에는 보고한 취약점들이 모두 수정됐다고 밝혔다. 공개 글의 공격 예시를 현재도 그대로 작동하는 방법으로 취급해서는 안 된다.

연결형 자동화를 운영하는 기업이라면 연결마다 어느 계정과 어떤 범위의 권한을 보관하는지부터 확인할 수 있다. 조회만 필요한 작업에 쓰기 권한까지 줄 이유는 없고, 한 업무의 연결에 다른 업무의 데이터 접근 권한을 함께 둘 필요도 없다. 이는 서비스 자체의 격리 결함을 고치는 조치는 아니지만, 연결 하나가 잘못 사용됐을 때 닿는 데이터와 동작의 범위를 줄인다.

플랫폼을 만드는 쪽의 과제는 최종적으로 해석한 자원에 대해 권한을 확인하고, 실행 단계에서도 그 자원과 권한을 유지하는 것이다. 수정 검증에는 최초 신고 경로뿐 아니라 같은 기능의 다른 호출, 주소를 정리하는 순서, 이전에 막았던 요청의 재실행까지 포함해야 한다.


출처: Binary Security, Azure’s Weakest Link: Five Full Cross-Tenant Compromises · Microsoft, Connectors architecture · Microsoft, 관리 ID를 사용한 연결 인증