2 điểm bởi GN⁺ 2024-05-26 | 1 bình luận | Chia sẻ qua WhatsApp
  • Checksum database của Go và public module proxy có thể chấp nhận cả những kho lưu trữ không có mã Go, tạo ra một đường đi để nạp dữ liệu từ kho Git tùy ý vào hạ tầng Go rồi tải xuống lại
  • Yêu cầu sum.golang.org/lookup/$module@$version sẽ lấy từ máy chủ gốc khi gặp phiên bản module chưa được ghi vào log, và trong quá trình đó proxy.golang.org cũng sẽ cung cấp file zip của kho lưu trữ tương ứng
  • Kho Ruby của Homebrew và một fork kho Rust đã xuất hiện trong checksum database; thí nghiệm cũng thành công trong việc đăng ký bằng pseudo-version không chỉ module Go mới mà cả các kho không có file Go
  • File zip module bị giới hạn tối đa 500 MiB ở cả dạng nén và giải nén, nhưng vẫn đủ lớn để vượt hạn chế tải xuống trên máy dev/CI/CD, lưu payload hoặc triển khai C2
  • Trong khoảng 1,59 triệu đường dẫn duy nhất của sum.golang.org, có khoảng 1,51 triệu là đường dẫn GitHub, tương đương 95%, cho thấy одновременно sự phụ thuộc của hệ sinh thái Go vào GitHub và khả năng lạm dụng public proxy

Các kho không phải Go được tìm thấy trong Go checksum database

  • Khi xem checksum database của Go, người ta phát hiện github.com/homebrew/homebrew-core xuất hiện rất nhiều trong bảng modules
    • github.com/homebrew/homebrew-core: 39,438 mục
    • github.com/Homebrew/homebrew-core: 30,896 mục
    • github.com/concourse/concourse: 25,372 mục
    • github.com/openshift/release: 24,065 mục
    • github.com/cilium/cilium: 22,138 mục
  • Kho Homebrew được biết là dùng Ruby, và cả trong kho lẫn các file sau khi clone đều không tìm thấy go.mod hay mã nguồn Go
  • Sự khác biệt chữ hoa/chữ thường được giải thích bằng quy tắc case encoding trong tài liệu của Go
    • Chữ hoa được mã hóa thành ! cộng với chữ thường tương ứng, để có thể lưu đồng thời example.com/Mexample.com/m ngay cả trên hệ thống tệp không phân biệt hoa thường
  • github.com/Edu4rdSHL/rust-headless-chrome cũng xuất hiện trong checksum database dù đây là một fork kho Rust không liên quan đến Go

Cách /lookup lấy kho lưu trữ

  • Theo Go Modules Reference trong tài liệu module Go, khi truy vấn checksum database, lệnh Go trước tiên lấy dữ liệu bản ghi từ endpoint /lookup
  • Nếu phiên bản module vẫn chưa được ghi vào log, checksum database sẽ thử lấy module đó từ máy chủ gốc trước khi phản hồi
  • Định dạng endpoint là $base/lookup/$module@$version
    • Nó trả về số bản ghi log cho $version của $module, dòng go.sum, và mô tả cây đã ký
  • Khi truy vấn pseudo-version của github.com/homebrew/homebrew-core, hệ thống trả về checksum record và hash của go.mod
  • Nếu kho không có tag phiên bản, quy tắc pseudo-version của Go sẽ được sử dụng

Thử nghiệm đăng ký module Go mới

  • Sau khi tạo module Go mới github.com/gdbinit/fluxmatter, người ta dùng yêu cầu lookup để kiểm tra việc đăng ký
  • Truy vấn @latest trả về lỗi cho biết đây không phải canonical version
    • bad request: version "latest" is not canonical
  • Truy vấn @v0.0.0 trả về lỗi không biết revision đó
    • not found: ... invalid version: unknown revision v0.0.0
  • Nhưng sau khi đồng bộ lại checksum database và truy vấn, module đã được đăng ký
    • github.com/gdbinit/fluxmatter|v0.0.0-20240524163826-a7e64ffd69f2|2024-05-24T16:40:51.203837Z
  • proxy.golang.org/github.com/gdbinit/fluxmatter/@latest trả về pseudo-version và thông tin nguồn gốc GitHub; file zip của phiên bản đó cũng có thể tải về và xác minh nén thành công
  • Ở bước gieo dữ liệu ban đầu, thậm chí không cần chỉ định chính xác phiên bản; chỉ một truy vấn lookup chứa đường dẫn module và giá trị trông giống phiên bản là đã hoạt động

Cả kho không có mã Go cũng được nạp vào public proxy

  • Thí nghiệm tương tự cũng thành công với kho github.com/gdbinit/readmem hoàn toàn không có mã Go
  • Yêu cầu lookup trả về lỗi không biết revision v0.0.0, nhưng checksum database vẫn đăng ký kho này dưới dạng pseudo-version
    • github.com/gdbinit/readmem|v0.0.0-20131006075740-407cb0a56933|2024-05-24T16:45:35.88456Z
  • @latest trên proxy.golang.org trả về pseudo-version của kho đó cùng thông tin nguồn gốc Git
  • Trong file zip tải xuống không có file Go mà có Entitlements.plist, README, file project Xcode, main.c v.v.
  • Thí nghiệm này dùng kho GitHub, nhưng nếu VCS hoạt động thì có thể cũng thực hiện được với các nền tảng hosting khác

Mức độ phụ thuộc vào GitHub và giới hạn kích thước

  • Số đường dẫn duy nhất trong checksum database là 1,591,375, trong đó các đường dẫn github.com% là 1,515,957
  • Khoảng 95% đường dẫn duy nhất được host trên GitHub {p:95}
  • Đây là thống kê thô, chưa loại bỏ fork hay các đối tượng không phải mã Go thật sự
  • Các ràng buộc về đường dẫn và kích thước file được áp dụng cho file zip module Go
    • File zip module tối đa 500 MiB
    • Tổng kích thước file sau giải nén cũng tối đa 500 MiB
    • File go.mod tối đa 16 MiB
    • File LICENSE cũng tối đa 16 MiB
  • Các giới hạn này nhằm giảm thiểu tấn công từ chối dịch vụ đối với người dùng, proxy và các phần khác của hệ sinh thái module
  • Xét về khả năng bị lạm dụng, 500 MiB vẫn là mức đủ lớn

Các kịch bản lạm dụng có thể xảy ra

  • Public Go proxy có thể được dùng để vượt qua hạn chế tải xuống theo đích đến trên máy dev hoặc máy chủ CI/CD
    • Giả định không có GOPROXY riêng tư
    • Mã độc có thể tải payload lên kho rồi khi cần sẽ tải xuống từ proxy
    • Ngay cả khi mã nguồn gốc biến mất, entry trong checksum database có thể vẫn chỉ còn lại một dấu vết nhỏ
  • Việc DoS proxy.golang.org có thể không dễ thực hiện
    • Có thể yêu cầu proxy tải xuống các kho Git tùy ý
    • Một kiểu tấn công khả dĩ là thu thập nhiều URL GitHub rồi gửi thật nhiều yêu cầu tới API lookup
    • Không rõ triển khai phía máy chủ, nhưng có thể tồn tại giới hạn xử lý song song như work queue
    • Cơ chế bảo vệ băng thông từ phía GitHub cũng có thể phát huy tác dụng
    • Cũng có thể có khả năng DoS vào không gian lưu trữ, nhưng mới chỉ là suy đoán
  • Có thể dễ dàng dựng C2 (command and control) trên proxy.golang.org
    • Truy vấn @latest có thể dùng để tìm phiên bản mới nhất của một module cụ thể
    • Payload có thể là file đơn giản, hoặc được giấu trong go.mod hay mã nguồn Go
    • Nếu muốn tránh dùng một kho duy nhất, có thể dùng module DGA

Luồng tải xuống cho C2

Kết luận và các câu hỏi còn bỏ ngỏ

  • Checksum database và proxy của Go, theo đúng quy trình được tài liệu mô tả, có thể lấy và lưu các module chưa được ghi log từ máy chủ gốc
  • Tình trạng hiện tại có vẻ chưa phải là vấn đề nghiêm trọng của hạ tầng Go, nhưng vẫn có thể bị lạm dụng dễ dàng và còn chỗ để cải thiện
  • Có thể có lý do đã được ghi nhận trong tài liệu hoặc chưa công khai cho việc cho phép các kho không phải mã Go được đưa lên proxy và checksum database
  • Để xác minh liệu đã có ai lạm dụng điều này hay chưa, cần khảo sát khoảng 1,6 triệu kho duy nhất và khoảng 22 triệu entry theo cơ sở dữ liệu cục bộ mới nhất
  • Vì sao một số dự án non-Go hợp lệ lại có trong database vẫn là câu hỏi còn bỏ ngỏ

1 bình luận

 
GN⁺ 2024-05-26
Các ý kiến trên Hacker News
  • Một dịch vụ trực tuyến cho phép người dùng tải dữ liệu lên và dữ liệu đó có vẻ được công khai rốt cuộc sẽ bị dùng cho điều khiển-lệnh, vi phạm bản quyền và hosting CSAM
    Điều này đặc biệt đúng với các dịch vụ khó chặn vì ngoài lưu trữ file còn có các công dụng quan trọng khác; chuyện này đã xảy ra với Twitter[1], Telegram[2], hạ tầng khóa PGP[3], chưa nói đến những mục tiêu hiển nhiên như 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 cũng từng như vậy, và dữ liệu được lưu thậm chí không cần phải công khai
      Với Gmail, họ phân phối thông tin xác thực để người khác đăng nhập, rồi đọc các file đính kèm đã tải lên qua IMAP
      Tôi từng là Google SAD-SRE (Spam, Abuse, Delivery)
    • PyPI cũng cho phép đưa file không phải Python tùy ý vào package, nên có vẻ dễ bị dùng cho mục đích này
      Cũng có thể mã hóa file bằng Base64 rồi nhúng vào chuỗi mã Python
    • Không biết đã có chưa, nhưng một mục tiêu kém hiển nhiên hơn có vẻ là HuggingFace
  • Tôi là Googler, đây là ý kiến cá nhân, và tôi không rành lĩnh vực này
    Hy vọng nhóm Go đã phối hợp với phía GCP và Drive, vì hosting file độc hại là vấn đề Google luôn phải xử lý
    Nó không khác mấy so với các endpoint khác mà Google đã cho phép người dùng đưa dữ liệu tùy ý vào

    • Tôi là cựu Googler và không biết rõ nhóm Go Dev Tools, nhưng trong số các tập đoàn lớn mà tôi từng làm việc hoặc nghe từ bạn bè thân thiết, Google thuộc nhóm làm hợp tác nội bộ kiểu này gần như tốt nhất
      Google rất giỏi trong việc để một nhóm trung tâm quản lý hạ tầng và chia sẻ cho toàn công ty. Miễn là không phải ứng dụng nhắn tin thì vậy; và hoàn toàn chỉ suy đoán thôi, nhưng có lẽ nhóm Go đang dùng kho lưu trữ blob nội bộ, và cũng có thể có một nhóm hạ tầng nội bộ tự động xử lý chống lạm dụng và quét file
  • Trên PyPI cũng có khá nhiều dự án không phải Python
    Vì người dùng Python có thể không biên dịch được mã thư viện, nên cần khả năng phân phối wheel là binary đã biên dịch
    Loại mã đó thường viết bằng C, nhưng cũng có thể bằng Golang[1]; tôi không tìm được ví dụ, nhưng hình như tôi từng thấy nó được dùng để phân phối cả ứng dụng chứ không chỉ thư viện
    Viết app bằng C, đưa lên PyPI rồi bảo người dùng cài bằng pip install thì khá hay
    [1] https://github.com/popatam/gopy_build_wheel_example

    • Chẳng hạn ngay cả khi thêm yêu cầu phải dùng Python, có lẽ người có ý đồ xấu chỉ cần cung cấp tối thiểu một ít mã stub Python là được
      Chẳng khác nào trên Linux mà ls được viết bằng Python, nên có lẽ tốt hơn là đừng chơi trò này
    • Cách dùng này hẳn đã hữu ích hơn nhiều trước khi pip yêu cầu phải ở trong một môi trường như venv/virtualenv/pipenv/pyenv thì mới tải package xuống
    • Cũng có pip install cmake, và binary độc quyền cũng có thể như pip install nvidia-cudnn-cu12
    • Gần đây tôi dùng PyPI khá nhiều cho các công cụ không phải Python như FFmpeg và Eigen
      Đây là một trong những lý do giúp tôi bỏ hẳn Homebrew
  • Có thể tôi nghĩ hơi ngây thơ, nhưng tôi không hiểu chuyện này khác gì so với việc tải file lên một repository GitHub
    Khác biệt chỉ là GitHub yêu cầu tạo tài khoản thôi sao? GitHub cũng có thể lưu dữ liệu tùy ý, và cũng không có giới hạn 500MB

    • GitHub có giới hạn request khá nghiêm ngặt đối với các request ẩn danh
  • Hệ thống module của CUE cuối cùng cũng đang được phát hành; MVS tương tự Go nhưng được xây trên hạ tầng OCI
    Nếu quan tâm đến hệ thống quản lý phụ thuộc thì đây là vài liên kết tham khảo
    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
    Module được bật mặc định từ 0.9.0-alpha-5: https://github.com/cue-lang/cue/releases/tag/v0.9.0-alpha.5
    Trong Go Sum, dự án Trillian hỗ trợ transparency log: https://github.com/google/trillian
    CUE dự định dựa trên các tùy chọn OCI như chứng thực (attestation)

    • Tôi không hiểu điều đó liên quan gì đến bài được liên kết
  • Tôi đã thử nghịch ý tưởng lợi dụng, hay đúng hơn là lạm dụng, golang proxy và sumdb để tạo nhật ký minh bạch miễn phí cho checksum của URL tùy ý
    https://getsum.pub/

  • Có thể tôi ngốc, nhưng tôi không hiểu chính xác vấn đề ở đây là gì
    Proxy cache các repository không phải Go thì có thể hơi lãng phí, nhưng ngay cả nếu không làm vậy, chẳng phải chỉ cần khiến nó cache một repository Go là cuối cùng vẫn có thể bắt nó lưu dữ liệu tùy ý sao?
    Nếu tôi không bỏ sót điều gì thì nghe như chẳng có gì đáng nói cả

    • Có vẻ như bạn không bỏ sót gì
      Tin mới ở đây dường như chỉ là một proxy công khai không có bảo mật nhận thứ cần proxy rồi cung cấp công khai theo cách không bảo mật
      Bài viết nói rằng một số mạng bị giám sát có thể tin URL của golang proxy hơn URL web tùy ý, nên có thể dùng để vượt qua bộ lọc uy tín, v.v.; nhưng đã có nhiều cách như vậy rồi và cách này không có vẻ gì đặc biệt
  • Hơi lạc đề, nhưng nhìn tên miền thấy cách chơi chữ có điềm không lành nên tôi đã kiểm tra put.as, và nhìn chung đúng như dự đoán

    • https://put.as/ hơi NSFW
    • Tôi đã mắc sai lầm khi mở nó ở chỗ làm
    • Đó là một từ số nhiều trong tiếng Tây Ban Nha. Nhưng mà…
  • Đây là vấn đề đã được biết đến: https://github.com/golang/go/issues/31866

    • Bản sửa đó sẽ giúp tránh nhầm lẫn, nhưng nếu ai đó cố ý làm thì chẳng phải chỉ cần thêm các file .mod.go ở thư mục gốc là được sao?
    • Không có gì ngạc nhiên khi Marwan xuất hiện trong issue này
      Anh ấy và Aaron đã tạo ra Athens, và theo tôi biết Marwan đã viết triển khai giao thức tải xuống Go đầu tiên làm nền tảng cho Athens
      Điều thú vị ở issue này là Athens vốn đã dùng lệnh go mod download -json được nhắc đến như bước kiểm tra trước cho việc xác minh module
      Về cơ bản, nếu repository vượt qua được như một module mà lệnh Go module hiểu, Athens sẽ phục vụ nó
      Nói theo cách bị cấm đoán hơn, bạn phải có thể tạo version module, pseudo-version, +incompatible, và module đó cùng các dependency của nó phải tạo được checksum hợp lệ
      Checksum module hiện liên quan đến việc bao gồm .mod, tất cả file, và từng dependency một cách đệ quy
      Vì vậy, như tác giả nói, chỉ cần một chương trình Go cơ bản là theo thiết kế đã có nhiều chỗ cho các file tùy ý
  • W3C đã đặt nền móng để mọi thứ trên web đều có thể được cache mạnh, vậy mà số lượng proxy cache đa dụng lại ít như thế này thì thật lạ
    Có phải các nhà xuất bản đang gửi các phản hồi Cache-Control: max-age quá ngắn hoặc Vary: Cookie dù không cần không?
    Có phải các ISP đang trả quá nhiều chi phí transit so với peering không?

    • Nói chung không có cách nào đảm bảo cache không sửa đổi nội dung
      Ví dụ, với các trang không dùng HTTPS, proxy của ISP có thể chèn quảng cáo
      Tải xuống phần mềm thường có chữ ký và checksum, nhưng nội dung tùy ý thì hiếm khi có những thứ đó