GitHub 프라이빗 페이지 버그 바운티로 35,000달러 벌기
(robertchen.cc)고3 학생이 Covid로 시간이 남자 버그 바운티 사냥을 해서 GitHub의 pirvate 페이지의 버그바운티로 35,000달러는 받은 이야기.
GitHub의 pirvate 페이지의 버그 바운티로 보고했고 두가지 CTF 보너스가 있었다.
* 10,000달러: 사용자 인터랙션 없이 `flag.private-org.github.io` 에서 플래그를 읽는다. `private-org` 조직 밖에 있는 계정에서 이 플래그를 읽을 수 있으면 5천 달러의 추가 버너스가 있다.
* 5,000달러: 사용자 인터랙션을 통해 `flag.private-org.github.io`에서 플래그를 읽는다.
# 인증 흐름
GitHub pages는 별도의 도메인 `github.io`으로 호스팅되므로 `github.com`의 인증 쿠키는 private pages 서버로 보내지지 않는다. 그러므로 private 페이지 인증은 `github.com`과 추가적인 통합 없이는 사용자의 신원을 알아낼 수 없다. 그러므로 GitHub은 커스텀 인증 흐름을 만들었다.
* private 페이지에 방문했을 때 서버는 `__Host-gh_pages_token` 쿠키의 존재를 검사한다.
* 쿠키가 없거나 올바르지 않으면 private page 서버는 `https://github.com/login`으로 리다이렉트 할 것이다.
* 이 다이렉트는 `__Host-gh_pages_session` 쿠키에 nonce도 설정한다.
* 이 쿠키는 __Host- 쿠키 접두사를 사용하므로 호스트 도메인이 아닌 곳에서 JavaScript로 설정하는 것을 막는다.
* `/login` 은 `/pages/auth?nonce=&page_id=&path=`로 리다이렉트한다.
* 여기서는 `token` 파라미터에서 `https://pages-auth.github.com/redirect`로 전달하는 임시 인증 쿠키를 생성한다.
* `/redirect`는 `https://repo.org.github.io/__/auth`로 포워드한다.
* 이 최종 엔드포인트는 `repo.org.github.io` 도메인에서 인증 쿠키인 `__Host-gh_pages_token`와 `__Host-gh_pages_id`를 설정한다.
* 여기서 이전에 설정한 `__Host-gh_pages_session`의 `nonce`도 검사한다.
오리지널 요청 경로와 페이지 ID는 쿼리 파라미터 `path`, `page_id`에 각각 저장되고 nonce도 `nonce` 파라미터에 저장된다.
# 악용
## CRLF 반환
* 첫 취약점은 `https://repo.org.github.io/__/auth`의 `page_id` 파라미터에서 CRLF 주입이었다.
* `page_id` 파싱이 공백문자를 무시하고 이 값이 `Set-Cookie` 헤더로 바로 설정된다는 것을 알게 되었다.
* 전통적인 CRLF 주입으로 파싱을 깨뜨릴 수는 있지만 다른 영향은 없다.
* `Location:` 헤더가 `Set-Cookie` 헤더 뒤에 붙기 때문에 302 리다이렉트 임에도 Location 헤더가 무시되고 본문이 렌더링된다.
## 공격
* GitHub 엔터프라이즈 코드를 보고 private page 서버가 openresty nginx로 구현되었다는 것을 알게 되었다.
* null 바이트를 추가해서 XSS를 성공했다. 이 null byte는 바디의 시작에 와야하므로 header 주입 공격은 할 수 없다.
* 여기서 private page 도메인에서 임의의 JavaScript 코드를 실행할 수 있게 되었다.
* 이제 남은건 nonce를 건너뛸 방법을 방법을 찾아야 한다.
## nonce 건너뛰기
* 관찰결과 같은 조직의 sibling private 페이지는 서로간에 쿠키를 설정할 수 있다는 것을 발견함.
* `private-org.github.io`에서 설정된 쿠키는 `private-page.private-org.github.io`로 전달된다.
* `__Host-` 접두사 보호를 피할 수 있으면 nonce를 쉽게 건너띌 수 있다.
* 모든 브라우저가 이를 지원하는 건 아니고 IE는 `__Host-` 접두사를 지원하지 않는다.
* 하지만 더 좋은 방법을 찾아보다가 재밌는 아이디어가 생각났다.
* 쿠키가 대소문자를 어떻게 처리하는지 확인해 본 결과 `__HOST`와 `__Host`를 다르게 다루는 것을 알게 되었고 GitHub private pages는 쿠키를 파싱할 때 대문자를 무시한다는 것을 알게 되었다.
* JavaScript로 nonce를 지정할 수 있게 되었다.
* 5천 달러의 보너스를 받게 되었다.
## 캐시 오염
* `/__/auth?` 엔드포인트의 응답은 피싱된 `page_id`의 정수 값으로 캐시된다.
* 이를 통해 XSS 페이로드를 통해서 캐시 오염에 성공하면 상호작용하지 않은 사용자도 영향을 받는다.
* 공격자가 `unprivileged.org.github.io`를 공격해서 인증을 오염시키면 XSS 페이로드가 캐시된다.
* 쿠키가 부모 도메인인 `org.github.io`에서 공유되므로 공격자는 `privileged.org.github.io`도 공격할 수 있다.
## 퍼블릭 private 페이지
* 15,000 달러의 보너스를 받고자 조직에 속하지 않는 사용자가 이 공격을 하도록 해야 했다.
* public 저장소에서 private 페이지를 설정하는 잘못된 설정으로 이 공격을 할 수 있었다.
* private 레포에서 페이지를 마든 뒤에 저장소를 public으로 바꾸는 것을 말한다.
* 이 잘못된 설정의 private 페이지는 모든 사용자에게 인증 흐름을 타게되고 조직 밖의 사용자가 읽기 권한을 갖게 한다.