Go 인프라 악용
(reverse.put.as)- Go의 checksum database와 public module proxy는 Go 코드가 없는 저장소까지 받아들일 수 있어, 임의 Git 저장소 데이터를 Go 인프라에 적재하고 다시 내려받는 경로가 생김
sum.golang.org/lookup/$module@$version요청은 기록되지 않은 모듈 버전을 만나면 원본 서버에서 가져오며, 이 과정에서proxy.golang.org에도 해당 저장소 zip이 제공됨- Homebrew의 Ruby 저장소와 Rust 저장소 fork가 checksum database에 있었고, 새 Go 모듈뿐 아니라 Go 파일이 없는 저장소도 pseudo-version으로 등록되는 실험이 성공함
- 모듈 zip에는 압축·비압축 모두 최대 500 MiB 제한이 있지만, 개발자 머신·CI/CD의 다운로드 제한 우회, 페이로드 저장, C2 구현에는 충분히 큰 규모임
sum.golang.org의 고유 경로 약 159만 개 중 GitHub 경로가 약 151만 개로 95% 수준이며, Go 생태계의 GitHub 의존도와 public proxy 악용 가능성이 함께 드러남
Go checksum database에서 발견된 비-Go 저장소
- Go의 checksum database를 살펴보던 중
modules테이블에서github.com/homebrew/homebrew-core가 매우 많이 나타남github.com/homebrew/homebrew-core: 39,438개github.com/Homebrew/homebrew-core: 30,896개github.com/concourse/concourse: 25,372개github.com/openshift/release: 24,065개github.com/cilium/cilium: 22,138개
- Homebrew 저장소는 Ruby를 사용하는 것으로 알려져 있고, 저장소와 클론한 파일에서도
go.mod나 Go 소스 파일이 발견되지 않음 - 대소문자 차이는 Go 문서의 case encoding 규칙으로 설명됨
- 대문자는
!와 해당 소문자로 인코딩돼, 대소문자를 구분하지 않는 파일 시스템에서도example.com/M과example.com/m을 함께 저장할 수 있음
- 대문자는
github.com/Edu4rdSHL/rust-headless-chrome도 Go와 관련 없는 Rust 저장소 fork인데 checksum database에 나타남
/lookup이 저장소를 가져오는 방식
- Go 모듈 문서의 Go Modules Reference에 따르면, Go 명령은 checksum database를 조회할 때 먼저
/lookup엔드포인트에서 레코드 데이터를 가져옴 - 모듈 버전이 아직 로그에 기록되지 않았다면 checksum database가 응답 전에 원본 서버에서 해당 모듈을 가져오려 시도함
- 엔드포인트 형식은
$base/lookup/$module@$version임$module의$version에 대한 로그 레코드 번호,go.sum라인, 서명된 트리 설명을 반환함
github.com/homebrew/homebrew-core의 pseudo-version을 조회하면 checksum record와go.mod해시가 반환됨- 저장소에 버전 태그가 없으면 Go의 pseudo-version 규칙이 사용됨
새 Go 모듈 등록 실험
- 새 Go 모듈
github.com/gdbinit/fluxmatter를 만든 뒤lookup요청으로 등록 여부를 확인함 @latest조회는 canonical version이 아니라는 오류를 반환함bad request: version "latest" is not canonical
@v0.0.0조회는 해당 revision을 모른다는 오류를 반환함not found: ... invalid version: unknown revision v0.0.0
- 하지만 checksum database를 다시 동기화해 조회하자 모듈이 등록돼 있었음
github.com/gdbinit/fluxmatter|v0.0.0-20240524163826-a7e64ffd69f2|2024-05-24T16:40:51.203837Z
proxy.golang.org/github.com/gdbinit/fluxmatter/@latest는 pseudo-version과 GitHub 원본 정보를 반환했고, 해당 버전의 zip 파일도 다운로드 및 압축 검증이 가능했음- 초기 시딩에는 정확한 버전을 지정하지 않아도, 모듈 경로와 버전처럼 보이는 값을 담은 lookup 쿼리만으로 동작함
Go 코드가 없는 저장소도 public proxy에 적재됨
- Go 코드가 전혀 없는
github.com/gdbinit/readmem저장소로도 같은 실험이 성공함 lookup요청은v0.0.0revision을 모른다는 오류를 반환했지만, checksum database에는 pseudo-version으로 등록됨github.com/gdbinit/readmem|v0.0.0-20131006075740-407cb0a56933|2024-05-24T16:45:35.88456Z
proxy.golang.org의@latest는 해당 저장소의 pseudo-version과 Git 원본 정보를 반환함- 내려받은 zip에는 Go 파일이 아니라
Entitlements.plist,README, Xcode project 파일,main.c등이 들어 있었음 - 이 실험은 GitHub 저장소를 사용했지만, 동작하는 VCS라면 다른 호스팅 사이트에서도 가능할 수 있음
GitHub 의존도와 크기 제한
- checksum database의 고유 경로 수는 1,591,375개였고, 그중
github.com%경로는 1,515,957개였음 - 고유 경로의 약 95% 가 GitHub에 호스팅됨 {p:95}
- 이 수치는 fork나 실제 Go 코드가 아닌 대상을 제거하지 않은 원시 통계임
- Go 모듈 zip에는 File path and size constraints가 적용됨
- module zip 파일은 최대 500 MiB
- 파일의 총 비압축 크기도 최대 500 MiB
go.mod파일은 최대 16 MiBLICENSE파일도 최대 16 MiB
- 이 제한은 사용자, proxy, 모듈 생태계의 다른 부분에 대한 서비스 거부 공격을 완화하기 위한 장치임
- 500 MiB는 악용 가능성을 고려하면 충분히 큰 크기임
가능한 악용 시나리오
- public Go proxy는 개발자 머신이나 CI/CD 서버에서 목적지 다운로드 제한을 우회하는 데 쓰일 수 있음
- private
GOPROXY가 없는 상황을 전제로 함 - 악성코드는 페이로드를 저장소에 올린 뒤 필요할 때 proxy에서 내려받을 수 있음
- 원본 소스가 사라져도 checksum database entry에는 작은 흔적만 남을 수 있음
- private
proxy.golang.org에 대한 DoS는 실행이 어려울 수 있음- 임의 Git 저장소를 proxy가 다운로드하도록 요청할 수 있음
- 가능한 공격은 GitHub URL을 많이 수집한 뒤
lookupAPI에 많은 요청을 보내는 방식임 - 서버 구현을 알 수 없지만 work queue 같은 병렬 처리 제한이 있을 수 있음
- GitHub 측 대역폭 보호가 작동할 가능성도 있음
- 저장 공간을 대상으로 한 DoS 가능성도 있으나 추정에 그침
- C2(command and control) 는
proxy.golang.org위에 쉽게 만들 수 있음@latest쿼리로 특정 모듈의 최신 버전을 찾을 수 있음- 페이로드는 단순 파일일 수도 있고,
go.mod나 Go 소스 파일 안에 숨길 수도 있음 - 단일 저장소 사용을 피하려면 module DGA를 쓸 수 있음
C2 다운로드 흐름
- implant가 명령을 받으려면 다음 단계를 수행하면 됨
https://proxy.golang.org/module_path/@latest요청- JSON 결과에서 pseudo-version 또는 version 추출
https://proxy.golang.org/module_path/@v/version.zip요청으로 zip 다운로드- zip 내용을 풀고 명령을 파싱
- 이 흐름은 Go 코드 300줄 이하로도 구현할 수 있을 정도로 단순함
결론과 남은 질문
- Go checksum database와 proxy는 문서화된 절차에 따라, 기록되지 않은 모듈을 원본 서버에서 가져와 저장할 수 있음
- 현재 상태가 Go 인프라의 심각한 문제로 보이지는 않지만, 쉽게 악용될 수 있고 개선 여지도 있음
- Go 코드가 아닌 저장소가 proxy와 checksum database에 올라갈 수 있도록 둔 문서화된 이유나 비공개 이유가 있을 수 있음
- 이미 누군가 이를 악용하고 있는지 확인하려면 약 160만 개 고유 저장소와 최신 로컬 database 기준 약 2,200만 개 entry를 조사해야 함
- 일부 유효한 non-Go 프로젝트가 왜 database에 있는지는 아직 열린 질문으로 남아 있음
댓글과 토론
Hacker News 의견들
-
사용자가 자료를 올리고 그 자료가 공개로 보이는 온라인 서비스는 결국 명령제어, 저작권 침해, CSAM 호스팅에 쓰이게 됨
파일 호스팅 외에도 중요한 용도가 있어 차단하기 어려운 서비스일수록 특히 그렇고, Twitter[1], Telegram[2], PGP 키 인프라[3]에서도 이미 벌어진 일이며 GitHub 같은 뻔한 대상은 말할 것도 없음
[1] https://pentestlab.blog/2017/09/26/command-and-control-twitt...
[2] https://www.blazeinfosec.com/post/leveraging-telegram-as-a-c...
[3] https://torrentfreak.com/openpgp-keyservers-now-store-irremo...- Gmail, Google Groups, Google Drive, Gchat도 마찬가지였고, 저장한 데이터가 공개일 필요조차 없었음
Gmail의 경우 자격 증명을 배포해서 로그인하게 한 뒤, IMAP으로 올려둔 첨부파일을 읽게 했음
전 Google SAD-SRE(Spam, Abuse, Delivery)였음 - PyPI도 패키지에 임의의 비 Python 파일을 넣을 수 있으니 이런 용도로 쓰기 쉬워 보임
Python 코드 문자열 안에 파일을 Base64로 인코딩해 넣는 식도 가능함 - 이미 있었는지는 모르겠지만, 덜 obvious한 대상은 HuggingFace로 보임
- Gmail, Google Groups, Google Drive, Gchat도 마찬가지였고, 저장한 데이터가 공개일 필요조차 없었음
-
Googler이고 개인 의견이며, 이 분야는 잘 모름
Go 팀이 GCP와 Drive 쪽과 협업했기를 바라는데, 악성 파일 호스팅은 Google이 늘 다루는 문제이기 때문임
사람들이 임의 데이터를 넣도록 Google이 이미 허용하는 다른 엔드포인트와 크게 다르지 않음- 전 Googler이고 Go Dev Tools 팀은 잘 모르지만, Google은 내가 일했거나 가까운 친구들에게 들은 거대 기업들 중 이런 식의 사내 협업을 거의 가장 잘하는 편임
Google은 중앙 팀이 인프라를 관리하고 전사에 공유하는 데 매우 능숙함. 메신저 앱만 아니면 그렇고, 순전히 추측이지만 Go 팀은 내부 blob 저장소를 쓰고 있을 것이며 악용 대응과 파일 스캔을 자동 처리하는 내부 인프라 팀도 있을 것 같음
- 전 Googler이고 Go Dev Tools 팀은 잘 모르지만, Google은 내가 일했거나 가까운 친구들에게 들은 거대 기업들 중 이런 식의 사내 협업을 거의 가장 잘하는 편임
-
PyPI에도 비 Python 프로젝트가 꽤 있음
Python은 사용자가 라이브러리 코드를 컴파일하지 못할 수 있으니 컴파일된 바이너리인 wheel 배포 능력이 필요함
그런 코드는 C로 많이 쓰지만 Golang[1]도 가능하고, 예시는 못 찾겠지만 라이브러리가 아니라 애플리케이션 배포에도 쓰인 걸 본 것 같음
C로 앱을 작성해 PyPI에 올리고 사용자에게pip install로 설치하라고 하는 건 꽤 멋짐
[1] https://github.com/popatam/gopy_build_wheel_example- 가령 Python 사용 요건을 넣더라도, 악의적으로 맞추려는 사람은 최소한의 Python 스텁 코드만 제공하면 되지 않나 싶음
Linux인데ls가 Python으로 쓰인 셈이니, 이런 게임은 안 하는 편이 나을 것 같음 pip가 패키지를 내려받으려면 venv/virtualenv/pipenv/pyenv 같은 환경 안에 있어야 한다고 요구하기 전에는 이 용도가 훨씬 유용했을 것 같음pip install cmake도 있고, 독점 바이너리도pip install nvidia-cudnn-cu12처럼 가능함- 최근 PyPI를 FFmpeg와 Eigen 같은 비 Python 도구에 많이 쓰고 있음
덕분에 Homebrew를 완전히 버릴 수 있었던 이유 중 하나임
- 가령 Python 사용 요건을 넣더라도, 악의적으로 맞추려는 사람은 최소한의 Python 스텁 코드만 제공하면 되지 않나 싶음
-
순진한 생각일 수도 있지만, 이게 GitHub 저장소에 파일을 올리는 것과 어떻게 다른지 모르겠음
차이가 GitHub는 계정을 만들어야 한다는 점뿐인가? GitHub에도 임의 데이터를 저장할 수 있고, 500MB 제한도 없음- GitHub는 익명 요청에 꽤 엄격한 요청 제한이 있음
-
CUE의 모듈 시스템이 드디어 출시되고 있고, MVS는 Go와 비슷하지만 OCI 인프라 위에 만들어졌음
의존성 관리 시스템에 관심이 있다면 참고할 링크들임
proposal: https://github.com/cue-lang/proposal/tree/main/designs/modul...
custom registry: https://cuelang.org/docs/tutorial/working-with-a-custom-modu...
road map: https://github.com/orgs/cue-lang/projects/10/views/8
0.9.0-alpha-5부터 모듈이 기본 활성화됨: https://github.com/cue-lang/cue/releases/tag/v0.9.0-alpha.5
Go Sum에서는 Trillian 프로젝트가 투명성 로그를 뒷받침함: https://github.com/google/trillian
CUE는 증명(attestation) 같은 OCI 옵션에 얹혀 가려는 계획임- 그게 링크된 글과 무슨 관련이 있는지 모르겠음
-
golang proxy와 sumdb에 편승, 즉 남용해서 임의 URL의 체크섬에 대한 무료 투명성 로그를 만드는 아이디어를 가지고 놀아봤음
https://getsum.pub/- 좀 복잡해 보임
공개 투명성 로그만 원한다면 sigstore 프로젝트의 공개 rekor 인스턴스가 훨씬 더 적합함
https://www.sigstore.dev/
https://docs.sigstore.dev/logging/overview/
- 좀 복잡해 보임
-
내가 멍청한 건지도 모르겠지만, 여기서 정확히 뭐가 문제인지 모르겠음
프록시가 비 Go 저장소를 캐시하는 건 조금 낭비일 수 있지만, 그걸 안 하더라도 Go 저장소를 캐시하게 만들면 어차피 임의 데이터를 저장하게 할 수 있지 않나?
뭔가 놓친 게 아니라면 완전히 별일 아닌 것처럼 들림- 놓친 건 없는 것 같음
여기서 새 소식은 보안 없는 공개 프록시가 프록시할 것을 받아서 보안 없는 방식으로 공개 제공한다는 정도로 보임
글에서는 일부 감시되는 네트워크가 임의 웹 URL보다 golang proxy URL을 더 신뢰할 수 있어 평판 필터 우회 등에 쓰일 수 있다고 하지만, 그런 방법은 이미 여러 가지가 있고 이 방식이 특별해 보이진 않음
- 놓친 건 없는 것 같음
-
주제에서 벗어나지만 도메인을 보고 말장난이 불길해서 put.as를 확인해봤고, 대체로 예상한 내용이었음
- https://put.as/ 약간 NSFW임
- 직장에서 그걸 열어보는 실수를 했음
- 스페인어 복수형 단어이긴 함. 하지만…
-
이미 알려진 이슈임: https://github.com/golang/go/issues/31866
- 그 수정은 실수 방지에는 도움이 되겠지만, 의도적으로 하려는 사람은 루트에
.mod와.go파일을 추가하면 되는 것 아닌가? - Marwan이 이 이슈에 있는 건 전혀 놀랍지 않음
그와 Aaron은 Athens를 만들었고, 내가 알기로 Marwan은 Athens의 기반이 된 최초의 Go 다운로드 프로토콜 구현을 작성했음
이 이슈가 흥미로운 건 Athens가 이미 모듈 검증의 사전 확인으로 언급된go mod download -json명령을 사용한다는 점임
대체로 저장소가 Go 모듈 명령이 이해하는 모듈로 통과되면 Athens가 제공함
더 금지된 표현으로 말하면, 모듈 버전·의사 버전·+incompatible을 만들 수 있어야 하고, 그 모듈과 의존성이 유효한 체크섬을 만들어야 함
모듈 체크섬은 현재.mod와 모든 파일, 그리고 각 의존성을 재귀적으로 포함하는 것과 관련 있음
그래서 글쓴이가 말한 것처럼 기본적인 Go 프로그램만 있으면 설계상 임의 파일을 위한 공간을 많이 가질 수 있음
- 그 수정은 실수 방지에는 도움이 되겠지만, 의도적으로 하려는 사람은 루트에
-
W3C는 웹의 모든 것이 강하게 캐시될 수 있도록 기반을 깔았는데, 범용 프록시 캐시가 이렇게 적은 건 이상함
게시자가 필요도 없는데 짧은Cache-Control: max-age나Vary: Cookie응답을 보내고 있는 걸까?
ISP들이 피어링보다 트랜짓 비용을 너무 많이 내고 있는 걸까?- 일반적으로 캐시가 내용을 변조하지 않았는지 보장할 방법이 없음
예를 들어 HTTPS가 아닌 사이트에서는 ISP 프록시가 광고를 삽입할 수 있음
소프트웨어 다운로드는 보통 서명과 체크섬이 있지만, 임의 콘텐츠에는 그런 게 별로 없음
- 일반적으로 캐시가 내용을 변조하지 않았는지 보장할 방법이 없음