# Gitlab Project Import RCE Analysis (CVE-2022-2185)

Khoảng đầu tháng này, gitlab có release một bản vá bảo mật cho các phiên bản từ 14->15, khá thú vị là trong advisory có nhắc đến một bug post-auth RCE với CVSS 9.9. 

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658296947592/WqiuO9CGH.png align="left")
Bug này tồn tại trong tính năng "Project Imports" của gitlab, được tìm ra bởi [@vakzz](https://hackerone.com/vakzz). Thật tình cờ, khi mò mẫm trong profile h1 của tác giả, tôi có thấy vào 4 tháng trước ông cũng tìm ra 1 bug tại tính năng import project:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658297019330/AS6McqcnC.png align="left")

Nghĩ tưởng ngon ăn nên tôi đã đâm đầu vào học Rails và debug cái bug này! (ai dè 30k ko hề dễ ăn như vậy ( ͡° ͜ʖ ͡°) )

Lưu ý là bài có thể sẽ khá dài hơn so với thông thường, nếu đọc để giải trí và tìm PoC thì bạn đọc có thể lướt tới phần cuối xem video tạm vậy!

# ENVIRONMENT SETUP & DEBUGGING

Phần này cũng khá là cồng kềnh, trắc trở và khó khăn, yêu cầu sự kiên nhẫn tới từ vị trí của Re-Searcher!
Ban đầu thì mình có tham khảo bài của một bạn ở Sun* để setup env, tuy nhiên sau khi chạy thì khá là chậm, và rất unreliable nên mình quyết định tự setup vậy!
Môi trường mình sử dụng máy ảo Ubuntu Desktop 18.04.
Đầu tiên là setup bộ GDK của gitlab lên trước:
```
apt update
apt install make git -y
curl "https://gitlab.com/gitlab-org/gitlab-development-kit/-/raw/main/support/install" | bash
```
Chờ đâu đó khoảng 15-30p thì gdk sẽ được setup xong, tiếp theo là check out phiên bản có lỗi của gitlab:
```
cd gitlab-development-kit/gitlab/
git checkout v15.1.0-ee
```
Sau khi checkout thì sửa các file sau:
- config/gitlab.yml, tìm dòng config host của gitlab, sửa thành ip của máy ảo để có thể browse từ bên ngoài vào
```
  gitlab:
    ## Web server settings (note: host is the FQDN, do not include http://)
    host: 192.168.139.137
    port: 3000
    https: false
```
Tìm tiếp các dòng có key `webpack`, set enabled: false:
```
  webpack:
    dev_server:
      enabled: false
```

Sau khi sửa xong, vào folder gitlab/ gõ lệnh sau để compile các resource của server:
```
rake gitlab:assets:compile
```
- config/puma.rb, tìm dòng khai báo `workers`, comment lại:
```
# workers 2
```
Khi đã sửa các file config xong xuôi, gõ tiếp các lệnh sau để start các service liên quan:
```
gdk stop
gdk start webpack rails-background-jobs sshd praefect praefect-gitaly-0 redis postgresql
```
Về phía IDE, mình sử dụng RubyMine (một sản phẩm của Jetbrains).
Dùng RubyMine, browse tới và mở folder `gitlab`, IDE sẽ tự detect và setup các component liên quan.
Thêm debug config bằng cách mở Run > Edit Configurations

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658298687937/k2F2kciPS.png align="left")

Thêm 1 config `Rails` như sau:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658298833286/8PfGHvCU9.png align="left")

Và 1 config sidekiq:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658298811117/EkkRzW9m_.png align="left")


Trong trường hợp sử dụng source code clone từ repo của gitlab về, các config để debug đã có sẵn, không cần phải setup thêm

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658298597049/uuYl6njop.png align="left")

Như vậy là đã có thể debug ngon nghẻ rồi, mặc dù sidekiq chạy không ổn định lắm, sẽ có trường hợp debug bị miss mất workers, hiện tại mình vẫn chưa rõ tại sao.

# CVE-2022-2185 ANALYSIS

Mình dựa vào một bug report trước đó của @vakzz để phân tích bug này, mặc dù sự liên quan giữa 2 bug là không nhiều lắm, nhưng vẫn khuyên bạn đọc nên đọc qua tại [đây](https://hackerone.com/reports/1439593).
## Relate information

Dựa vào thông tin có được từ chính trang lưu thông tin CVE của gitlab [CVE-2022-2185.json](https://gitlab.com/gitlab-org/cves/-/blob/master/2022/CVE-2022-2185.json), ta biết được bug này là một dạng command injection, 

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658299517871/cExf2_g7y.png align="left")

Mặc dù trong phần references có để lại link tới hackerone report và gitlab issue nhưng hiện tại đều đang để ở private mode:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658299581044/YCyFBCE3_.png align="left")

Đọc các commit trên [gitlab-v15.1.1](https://gitlab.com/gitlab-org/gitlab/-/commits/v15.1.1-ee) thì cũng may mắn một chút là không có nhiều commit lắm, trong đó có một commit đáng chú ý như sau:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658300203657/EqQ0-QoVg.png align="left")

Commit [5d58c705](https://gitlab.com/gitlab-org/gitlab/-/commit/5d58c705ab8a70ee280d735222491af0ef7252fc) có tên khá thú vị và liên quan tới bug này `security-update-bulk-imports-project-pipeline-15-1`

Một số thay đổi đáng chú trong commit này:
Tại lib/gitlab/import_export/**decompressed_archive_size_validator.rb**
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658301328134/GzFGSE56c.png align="left")

Method validate_archive_path check các trường hợp @archive_path là Symlink, không phải là String hoặc không phải là File
```ruby
     def validate_archive_path
        Gitlab::Utils.check_path_traversal!(@archive_path)

        raise(ServiceError, 'Archive path is not a string') unless @archive_path.is_a?(String)
        raise(ServiceError, 'Archive path is a symlink') if File.lstat(@archive_path).symlink?
        raise(ServiceError, 'Archive path is not a file') unless File.file?(@archive_path)
      end

```
Chương trình sau khi validate_archive_path sẽ tiếp tục gọi tới Open3.popen3(command, pgroup: true) để chạy command
Đoạn khai báo command như sau:
```ruby
    def command
        "gzip -dc #{@archive_path} | wc -c"
      end
```
Method này trực tiếp nối chuỗi @archive_path vào command `gzip -dc `, do đó mình suy đoán bug command injection xảy ra tại vị trí này!
(Ruby suck, function mà call như attribute vkl ?!)

class DecompressedArchiveSizeValidator được sử dụng ở 2 vị trí đó là:
- file_importer.rb
- file_decompression_service.rb

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658302423552/QkFynW444.png align="left")

------------------------------------------
#### Side note về workers trong gitlab

Gitlab hoạt động theo cơ chế là giao diện web chỉ thực hiện xử lý các tác vụ chung chung, khi liên quan tới các tác vụ chính, nặng hơn, nó sử dụng thêm sidekiq với vai trò như là các worker, thực hiện các job từ web controller đẩy qua.
Đây cũng là lý do mà khi setup debug phải thêm cả config để debug sidekiq

------------------------------------------
### Case 1:
Với nhánh file_importer.rb, ta bắt đầu từ **Import::GitlabProjectsController**.create và gọi tới Projects::GitlabProjectsImportService.new(current_user, project_params).execute để tạo job

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658302750887/3MsFZzFfD.png align="left")

Dòng 17, 18, 19 đã bị comment để việc debug được dễ dàng hơn, không hiểu tại sao mà môi trường debug bằng GDK lại bị lỗi, tất cả các file upload lên đều bị báo invalid!! Vấn đề này không xảy ra tại các bản product.

`project_params` đã bị limit, chỉ cho phép truyền vào các tham số: name, path, namespace_id, file

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658303212059/oBoq3aXE7.png align="left")

Stacktrace tới đây như sau:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658302766465/cXcc0U2P0.png align="left")

Từ **GitlabProjectsImportService**.execute tiếp tục gọi tới prepare_import_params để sửa, thêm bớt các tham số quan trọng khác (1)


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658303538387/mRCbkW__o.png align="left")

Sau đó, **GitlabProjectsImportService**.execute lại gọi tới **Projects::CreateService**.execute để tạo Project với params vừa truyền vào của GitlabProjectsController. Tại **Projects::CreateService**.execute, nếu project đang import không phải là một template, method sẽ tiếp tục khởi tạo object Project với params được truyền vào:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658303706477/16CdSHRWi.png align="left")

Project được tạo xong thì method sẽ tiếp tục đi vào nhánh gọi tới `validate_import_source_enabled!` để validate import_type:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658304313745/1Taxfqi_7.png align="left")

Trong đó sẽ có 2 nhánh thỏa mãn điều kiện, đầu tiên `import_type` thuộc một trong các loại sau
```
INTERNAL_IMPORT_SOURCES = %w[bare_repository gitlab_custom_project_template gitlab_project_migration].freeze
```

Trường hợp thứ 2, `import_type` sẽ phải tồn tại trong list **Gitlab::CurrentSettings**.import_sources:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658304479539/ECtNohkYw.png align="left")

Tạo xong object và thực hiện thêm một số bước modify linh tinh, method này sẽ gọi tới **Projects::CreateService**.import_schedule để thêm schedule cho worker thực hiện việc import:

```ruby
  def import_schedule
      if @project.errors.empty?
        @project.import_state.schedule if @project.import? && !@project.bare_repository_import? && !@project.gitlab_project_migration?
      else
        fail(error: @project.errors.full_messages.join(', '))
      end
    end
```
Để có thể được vào schedule import thì project này phải có import_type là 'gitlab_project'.

Sau khi được add vào schedule, worker sẽ nhận job và thực thi như sau:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658304788748/Iy7YVJKWq.png align="left")

Stacktrace tới đoạn **DecompressedArchiveSizeValidator**.execute

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658302692407/wj4_jeeyr.png align="left")

Tuy nhiên, theo nhánh này thì ta không thể control được @archive_path

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658305003011/9A9-dFC_B.png align="left")

Khi worker thực thi job, @archive_file được lấy từ Project.import_source, tuy nhiên attribute này mặc định không được set, có giá trị null!

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658305656778/9g5TceyqV.png align="left")

Giá trị này vẫn null tại **Gitlab::ImportExport::FileImporter**.new

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658305714179/JIqlwTIVg.png align="left")

Chỉ tới khi gọi tới **Gitlab::ImportExport::FileImporter**.copy_archive thì giá trị này mới được set:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658305811798/ibyV3SMJb.png align="left")

@archive_file_name được gen dựa trên full\_path của Project, giá trị này không thể bị thao túng nên theo nhánh này hiện tại ta không thể có bug command injection được ¯\_(ツ)_/¯

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658305865776/es0U1QsRw.png align="left")

### Case 2

Do case thứ nhất, file_importer.rb đã bị phế nên mình chuyển qua nhánh thứ 2 để nghiên cứu, nhánh này là file_decompression_service.rb

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658306430178/EQqdGlIy0.png align="left")

Nhánh này khá là ngoằn ngoèo để có thể tìm được payload đúng để access được.

Đầu tiên bạn phải vào phần import group của gitlab, điền các thông tin như Gitlab URL, access token 


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658316765091/lZHlnXU-C.png align="left")

Sau khi điền đúng ta sẽ vào được trang import như sau, click bừa vào import 1 cái:


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658316823856/kuE4Od6lC.png align="left")

Để ý trong Burpsuite, ta có một request như sau:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658306834299/BxA7V3ffB.png align="left")

Search từ khóa group_entity trong source code, mình phát hiện ngoài group_entity thì còn có cả project_entity:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658306934538/3jMo8ygMR.png align="left")

Tính năng này mình không tìm được trên web cũng như document nào, rất có thể đây là một tính năng ẩn, đang phát triển của gitlab!

Tính năng Bulk Import này được handle bởi Import::BulkImportsController

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658307108123/bfWts33b9.png align="left")

Sau khi thực thi create_bulk_import, method BulkImportsController.execute tiếp tục gọi tới BulkImportWorker.perform_async, nội dung method như sau:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658307444249/65n4r1Sgd.png align="left")

Chú ý vào phần gọi tới `BulkImports::CreatePipelineTrackersService.new(entity).execute!`. Method này xem xét các Pipeline nào phù hợp để thực thi với các param vừa truyền vào:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658307602547/IGMC4hF8B.png align="left")

Ví dụ như với `project_entity`, ta có một số Pipeline như sau:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658307871944/xloP6mGJs.png align="left")

--------------------------------------------------------
#### Site note về Pipeline trong Bulk Import
Khái niệm này chỉ giành riêng cho Bulk Import, 
File thực thi các Pipeline này là lib/bulk_imports/pipeline/runner.rb

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658308165180/UNv31xu-z.png align="left")

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658308199083/HgScyCNUC.png align="left")

Các Pipeline sẽ có khai báo, override các method như `extract`, `transform`, `load`, `after_run`.

Runner sẽ duyệt và thực thi các method này theo thứ tự: extract data, transform data, load data, after_run

Và sẽ thực thi lần lượt các Pipeline theo thứ tự được khai báo trong file stage.rb


--------------------------------------------------------

Quay trở lại với Bulk Import project, pipeline ProjectPipeline sẽ là pipeline đầu tiên được thực thi

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658308443455/rLDazlDDU.png align="left")

Nội dung của ProjectPipeline:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658308520655/kfhpi8cSs.png align="left")

Nội dung của method `execute` là gọi tới **Projects::CreateService**.execute với tham số `params` = `data`. Như đã đề cập trong side note, data chính là dữ liệu được sửa đổi bởi các Transformer

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658308696797/ch0ALwKgx.png align="left")

Các extractor và transformer của ProjectPipeline là:
```ruby
extractor ::BulkImports::Common::Extractors::GraphqlExtractor, query: Graphql::GetProjectQuery
transformer ::BulkImports::Common::Transformers::ProhibitedAttributesTransformer
transformer ::BulkImports::Projects::Transformers::ProjectAttributesTransformer
```

Theo flow của Pipeline:
- GraphqlExtractor.extract sẽ lấy dữ liệu từ target về thông qua graphql
- ProhibitedAttributesTransformer, ProjectAttributesTransformer sẽ sửa đổi data vừa lấy về

Với `GraphqlExtractor`, trong fix commit của gitlab `Graphql::GetProjectQuery` được sửa như sau:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658309022234/33VZofoqy.png align="left")

Có thể thấy rõ ràng ở đây, số lượng variable cần lấy đã được giảm thiểu đi khá nhiều.

Một ví dụ về data lấy từ GraphqlExtractor:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658310414727/dsVo9nBXc.png align="left")

Với `ProhibitedAttributesTransformer`, chức năng chủ yếu của transformer này là loại bỏ một số attribute nhạy cảm:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658309285179/bPuxmrDxm.png align="left")

Với **ProjectAttributesTransformer**.execute:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658309472852/VuohLOXp0S.png align="left")

Method này nhận vào data, thực hiện thêm một số bước set các attribute cần thiết như `import_type`, `name`, `path`, sau đó gọi tới `data.transform_keys!(&:to_sym)` để thực hiện convert tất cả các key của Hash vừa truyền vào sang dạng Symbol.

*//Trong Ruby có khái niệm Symbol vs String, đại khái Symbol sẽ có dấu hai chấm ":" ở phía trước*

Đây là một ví dụ sau khi thực hiện `data.transform_keys!(&:to_sym)`

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658309877006/7l8nuiVEh.png align="left")

* Và nên nhớ rằng, `data` vẫn hoàn toàn có thể bị control, do nó lấy về từ GraphQL, GraphQL lại lấy từ trang web mà mình control

Quay trở lại phần import đã nhắc ở Case 1, ta hoàn toàn có thể ghi lại được project.import_source, từ đó có thể control được @archive_file và RCE (〜￣▽￣)〜

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658310961925/8hRvmEMo4.png align="left")

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658311775582/PpQsC5mE2.png align="left")

Trong fix commit của `ProjectAttributesTransformer`, thay vì nhận vào `data`, transformer này đã tạo một Hash mới, chỉ thêm một số key/value cần thiết và return đúng Hash đó, nghĩa là đã hạn chế được những attribute khác bị thêm vào:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658310158082/refIojMsJ.png align="left")

Trong description, gitlab cũng có đề cập tới `special elements` này có thể gây ra command injection

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658310269114/Lo94hQqZQ.png align="left")

Hiện tại, `data` sau khi extract và transform sẽ được truyền vào **Projects::CreateService**.execute

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658310364398/ia3aOAn8k.png align="left")

Có một điều rất tiếc là project tạo ra từ ProjectPipeline lại chỉ có thể có `import_type` = 'gitlab_project_migration'

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658310603127/ZoR4ZdBDI.png align="left")

Khi check để thêm vào import schedule, project này sẽ bị reject bởi điều kiện `!@project.gitlab_project_migration?`
```ruby
  def import_schedule
      if @project.errors.empty?
        @project.import_state.schedule if @project.import? && !@project.bare_repository_import? && !@project.gitlab_project_migration?
      else
        fail(error: @project.errors.full_messages.join(', '))
      end
    end
```
Mặc dù đã có thể control được các attribute của object Project, nhưng attribute quan trọng nhất đã bị ghi đè mất và không có cách nào để ghi đè lại được (thực ra là có nhưng mình sẽ nói trong bài khác).

-----------------------------------------------------

### Case 1 + 2 = 3
Và mình bị stuck ở đó gần 2 tuần liền, ...

Cũng một phần là do tính năng debug của ruby khá lởm, lúc thì chạm bp, lúc thì không. Đôi khi RubyMine còn tự dưng crash, khá là đau đầu với đám này.

Cho tới vài ngày gần đây, mình có tìm ra một cách khác debug nhanh hơn mà không cần bật gitlab server, đó là dùng chính cái RSpec của gitlab để debug. Cái thuận tiện của việc này là tránh phải chờ sidekiq bị duplicate job và không chạy job mình đang debug!

Ở đây mình debug ProjectPipeline bằng project_pipeline_spec.rb, sửa một số data liên quan tới project_data rồi chạy là xong:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658311830457/G4cGXipWP.png align="left")

Nhờ vậy, việc debug của mình khá là nhanh và đã đạt được một số kết quả mới.

Đọc kỹ lại nhánh **Projects::CreateService**.execute, mình nhận ra là đã bỏ qua mất nhánh xử lý template:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658311960438/vSAvlOvvo.png align="left")

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658312013843/Cu8DrMNZn.png align="left")

Nhánh này gọi tới **Projects::CreateFromTemplateService**.execute cùng với `params` chính là `data` lấy từ ProjectPipeline. 

Method này chủ yếu check sự tồn tại của template_name, sau đó gọi tới **GitlabProjectsImportService**.execute cùng với `params` để xử lý tiếp:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658312363018/obHgykMf0.png align="left")

Như đã nói qua ở phần 1, **GitlabProjectsImportService**.execute sau đó sẽ gọi tới `prepare_import_params` để xử lý các params:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658312866337/5XQYTiaye.png align="left")

Tại đây, nếu đang xử lý template_file, chương trình sẽ ghi đè lại param `import_type` = 'gitlab_project'. 

Sau khi xử lý param xong, **GitlabProjectsImportService**.execute sẽ gọi tiếp tới **Projects::CreateService**.execute để tạo lại project với các params vừa sửa.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658312968035/BXLjqaYcz.png align="left")

Như vậy, `import_type` đã được sửa thành 'gitlab_project', mà chương trình vẫn sử dụng lại các `params` cũ của Pipeline 
=> RCE ( ͡° ͜ʖ ͡°)( ͡° ͜ʖ ͡°)( ͡° ͜ʖ ͡°)

Có một điểm cần lưu ý của bug này đó là command sẽ không được thực thi luôn.

Tại **Gitlab::ImportExport::FileImporter**.import, method wait_for_archived_file sẽ được gọi để chờ @archive_file tồn tại rồi mới đi vào nhánh xử lý phía dưới (nhánh có thể inject command)

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658313742071/q4v6aJJRy.png align="left")

Nội dung của wait_for_archived_file:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658313848853/xcquNY_1t.png align="left")

Với MAX_RETRIES = 8, đoạn này chương trình sẽ loop 8 lần để chờ file exists, với mỗi lần sẽ sleep 2\*\*i, áp dụng công thức tính tổng chuỗi lũy thừa vừa hỏi được của mấy em năm nhất, ta biết được sẽ phải chờ 2\*\*8 -1 = 255 giây nếu file không tồn tại:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658313968688/LetZV_74O.png align="left")

Và với trường hợp file không tồn tại thì method này cũng vẫn tiếp tục gọi tới `yield` ở phía dưới, đồng nghĩa với việc các statement sau wait_for_archived_file vẫn được gọi bình thường, ví dụ:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1658314216882/GpVeHcM41.png align="left")

Đến đây thì mọi chuyện về bug này đã sáng tỏ rồi, mặc dù quá trình đọc Ruby/Rails khá là gian nan và đau khổ, nhưng cũng đem lại được nhiều kiến thức và vài thứ hay ho, hy vọng sẽ có đủ để có thể chia sẻ cho bạn đọc trong một ngày không xa!

PoC video: 

Đang chờ 255s để trigger bug nên chưa có video!

https://youtu.be/mLotC1oxNm8
