JUSTCTF2024 - Writeup web challenges

·

5 min read

Cũng 1 tháng rồi mình mới ngồi lại chơi CTF sau những ngày tháng ôn thi cuối kì trên trường và tìm bug bounty. Tuy giải này mình chỉ 3/6 bài nhưng mình đánh giá khá cao với những challenges của giải. Nó mang đến cho mình những kiến thức mới cộng thêm quá trình tư duy để research những tài liệu để giải chall. Trong bài viết này mình chỉ viết 2/3 bài mình thấy hay và thú vị mong mọi người hãy đón nhận nó 😍😍😍.

Bypass Backlash: Route Exploration

Chúng ta sẽ bắt đầu với quy trình giải chall. Trang web chỉ cho chúng ta những file ảnh.

Và chỉ có chức năng truy cập vào bức ảnh qua url : http://bbre.web.jctf.pro/avatar?image=hacker.jpg

Trước khi mình đi đến đọc source code để hiểu rõ ý tưởng bài web mình cứ nghĩ đây là một chall về lỗ hổng LFI hoặc path traversal !!! Nhưng không phải sau một thời gian kiểm thử chúng dường như chall không dễ như mình tưởng tương. Dẫn đến mình phải đi đọc source code để xem trang web hoạt đông như thế nào.😎

Thực sự sau khi mở code chall lên mình khá ngợp vì mình cũng chưa từng đọc code ruby nhưng mình liền nghĩ web chỉ có một chức năng đọc ảnh thì hãy đi đến phần controllers để xem chức năng hoạt động như thế nào.

class AvatarsController < ApplicationController

  def show
    @images = [
      "hacker.jpg",
      "bot.jpg",
      "basement.jpg",
      "center.jpg",
      "vision.jpg",
    ]
  end

  def serve
    # Make a request to the njs script to validate the image
    image_url = url_for(request.query_parameters.merge(controller: 'avatars', action: 'get', host: 'justcattheimages.s3.eu-central-1.amazonaws.com', subdomain: false, domain: 'justcattheimages.s3.eu-central-1.amazonaws.com', protocol: 'http', only_path: false, port: 80))
    image = HTTParty.get("http://nginx:8000/fetch?url=#{image_url}")
    send_data image, type: 'image/jpeg', disposition: 'inline'
  end

  def get

  end
end

Sau khi đọc đoạn code trên thi mình nhận ra khả năng trang web này tồn tại lỗ hổng SSRF để lấy flag. Như ta đã đọc ở hàm serve thì server sẽ nhận những param và gộp lại thông qua url_for để tạo ra một url mới để nginx thực hiện request lấy ảnh. Từ những thông tin chúng ta đang nắm bắt câu hỏi đặt ra ở đây liệu flag nằm ở đâu.

Mình liền search FLAG trong source code thì thấy rằng flag sẽ được lấy qua path /flag và chỉ lấy được qua mạng nội bộ. Những thông tin này mình đọc được qua file nginx.conf

load_module modules/ngx_http_js_module.so;

user nginx;

worker_processes 10;

events {
  use epoll;
  worker_connections 128;
}

http {
    resolver 8.8.8.8;

    server {
        listen 8000;

        js_path "/etc/nginx/njs/";
        js_import js from validate.js; 

        location /fetch {
            set $target_url $arg_url;  # Get URL from query parameter
            js_set $valid js.valid_url;
            if ($valid != '') {
                return 403;
            }
            proxy_pass $target_url;
        }

        location /flag {
            internal;
            return 200 "FLAG_PLACEHOLDER";
        }
    }

    server {
        listen 80;

        location / {
            proxy_pass http://rails_app:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

Mình sẽ mô tả quá trình lấy một bức ảnh như sau:

Câu hỏi đặt ra suy nghĩ trong đầu mình hiện lên làm sao để request đến /flag . Trong đầu mình liền có 2 ý tưởng một là tác động đến hàm url_for để thay đổi host hoặc thứ 2 sẽ có một header nào đó của nginx để khiến nginx có thể redirect nội bộ. Với trường hợp thứ nhất thì chúng ta phải bypass được vaildate url:

Chú ý đoạn regex chúng ta thấy rằng chúng cho phép chúng ta nhập nhiều dòng nên từ đó chúng ta có thể bypass như sau:

Vậy làm sao để chúng ta có thể gửi url và bypass bằng cách xuống dòng như vậy??? Hãy cùng mình đi nghiên cứu đến hàm url_for

Sau khi search gg với key word "url_for in ruby" mình đã tìm thấy một link khá uy tín.

ActionDispatch::Routing::UrlFor (rubyonrails.org)

Ở bài document mình khá quan tâm đến param script_name khi param này được sử dụng nó sẽ auto tự động được thêm được dẫn vào trước chỉ sau host. Vậy mình lại có ý tưởng vì sao mình lại không cố thêm vào để để chuyển host thành subdomain của một host khác nhỉ 😁😁😁. Đúng như mình dự đoán điều này khá thành công.

Vậy bước cuối cùng làm sao mình có thể fetch đến /flag từ ip nội bộ. Quan sát lại file nginx.conf mình nghĩ đến dòng load module ngx_http_js_module. Mình lại đi search gg xem có ra gì không. nginx documentation

Mình liền chú ý đến module Module ngx_http_proxy_module (nginx.org)

Mình đọc sẽ hiểu được rằng header "X-Accel-Redirect" sẽ có tác dụng redirect trong nội bộ.

Vậy mình sẽ thử thêm header vào host của kẻ tấn công. BÙM chúng ta đã có thể lấy flag thành công.

Piggy

Sau khi đọc source code của trang web chúng ta thấy được rằng trang web thực hiện một số hoạt động liên quan đến template. Và khả năng trang web tồn tại lỗ hổng SSTI.

Quan sát ở file config.yml mình thấy được rằng template mà trang web sử dụng là template_toolkit.

static_file_handling: 1
public_dir: "public"
engines:
  template:
    template_toolkit:
      ENCODING: 'utf8'
      INCLUDE_PATH: './views'

Mình thực hiện google về template_toolkit và thấy được một document uy tín: https://template-toolkit.org/

Tại đây chúng ta sẽ thấy được một số template ví dụ về đọc file hoặc liệt kê file trong thư mục.

Tôi đã tìm thấy một template liệt kê file trong thư mục và tôi đã thử nó như sau:

https://template-toolkit.org/docs/manual/Plugins.html#section_Directory

Và từ đấy, mình thực hiện đọc file flag như sau: https://template-toolkit.org/docs/modules/Template/Plugin/Datafile.html

Tổng kết

Mong rằng với bài viết trên sẽ có phần nào giúp ích cho các bạn có thêm kiến thức mới và phát triển tư duy. Tuy lần này mình không thể clear được 6 bài. Nhưng với những chall trên đã cho mình những kiến thức khá hay. Hẹn gặp lại các bạn với những giải CTF sắp tới 😍😍😍.