Finding a $90,000 Remote Code Execution Vulnerability in Google
구글에서 9만 달러 규모 원격 코드 실행 취약점을 찾다
구글 개발자 문서 사이트의 OAuth 세션 쿠키에서 OAuth client secret과 Flask 서명 키가 함께 노출됐습니다. 공격자는 쿠키를 위조해 서버가 pickle 데이터를 역직렬화하게 만들었고, 여러 DevSite 사이트에서 원격 코드 실행을 입증했습니다.
- 주제
AI 요약
연구자는 구글 개발자 문서 사이트가 공유하는 OAuth 흐름을 살피다 /oauth2authorize가 발급하는 Flask 세션 쿠키를 발견했습니다. 쿠키는 약 1,200바이트로 예상보다 컸고, Flask 세션은 서명만 적용하므로 내용을 읽을 수 있었습니다. 압축을 풀어 확인하자 세션에 OAuth 흐름 객체가 들어 있었고, 객체의 바이트 필드를 Base64로 디코딩하니 pickle 데이터가 나왔습니다. 여기에는 OAuth client_id와 client_secret이 평문으로 포함됐습니다.
노출된 자격 증명으로 쿠키 서명 위조
연구자는 OAuth client_secret을 Flask SECRET_KEY로 사용해 기존 쿠키의 서명을 검증했습니다. Flask가 itsdangerous의 쿠키 세션 키 파생 방식을 사용한다는 점에 따라 cookie-session과 SHA-1로 서명을 다시 계산했고, 결과가 쿠키 서명과 바이트 단위로 일치했습니다. 서명 한 글자를 바꾸면 요청이 거부됐으며, 복구한 키로 원래 세션을 다시 서명하면 받아들여졌습니다. 따라서 공격자는 서버가 신뢰하는 세션 값을 원하는 대로 바꿀 수 있었습니다.
위조 세션에서 원격 코드 실행까지
OAuth 콜백은 세션에서 google_oauth2_flow_<csrf> 항목을 읽고, 바이트 데이터를 디코딩한 뒤 pickle.loads(...)로 역직렬화했습니다. 공격자는 자신이 정한 CSRF 토큰과 그에 맞는 흐름 항목을 쿠키에 넣을 수 있었습니다. 복구한 서명 키로 쿠키를 서명하면 서버가 공격자 제공 pickle을 역직렬화합니다. Python pickle의 REDUCE opcode는 지정한 호출 가능 객체를 인자로 실행하므로, 임의 코드 실행으로 이어집니다.
연구자는 운영 서버에서 명령을 실행하지 않고 time.sleep(N)을 호출하는 pickle로 동작을 확인했습니다. 0초, 3초, 5초, 8초를 지정했을 때 응답 시간은 각각 0.207초, 3.193초, 5.194초, 8.283초였습니다. 대조 시험에서도 쿠키가 없거나 서명이 틀리면 요청이 거부됐고, 유효한 서명만으로 흐름 항목이 없을 때도 상태 오류가 났습니다. 실제 흐름을 담은 쿠키는 토큰 교환 단계까지 도달했으며, 가짜 인증 코드 때문에 invalid_grant로 실패했습니다. sleep 페이로드를 넣은 쿠키만 지정한 시간만큼 지연됐습니다.
영향을 받은 사이트와 수정 사항
문제는 단일 앱이 아니라 공유 DevSite 플랫폼에 있었습니다. developers.google.com에서 얻은 키로 firebase.google.com, developer.android.com, source.android.com, docs.partner.android.com의 세션에도 서명할 수 있었습니다. 연구자는 노출된 OAuth client secret을 교체하고 해당 키로 서명된 세션을 무효화할 것을 권고했습니다. 배포 환경마다 서버 측 저장소에서 별도의 고엔트로피 Flask SECRET_KEY를 가져오고 OAuth 자격 증명과 분리해야 합니다. OAuth 흐름 객체는 서버에 보관하고 쿠키에는 불투명한 참조만 넣어야 하며, 클라이언트 경계를 넘는 데이터에는 pickle 대신 스키마 검증을 적용하는 데이터 전용 형식을 써야 합니다.
구글은 취약점을 수정했고, 연구자는 일반 구글 애플리케이션 범주(T2)의 S0 원격 코드 실행 보상으로 9만 달러를 받았습니다. 기본 보상 7만 5천 달러에 보고서 품질 가산율 1.2를 적용한 금액입니다.
Lobsters 반응
- @neilmadden — 좋은 버그네요! 예전에는 구글이라면 훨씬 잘했을 거라고 생각했을 텐데요. 안타깝지만 이제는 그렇지 않네요.
원문: Simon Koeck / 번역·요약: Trawling