10P by outsideris 2021-04-07 | favorite | 댓글과 토론

고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 페이지는 모든 사용자에게 인증 흐름을 타게되고 조직 밖의 사용자가 읽기 권한을 갖게 한다.