CVE-2021–22201: Arbitrary file read on Gitlab

Son Nguyen
tradahacking
Published in
10 min readJun 2, 2021

Dạo gần đây, liên tục là những lỗ hổng Critical đến từ nhà Gitlab, từ RCE via unsafe inline Kramdown cho đến RCE when removing metadata with ExifTool. Theo dòng thời gian, hôm nay mình sẽ kể lại cho các bạn về quá trình mình PoC lại một lỗ hổng gần đây của Gitlab khá thú vị đó là CVE-2021–22201. Mình không có nhiều kinh nghiệm trong việc audit cũng như code Ruby on Rails nên có gì sai sót các bạn góp ý nhé.

Tổng quan

Thực tế, CVE-2021–22201 được Gitlab công bố cách đây khoảng 1 tháng cùng đợt với CVE-2021–22204 (Exiftool), cũng chính vì vậy mà nó hầu như không hề được chú ý tới. Lỗ hổng này cho phép attacker đọc file tùy ý trên server và còn có thể SSRF được nữa. Vào đầu tuần trước, HackerOne cũng đã release PoC của vuln này tuy nhiên… “có cũng như không mà thôi” vì cái quan trọng nhất là payload đã được Hackerone ẩn đi.

Phân tích chi tiết

Đọc chi tiết description thấy rằng lỗi này xảy ra trong quá trình import project vào Gitlab. Tìm kiếm thêm thông tin khác, mình biết được lỗ hổng này ảnh hưởng từ Gitlab version “>=13.9, <13.9.5” hoặc “>=13.10, <13.10.1”.

Vì Gitlab là một project OpenSource nên chắc chắn rằng bản patch sẽ được commit trên git của nó. Dễ rồi, truy cập vào repo của gitlab, đổi branch/tag vềversion 13.9.5 và chọn History để xem tất cả các commit.

Gitlab version 13.9.5

Đập vào mắt mình là rất nhiều các commit liên quan tới việc merge các nhánh security và tất cả chúng đều có đã được nêu chi tiết ở security release note của Gitlab. Dừng lại một chút, mình đọc details tất cả các commit này và mình đã xác định được lỗ hổng này nằm ở commit nào.

Gitlab History

Title: Disable arbitrary URI and file reads in JSON validator

security-sh-json-validator-open-uri-patch.yml

Đọc title là đủ biết lỗ hổng này liên quan tới Json Validator rồi. Nhìn vào đoạn mã patch, dễ thấy rằng Gitlab sử dụng json-schema để làm Json Validator. Vậy là lỗi nằm ở bên thứ 3 .__.

( cho bạn nào chưa biết, Gitlab chấp nhận report lỗi nằm ở 3rd party miễn là lỗi đó nghiêm trọng và bạn chỉ được nhận 50% số tiền bounty mà thôi)

Có 2 comment liên quan tới json-schema xuất hiện trong phần code trên, đọc qua thì mình cũng để hiểu sơ sơ về lỗ hổng này vì sao xuất hiện. Tuy nhiên, để confirm lỗi vẫn phải diff patch thì mới rõ hơn.

NOTE: Những comment trong source vô cùng quan trọng, nó là những gơi ý giúp bạn có thể tìm ra được mấu chốt vấn đềtrong quá trình audit code. Đừng bỏ qua nó!

Ruby JSON Schema Validator

Github Repo: https://github.com/ruby-json-schema/json-schema

Trước tiên, để bắt đầu phân tích lỗi ở một ứng dụng nào, bạn nên đọc document và tìm hiểu các sử dụng ứng dụng đó. Json-schema được sử dụng để validate các dữ liệu json với một schema định trước. Hơn nữa, json-schema còn cho bạn truyền vào một file_path hoặc url chứa dữ liệu json, nó sẽ tự động đọc và validate cho bạn.

Ví dụ:

JSON::Validator.validate({ type: 'string' , format: 'uri' }, 'http://xxxxx.com/')

Sau khi đã nắm những thông tin cơ bản, mình tiến hành diff bản vá của gitlab và source code cũ của json-schema.

Có thể thấy rằng bản patch chỉ bỏ đi một phần code xử lý exception trong function initialize_data. Sau khi đã bắt được exception JsonParseError, json-schema cố gắng mở đọc file một lần nữa trước khi văng ra một event exception khác. Chính cách xử lý dư thừa này là nguyên nhân dẫn tới việc attacker có thể khai thác và đọc file tùy ý trên server.
( Để hiểu rõ hơn, bạn có thể đọc lại issue mà trong bản patch đã có comment)

Okie, tới đây sau khi đã hiểu sơ sơ về root cause lỗ hổng, mình tiến hành code một đoạn mã example để confirm có đúng như mình nghĩ hay không?

#gem install json-schemarequire "json-schema"
JSON::Validator.validate({"format": "string"}, "/etc/passwd")

Tuyệt vời, vậy chính xác đây là điểm bị lỗi rồi. Bây giờ tiến hành tìm kiến function nào trong Gitlab sử dụng json-schema nữa mà thôi.

Arbitrary file read on Gitlab

Việc đầu tiên là clone version Gitlab bị lỗi về, ở đây mình dùng version 13.9.0-ee để phân tích. Mình khuyên các bạn nên sử dụng RubyMine để dễ dàng index cũng như support tốt nhất trong quá trình phân tích.

Mở project Gitlab, tìm tới Gemfile và bundle tất cả các thư viện mà Gitlab sử dụng. Bây giờ là tới quá trình tìm json-schema trong source code của Gitlab.

Ctrl + Shift +F lên, chuyển qua tab In Project và tìm keyword bạn muốn. Ban đầu, mình tìm theo “json-schema” mà không có gì được biệt nên mình đã chuyển qua tìm thẳng các function validation. Cụ thể là “Json::Validation.”

Find in Project

Okie, Gitlab chỉ sử dụng Json::Validator.validateJson::Validator.validate! của json-schema mà thôi. Các bạn chú ý, những line màu xanh không cần phải audit, bởi nó nằm trong spec… hiểu nôm na nó là testcase mà thôi chứ Gitlab không sử dụng nó trong quá trình vận hành.

Đến đây, cả 2 function đều có thể xảy ra lỗi nhưng do mình có viết example với Json::Validator.validate trước rồi nên mình audit function này trước. Nó được sử dụng trong class JsonSchemaValidator của file json_schema_validator.rb.

json_schema_validator.rb

Có thể thấy rằng ngoài json-schema thì Gitlab sử dụng thêm 1 thư viện nữa để validate json đó là JSONSchemer. Tuy nhiên, không cần quan tâm lắm vì nó phụ thuộc vào draft_version mà Gitlab sẽ lựa chọn sử dụng cái nào. Dễ thấy, schema_path là những file được chỉ định ở thư mục: app/validators/json_schemas/*.json

json-schemas

Đế n đay có 2 câu hỏi mình đặt ra?

  1. Là làm thế nào để touch đến function này khi class JsonSchemaValidator không được call bởi bất cứ function nào? ( bởi mình Crtl + chuột trái không thấy có bất cứ function sử dụng nó)
  2. valid_schema? là một boolean function? Rồi exception văng nội dung file cần đọc ra nằm ở đâu? Sao mình access tới đây?

Đáp án cho câu hỏi thứ 1:

Phần comment đã cho mình câu trả lời, đó là do mình không biết tí gì về Ruby on Rails ( hay còn lại là gà). Sau một vòng research thì mình đã hiểu được cơ chế của ActiveRecordValidations. Các bạn nên đọc tài liệu mình đính kèm để hiểu chi tiết hơn nhé. Thực ra đây là một cách Custom Validators của Rails. bạn chỉ cần sử dụng:

validates :data, json_schema: { filename: "file" }

thì validate_each(record, attribute, value) sẽ được gọi, y như comment của code ( mình nói comment trong source code rất qua n trọng mà). Tiếp tục search keyword “json_schema”

Kha khá class call đấy… Thực tếtới đây, không còn cách nào khác là các bạn phả i audit từng function, class một. Nhưng trước khi audit, google thông tin về các file build_metadata_secrets, build_report_resul_data hay git_trailers…( các file trong folder json_schemas) là gì đã. Nó sẽ thu nhỏ scope bạn cần phải audit lại đó.

Tiếp tục quá trình trace code để tìm điểm input, mình nhận thấy class MergeRequestDiffCommit là đáng nghi nhất nên deep sâu vào đây.

MergeRequestDiffCommit

Đúng như mình nghĩ và nhờ một phần vào may mắn, validate trong MergeRequestDiffCommit sẽ được call tới nhờ input từ MergeRequest. Mà MergeRequest là một trong những item cũng được Gitlab cho phép export.

Export Project

Để các bạn tiện audit, mình sẽ tóm lại mối liên hệ giữa các class ở đây.

MergeRequest
|-MergeRequestDiff
|-MergeRequestDiffCommit
|- JsonSchemaValidator
|-
JSON::Validator.validate ( Sink)

Tiến hành fuzz trên Gitlab, mình tạo một project linh tinh trong đó có nhánh dev và master. Tạo một Merge Request :

Tiến hành export Project này, Settings -> Advanced -> Export Project. Download về và mở file merge_requests.ndjson trong thư mục project export ra.

Tèo teo, ở đây sẽ có một key: value là “trailers”:{}, mình đổi thành “trailers”: “/etc/passwd” sau đó ném lại và import vào lại Gitlab xem sao.

Import project thành công nhưng Merge Requests lại không xuất hiện merge request của mình nữa… Đến đây mình phải trả lời câu hỏi số 2, làm sao lấy được exception khi import project?

Mình đọc document và tìm ra được api có thể get status của quá trình import.

API get status import gitlab

Tiến hành ngay và luôn nàooo …..

Bùmm, bất ngờ chưa.

Real case

Nếu bạn quan tâm tới các lỗ hổng của Gitlab thì chắc cũng đã biết tới CVE-2020–10977, cũng là một lỗi đọc file tùy ý trên server và dẫn tới RCE. Tuy nhiên, từ sau lỗ hổng này, Arbitrary file read lead to RCE gần như là không còn khả thi ( trừ khi Gitlab được host trên các dịch vụ cloud).

Mình cũng có research và tham khảo ý kiến từ một vài dân chơi Gitlab khác nhưng không tìm ra được các nào có thể RCE nhờ việc đọc file được. Vì vậy, mình sẽ đưa ra một hướng tấn công có impact theo mình là cao nhất đó là tạo Personal Access Tokens, từ đó có thể full quyền kiểm soát các project trên Gitlab ( cũng chính là impact mà tác giả đã nói trong PoC của HackerOne).

Bước 1: Thay vì đọc /etc/passwd, chỉnh sửa lại payload để có thể đọc được file gitlab_shell_secret tại /var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret.

Bước 2: Sau khi có được gitlab_shell_secret, tham khảo các Internal API ở đây để tiến hành khai thác:

# Get info user with keyid
curl --request GET --header "Gitlab-Shared-Secret: <Base64 encoded secret>" "http://localhost/api/v4/internal/discover?key_id=1"

Key_id tồn tại khi user đó được cáu hình SSH Keys, bạn có thể brutefore key_id tùy ý để tìm kiếm user admin.

# Get new personal access-token
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" --data "key_id=1&name=sondeptrai&scopes[]=read_user&scopes[]=read_repository&scopes[]=api&scopes[]=read_api&scopes[]=write_repository&expires_at=2021-07-24" "http://localhost/api/v4/internal/personal_access_token"

Tại đây,mình có test theo API trên document nhưng không được. Nếu sử dụng user_id thì response trả về sẽ là “Could not find a user without a key”

Thay vì sử dụng user_id thì bạn phải sử dụng key_id:

Bước 3: Tạo được Personal Access Tokens, dùng nó với các API Project Gitlab. Chẳng hạn như list tất cả project ra.

curl "http://localhost/api/v4/projects?private_token=<your_access_token>"

Xong rồi, tới đây bạn chỉ cần git clone project về nữa mà thôi.

git clone ....
Username: Đã lấy từ API bước 2
Password: Personal Access Tokens

Tổng kết

CVE — 2021–22201 là một lỗ hổng mà mình gặp nhiều khó khăn trong quá trình PoC. Tuy không thể RCE được server nhưng với việc clone được các project kể cả private cũng là một thành công lớn trong việc bạn đi pentest rồi. Con đường phân tích lỗ hổng không hề dễ dàng và cần một số yếu tố nhất định, bạn hãy nên chuẩn bị:
1. Kiến thức nền tảng về ngôn ngữ, framework mà application đó đang sử dụng.
2. Nếu không có điều 1 thì bạn hãy rèn luyện tư duy, cách đọc document hiểu quả, càng nhanh càng tốt.
3. Nếu không có 2 điều trên thì bạn cần có một độ trâu bò nhất định :D :D ( làm từ từ rồi cũng ra)

Còn nếu không nữa, hãy bỏ qua lỗi đó và tìm lỗ hổng bảo mật nào phù hợp mới mình hơn :D

Thanks for reading…

--

--