Gần đây, một mã độc đã được thêm vào GitHub Action tj-actions/changed-files. Nếu bạn sử dụng action này, nó có thể làm lộ các thông tin bí mật trong nhật ký build của bạn. Nhật ký build này là công khai đối với các kho lưu trữ công cộng, vì vậy bất kỳ ai cũng có thể xem được các thông tin nhạy cảm của bạn. Đáng sợ đúng không?
Mục lục
Tham chiếu có thể thay đổi vs tham chiếu không thể thay đổi
Cuộc tấn công này có thể xảy ra vì việc tham chiếu đến các tag trong workflow GitHub Actions là phổ biến. Ví dụ:
jobs:<br>
changed_files:<br>
...<br>
steps:<br>
- name: Get changed files<br>
id: changed-files<br>
uses: tj-actions/changed-files@v2
Thoạt nhìn, đây có vẻ là một tham chiếu không thể thay đổi đến “phiên bản 2” của action này, nhưng thực tế đây là một tag Git có thể thay đổi. Nếu ai đó thay đổi tag v2 trong kho lưu trữ tj-actions/changed-files để trỏ đến một commit khác, action này sẽ chạy mã khác vào lần tiếp theo.
Nếu bạn chỉ định một ID commit cụ thể thay vì tag (ví dụ: a5b3abf), đây là một tham chiếu không thể thay đổi và sẽ chạy cùng một mã mỗi lần. Việc sử dụng tag hay ID commit là sự đánh đổi giữa sự tiện lợi và bảo mật. Chỉ định một ID commit cụ thể đảm bảo mã không thay đổi bất ngờ, nhưng các tag lại dễ đọc và so sánh hơn.
Tôi có đang sử dụng các tham chiếu có thể thay đổi không?
Tôi không lo lắng về cuộc tấn công này vì tôi không sử dụng tj-actions, nhưng tôi tò mò muốn biết các GitHub Actions khác mà tôi đang sử dụng. Tôi đã chạy một script shell ngắn trong thư mục chứa các bản sao local của tất cả các kho lưu trữ của tôi:
find . -path '*/.github/workflows/*' -type f -name '*.yml' -print0 \<br>
| xargs -0 grep --no-filename "uses:" \<br>
| sed 's/\- uses:/uses:/g' \<br>
| tr '"' ' ' \<br>
| awk '{print $2}' \<br>
| sed 's/\r//g' \<br>
| sort \<br>
| uniq --count \<br>
| sort --numeric-sort
Script này in ra danh sách tổng hợp tất cả các actions tôi đang sử dụng. Dưới đây là một phần nhỏ của kết quả:
1 hashicorp/setup-terraform@v3<br>
2 dtolnay/rust-toolchain@v1<br>
2 taiki-e/create-gh-release-action@v1<br>
2 taiki-e/upload-rust-binary-action@v1<br>
4 actions/setup-python@v4<br>
6 actions/cache@v4<br>
9 ruby/setup-ruby@v1<br>
31 actions/setup-python@v5<br>
58 actions/checkout@v4
Tôi đã xem qua toàn bộ danh sách và cân nhắc mức độ tin tưởng vào từng action và tác giả của nó.
Action đến từ một tổ chức lớn như actions hoặc ruby?
Họ không hoàn hảo, nhưng họ có khả năng có các quy trình bảo mật tốt để bảo vệ chống lại các thay đổi độc hại.
Action đến từ một nhà phát triển cá nhân hoặc tổ chức nhỏ?
Ở đây tôi thường thận trọng hơn, đặc biệt nếu tôi không biết rõ tác giả. Điều đó không có nghĩa là các cá nhân không thể có bảo mật tốt, nhưng có nhiều sự khác biệt trong thiết lập bảo mật giữa các nhà phát triển ngẫu nhiên trên Internet so với các tổ chức lớn.
Tôi có cần sử dụng action của người khác không, hay tôi có thể tự viết script để thay thế?
Đây là điều tôi thường ưu tiên, đặc biệt nếu tôi chỉ sử dụng một phần nhỏ chức năng mà action cung cấp. Việc này đòi hỏi nhiều công sức hơn ban đầu, nhưng tôi sẽ biết chính xác những gì nó đang làm và ít rủi ro hơn từ các thay đổi upstream.
Tôi cảm thấy khá hài lòng với danh sách của mình. Hầu hết các actions tôi sử dụng đều đến từ các tổ chức lớn, và một số ít là các actions cụ thể cho các công cụ dòng lệnh Rust của tôi, là những thứ không quá quan trọng, nơi tác động của một kho lưu trữ GitHub bị xâm phạm sẽ tương đối nhỏ.
Cách hoạt động của script này
Đây là một ví dụ cổ điển về việc sử dụng các pipeline Unix, nơi tôi kết hợp nhiều công cụ xử lý text tích hợp sẵn. Hãy cùng xem cách nó hoạt động.
find . -path ‘*/.github/workflows/*’ -type f -name ‘*.yml’ -print0
Lệnh này tìm kiếm bất kỳ file workflow GitHub Actions nào – bất kỳ file nào có tên kết thúc bằng .yml trong thư mục .github/workflows/. Nó in ra danh sách các tên file, ví dụ:
./alexwlchan.net/.github/workflows/build_site.yml<br>
./books.alexwlchan.net/.github/workflows/build_site.yml<br>
./concurrently/.github/workflows/main.yml
Nó in chúng với một byte null (\0) giữa chúng, giúp phân tách các tên file trong bước tiếp theo. Mặc định nó sử dụng dòng mới, nhưng byte null an toàn hơn, đề phòng trường hợp tên file chứa ký tự dòng mới.
xargs -0 grep –no-filename “uses:”
Sau đó chúng ta sử dụng xargs để duyệt qua từng tên file. Cờ `-0` cho biết nó sẽ tách trên byte null, và sau đó nó chạy grep để tìm các dòng bao gồm “uses:” – đây là cách bạn sử dụng một action trong file workflow của bạn.
sed ‘s/\- uses:/uses:/g’
Đôi khi có một dấu gạch ngang đầu dòng, đôi khi không – điều này phụ thuộc vào việc uses: có phải là khóa đầu tiên trong từ điển YAML hay không. Lệnh sed này thay thế “- uses:” với “uses:” để bắt đầu làm sạch dữ liệu.
tr ‘”‘ ‘ ‘
Đôi khi tên của action được đặt trong dấu ngoặc kép, đôi khi không. Lệnh này loại bỏ bất kỳ dấu ngoặc kép nào từ đầu ra.
awk ‘{print $2}’
Lệnh này tách chuỗi trên các khoảng trắng và in ra token thứ hai, chính là tên của action.
sed ‘s/\r//g’
Tôi có một vài file workflow sử dụng carriage returns (\r), và chúng được bao gồm trong đầu ra awk. Lệnh này loại bỏ chúng, làm cho dữ liệu nhất quán hơn cho bước cuối cùng.
sort | uniq –count | sort –numeric-sort
Lệnh này sắp xếp các dòng để các dòng giống nhau nằm liền kề nhau, sau đó nhóm và đếm các dòng, và cuối cùng sắp xếp lại để đặt các dòng xuất hiện nhiều nhất ở dưới cùng.
Tôi có lệnh này dưới dạng shell alias gọi là tally.
Nếu bạn sử dụng GitHub Actions, bạn có thể muốn sử dụng script này để kiểm tra các actions của riêng mình và xem những gì bạn đang sử dụng. Nhưng hơn thế nữa, tôi khuyên bạn nên làm quen với các công cụ xử lý text và pipeline Unix – ngay cả trong thời đại của AI, chúng vẫn là một cách mạnh mẽ và linh hoạt để tạo ra các script xử lý dữ liệu một lần.