Jenkins là một Java opensource dùng để thực hiện chức năng tích hợp liên tục, triển khai liên tục (CI/CD – Continuous Integration/Continuous Delivery) và xây dựng các tác vụ tự động hóa. Nó đóng vai trò như một web server tự động thực hiện các tác vụ build, test. Đại khái thì khi lập trình viên tích hợp code của mình vào dự án thì Jenkins sẽ tự động build và chạy test, nếu không có lỗi gì xảy ra thì sẽ đưa code vào triển khai. Điều này làm cho việc phát triển các ứng dụng nhanh chóng hơn.
Mặc định, nếu Jenkins được cài ở dạng recommended (được cài thêm các extension phổ biến) thì một webapp Jenkins sẽ có 2 loại user:
- anonymous user
- authenticated user, có quyền hạn như admin Và 4 loại permission:
- anyone can do anything: cho phép bất kì user nào kể trên thực hiện bất kì thay đổi với project
- logged-in users can do anything: cho phép những user đã đăng nhập thực hiện thay đổi lên project
- legacy mode: chỉ người dùng được gán role "admin" được phép thay đổi project, còn lại đều chỉ được cấp read access
- Matrix-based security: mỗi người dùng được cấu hình để có vai trò cụ thể (như bảng). Vai trò này có tác dụng ở tất cả project (account scope)
- Project-based security: cũng như bảng trên, nhưng người dùng được cấu hình quyền hạn dựa trên project (project scope) Còn nếu không cài ở dạng recommended thì có thể Jenkins sẽ làm giảm đi số lượng permissions xuống, nhưhg vẫn giữ nguyên 2 loại users như trên.
Ngoài ra, Jenkins còn cho phép cấu hình quyền hạn của anonymous user bằng cách cấp thêm read access:
Tuy nhiên, với phiên bản Jenkins từ 2.441 trở xuống (hoặc 2.426.2 LTS trở xuống), việc có cấp quyền read cho anonymous user hay không trở nên vô nghĩa khi mà CVE-2024-23897 được phát hiện.
- Lỗ hổng này cho phép ngay cả anonymous user cũng có thể đọc được bất kì file nào trong máy chủ, với số lượng hữu hạn kí tự. Cụ thể, nếu một attacker sử dụng command line interface (CLI) để truy cập đến Jenkins và thêm kí tự '@' thì phần sau @ sẽ được xem như là đường dẫn 1 tệp tin, và thông báo lỗi trả về từ Jenkins sẽ tiết lộ một phần nội dung tệp tin đó.
- Ngoài ra, Jenkins có lưu một số secret key ở dạng plaintext, vì thế nếu các key này được dùng cho mục đích như SSH hay là authorization code cho admin account thì có thể dẫn đến RCE.
-
Để setup thì chỉ cần một file Dockerfile đơn giản như sau:
FROM jenkins/jenkins:2.441-jdk17 EXPOSE 8080 EXPOSE 50000
-
Rồi chạy
Docker build -t jenkins .
là xong. Sau khi build xong, khởi tạo container và vào localhost port 8080: -
Copy paste admin password được log ở trong docker ra xong sẽ đến phần install plugin. Ở đây mình chọn Install suggested plugins:
-
Tạo tài khoản admin. Nếu không thì dùng tài khoản admin default cũng được:
-
Vậy là xong.
-
Ở trang chủ của Jenkins chọn Manage Jenkins -> Jenkins CLI
-
Tải file
jenkins-cli.jar
về máy. File này dùng để connect đến Jenkins host thông qua CLI -
Ta thử cung cấp thêm tham số cho lệnh help xem sao: => exploit thành công
-
Khi đọc Available commands của Jenkins, ta thấy có tới hơn 70 lệnh khác nhau được Jenkins định nghĩa, tuy nhiên hầu hết đều yêu cầu người dùng phải có quyền Read (mặc định được tắt đối với anonymous user). Ví dụ như câu lệnh gọi Groovy shell sau:
-
Sau khi tự động hóa việc chạy 70 câu lệnh thì mình nhận thấy có 2 câu lệnh hữu dụng, gồm:
who-am-i
vàhelp
, cho phép đọc tối đa 3 dòng trong 1 file (đối với anonymous user không có read access: -
Lệnh
who-am-i
chỉ cho phép đọc dòng đầu tiên của tệp, nhưng như vậy là đủ để đọc /proc/self/environ: -
Ở /var/jenkins_home và /var/jenkins_home/secrets có chứa một directory là secret, chứa khá nhiều key quan trọng trong hệ thống:
-
Theo document thì các key này đa số được lưu ở dạng plaintext có thể được dùng trong các việc như mã hóa AES, ...
-
Tác động lên hệ thống nếu các key này lộ ra được liệt kê đầy đủ tại đây
-
Như vậy, với lỗ hổng này, attacker có thể đọc được khá nhiều tập tin quan trọng bên trong Jenkins server.
-
Nếu như anonymous user được cấp read access thì có thể full read một file bất kì. Để full read thì ta dùng lệnh
connect-node
hoặcreload-jobs
:
-
Khi compare ver 2.441 với 2.442 thì ta thấy commit chủ yếu xảy ra ở hai file là CLIRegisterer.java và CLICommand.java source. Các CLI hander của Jenkins đều được define ở core\src\main\java\hudson\cli
-
Khi command được user gửi từ CLI đến server thì sẽ được call đến class CLIRegisterer xử lí, dưới dạng tham số args của hàm main.
-
Ở hàm main thì có define một parser thông qua return value của phương thức
bindMethod()
. Phương thức này nhận mộtList<MethodBinder>
object làm tham số, vớiMethodBinder
là một class dùng để chuyển các cmd line arguments từ user input sang cho thư việnargs4j
parse: -
Ở trong phương thức
bindMethod()
, đầu tiên phương thứcregisterOptionHandlers()
dùng để gọi các option handler tương ứng cho từng arg, sau đó nó tạo mộtCmdLineParser
object. Các tham số của user sẽ được đưa vào một stack và sau đó tìm ra các method resolver tương ứng. Đến cuối cùng, các method resolver lại được pass sang cho thư việnargs4j
xử lí thông qua classMethodBinder
: -
Sau khi xong phần parse command line arguments thì chương trình bắt đầu parse arguments:
-
Tiếp tục trace vào method này, ta thấy nó làm các công việc như sau:
- Đầu tiên, method này check xem các tham số có rỗng không. Sau đó, trong khối lệnh if, nó kiểm tra syntax của các arguments qua method
this.parserProperties.getAtSyntax()
- Tiếp tục trace lên thuộc tính
this.parserProperties
, ta thấy rằng nó được khởi tạo bằng class methodParserProperties.defaults()
. Khối lệnh if bên dưới chỉ đơn giản là sort lại các argument, tuy nhiên nó không được thực thi do Jenkins đã instantiate một object CmdLineParser với tham số là null: - Trace vào
ParserProperties.defaults()
, ta thấy rằng method này lại gọi đến constructor của classParserProperties
, tuy nhiên thuộc tínhatSyntax
của class này lại được init là true, vì thế khối if của methodparseArgument()
nêu trên luôn được thực thi. - Ở if code block trên, args từ user input lại được đưa thẳng vào method
expandAtFiles(args)
. Trong method này, nó kiểm tra xem nếu có tham số nào bắt đầu bằng '@' thì thực hiện đọc file và parse các tham số có trong file đó. Tuy nhiên, nếu không parse được thì throw một error đi kèm với nội dung của file: - Sau khi thực hiện parse arguments từ file xong, chương trình tiếp tục duyệt qua các tham số và parse chúng. Khi này thì nội dung được đọc từ file đã được đưa vào instance cmdLine, sau đó chúng sẽ được kiểm tra xem liệu các tham số có quá dài hay là không hợp lệ không. Để ý rằng nếu một trong hai lỗi đó xảy ra thì chương trình sẽ throw một message đi kèm với các tham số bị lỗi: => nếu user input refer đến một file như /etc/passwd thì chắc chắn chương trình sẽ báo lỗi, kèm nội dung của file.
- Đầu tiên, method này check xem các tham số có rỗng không. Sau đó, trong khối lệnh if, nó kiểm tra syntax của các arguments qua method
- Đầu tiên ta cần install thư viện args4j.
- Lỗ hổng của Jenkins xảy ra bởi việc handle các error message không đúng cách. Ta chỉ cần 1 chương trình Java đơn giản như sau để tái tạo lỗ hổng:
import org.kohsuke.args4j.CmdLineException; public class TestFile { public static void main(String[] args) throws CmdLineException { // anonymous user input comes from CLI application String[] userInput = {"@file_path_here"}; // the backend initialize a command line parser CmdParser parser = new CmdParser(); // the parser parses user input, which includes character '@' parser.parse(userInput); } }
Related: