Lại một lần nữa là CVE của Bitbucket. Đừng ai thắc mắc tại sao mình hay làm thằng này. Chỉ đơn giản là nó setup dễ thôi.
1. Đọc mô tả
Đây là mô tả của Atlatssian về CVE này
Có thể khai thác lỗ hổng thông qua HTTP Request với quyền read 1 repo. Ngoài ra phiên bản fix lỗi là 7.6.17, vì vậy mình cài trên local phiên bản 7.6.16 để dễ diff.
2. Vị trí lỗi
Thằng bitbucket sử dụng built-in command git trên server để chạy các API liên quan đến API git, trong đó có API archive gọi đến command git archive trên hệ thống.
API archive là API để download file code được nén lại của repo.
API: rest/api/latest/projects/< prj key>/repos/< repo name>/archive
3. Phân tích
Tất cả các API gọi tới built-in command git đều được xử lý bởi DefaultGitScmCommandBuilder trong lib/bitbucke-git.jar. Trong đó, đoạn code trigger archive là
Class DefaultGitArchiveBuilder có thể nhận các tham số format và prefix trong API:
Như ta thấy ở trên, lệnh git archive có arg –format và –prefix và trong trường hợp này sẽ lấy giá trị format và prefix mà mình thêm vào trên server đẩy vào đây. Đương nhiên nó đã có các giá trị mặc định trước nên dù không thêm thì API vẫn hoạt động bình thường.
Lưu ý để thực hiện được chèn 2 tham số là format và prefix thì yêu cầu code phải có cái đã, tức là repo không được rỗng, nếu không nó sẽ như thế này đây:
Chương trình kia không hề kiểm tra giá trị format và prefix trước khi nó được chèn vào command, vì vậy ta có thể chèn vào câu lệnh sau: git archive –format=< format> –prefix=< prefix> … một giá trị gì đó để nó có thể chạy được.
Tuy nhiên mình không khai thác được giá trị format, vì nó đã có một enum chỉ cho phép một trong só các format sau:
Ban đầu mình nghĩ chỉ cần chèn prefix=abcd || whoami || hay đại loại vậy là nó sẽ nhảy vào câu lệnh git và đồng thời thực thi lệnh whoami, nhưng mình thử mãi không thành công.
Đi dò tìm thì mình phát hiện ra 1 điều, thằng prefix này vẫn lấy tất cả string (bao gồm ||whoami|| luôn) để cho vào prefix chứ không phải chèn vào command trực tiếp. Tuy nhiên string của hệ thống lại có thể bị escape bởi string Null. Kiểu như string = “haha u0000 hehe” thì nếu cho vào trong terminal nó sẽ bị tách thành “haha” và “hehe”.
String null trong terminal là u0000 còn trên URL là * %00*.
Tóm lại, mình sẽ phải thực hiện chèn prefix =%00 ;whoami; %00. Mình thử lại thì được như thế này
Lệnh vẫn không được thực hiện. Nó có 1 dấu “/” ở cuối, có lẽ do thằng bitbucket set cho prefix luôn có dấu / để ngăn cách giữa prefix và nội dung file. Vậy mình phải nghĩ tới sử dụng một tham số khác có thể sử dụng dấu / này như một phần của nó.
Lệnh git archive có một tham số là –remote chỉ repo cần truy xuất, giá trị –remote này có thể ghép với kí tự / để lấy repo, vì repo ở đây là đường dẫn tới folder:
Giờ thì sẽ đặt prefix=a ;whoami; –remote=hehe, kết quả là:
Đến lúc này thì mình nghĩ, 1 là phải là remote hợp lệ, 2 là thực tế câu lệnh đã thực hiện rồi mà mình không biết, nên mình thử curl đến server port 8000 của mình xem sao, nhưng thử curl không thành công.
Mình thử remote hợp lệ, kết quả là:
***Có nghĩa là nếu một string không có 1 argument nào đằng trước, lệnh sẽ nhận cái string đó làm ref (mặc định ref là HEAD), mà không tồn tại ref ;whoami; này, vì vậy mình lại phải thêm 1 argument khác vào trước cái ;whoami;***, và mình đã thử –output, kết quả là
Vẫn không được, trong khi mình thử trên terminal thì kết quả là:
Vậy là dấu** ;** có thể không hợp lý trong trường hợp này (do mình gà nên không phân biệt được lúc nào nên dùng ; | && || ). Mình thử tất cả các trường hợp trên terminal trước thì thấy chỉ có dùng ` thì output nó sẽ tạo ra file với tên = lệnh whoami:
Ở đoạn này mình nghĩ là: dấu ` sẽ lấy giá trị kết quả câu lệnh chạy bên trong nó làm string, nên hoàn toàn có thể chèn câu lệnh vào trong đây và thực thi được. Mình thử trên bitbucket:
Thú thực đến đoạn này mình vẫn không hiểu sao nó không chạy :v Chắc nó vẫn nhận thằng output=whoami
mà thôi.
Nhưng khi mình đổi từ thằng –output sang thằng –exec thì nó lại chạy ngon nghẻ, và kết quả in hẳn ra error luôn:
Với bkcs là kết quả của lệnh whoami của mình.
Mình hiểu ra là thực ra command git thằng bitbucket dựng không có lỗi, lỗi nằm ở cơ chế của terminal. Nếu như terminal mà nó đã in ra output có chứa <cmd>
thì nó cũng sẽ thực thi cmd này luôn.
Tóm lại, mấu chốt để khai thác lỗi này là làm sao để terminal in ra dòng** <cmd>
** và nó sẽ tự thực hiện command bên trong.
Và chỉ có* –exec* mới có thể cho nó hiển thị lỗi đó (dù thú thực mình cũng không rõ tác dụng của argument này lắm, có lẽ sẽ tìm hiểu sau). Muốn –exec hiển thị lỗi này, trước tiên mình phải làm sao để API nó không nhảy vào exception khác trước, nên mới cần việc bypass prefix và –remote như trên kia.
Điều mình học được thông qua lỗ hổng này:
Terminal sẽ phân tách string qua string null u0000
Terminal sẽ thực hiện command nếu command nằm trong dấu “ khi in ra console
Nguồn: viblo.asia