1 điểm bởi GN⁺ 2023-07-04 | 1 bình luận | Chia sẻ qua WhatsApp
  • Nginx, máy chủ web đa năng chiếm thị phần áp đảo từ năm 2004
  • Cấu hình sai trong Nginx có thể dẫn đến lỗ hổng bảo mật và rò rỉ dữ liệu
  • Giới thiệu NavGix, công cụ tự động để phát hiện lỗ hổng của Nginx
  • Lỗ hổng này có thể cho phép truy cập vào các tệp và thư mục ngoài phạm vi dự kiến
  • Các nghiên cứu tình huống về Bitwarden và HPC Toolkit của Google cho thấy mức độ nghiêm trọng của lỗ hổng này

1 bình luận

 
GN⁺ 2023-07-04
Ý kiến trên Hacker News
  • Tham khảo thêm, gixy (trình kiểm tra cấu hình nginx) phát hiện được vấn đề này: https://github.com/yandex/gixy/blob/master/docs/en/plugins/a...
    Và NixOS tự động chạy gixy trên các cấu hình được tạo thông qua nó, rồi từ chối build hệ thống nếu có vấn đề

    • Nếu web server cần thêm công cụ để người dùng tránh những cái bẫy kiểu này, thì có lẽ nên xem lại mặc định
    • Trước giờ mình không biết gixy, thử chạy trên home server thì phát hiện ra một lỗ hổng. Mình đã dùng $uri trong 301 redirect
    • Mình đã thử Nix một lần, đến giờ thấy khá ổn
      Nhưng mình tò mò không biết có trình tìm kiếm tùy chọn nào tốt hơn không. Kiểu tìm trên toàn bộ hàng nghìn tùy chọn không hợp lắm; mình muốn chỉ xem các package như ssh và những tùy chọn tương ứng với chúng, nhưng kết quả không liên quan lẫn vào quá nhiều
    • NixOS không còn chạy Gixy nữa. Xem https://github.com/NixOS/nixpkgs/pull/209075
  • Có thể đây là câu hỏi ngớ ngẩn, nhưng có lý do chính đáng nào để nginx cho phép đi lên thư mục cha bằng .. trong đường dẫn URL không? Trông nó như hành vi đang chờ gây rắc rối vậy
    Sửa: mình hơi rối về chuyện chính xác lỗ hổng gốc xảy ra thế nào. Có vẻ như http://localhost/foo../secretfile.txt được diễn giải thành kiểu /var/www/foo/../secretfile.txt; vậy thì trên một server không có lỗ hổng, tại sao http://localhost/foo/../secretfile.txt lại không được diễn giải y hệt? Mình không hiểu vì sao .. trong đường dẫn chỉ hoạt động trong một số trường hợp

    • Đây là vấn đề đã được biết đến từ rất lâu trong nginx và là vector tấn công phổ biến trong CTF: https://book.hacktricks.xyz/network-services-pentesting/pent...
    • Vấn đề là URL không phải là đường dẫn thực tế. URL là địa chỉ trừu tượng tới một tài nguyên, có thể là thư mục, tệp, file thực thi, stream, v.v.
      Trong trường hợp này, một phần của URL được nginx diễn giải thành thư mục (http://localhost/foo) do cách nó được ánh xạ từ cấu hình sang filesystem cục bộ. Vì nó có vẻ trỏ đến thư mục, nên khi nginx tạo đường dẫn đầy đủ cho tài nguyên được yêu cầu, nó trở thành "${mapped_path}/../secretfile.txt"; điều này vô nghĩa theo góc nhìn URL nhưng lại hợp lệ trên filesystem cục bộ. URL chỉ là chuỗi chứ không phải thành phần đường dẫn thực sự, nên vị trí của dấu gạch chéo về bản chất không quá quan trọng
      Đây là vấn đề rất thường gặp trên web server nói chung kể từ khi web xuất hiện. Việc ánh xạ URL trực tiếp sang đường dẫn file phổ biến vì ban đầu nó bắt nguồn từ các file server đơn giản có chỉ mục, nhưng rồi nhanh chóng chuyển sang môi trường hỗn hợp nơi URL không còn là đường dẫn mà là định danh ứng dụng. Kiểu chỉ định app bằng một phần của đường dẫn rồi coi phần còn lại là tham số hay đối số
      Và nói chung, việc tôn trọng . hay .. trong URL đối với các đối tượng filesystem thường là vô nghĩa. Các app của mình sẽ chuẩn hóa request path để đảm bảo ánh xạ đúng. Trình duyệt coi URL như đường dẫn khi tạo liên kết tương đối, nên cũng phải cẩn thận với việc dùng dấu / ở cuối khi nào và như thế nào. Nó có thể trỏ tới tài nguyên có ý nghĩa phía server hoàn toàn khác
    • Trong trường hợp sử dụng “bình thường” thì không cần. Có vẻ hợp lý hơn nếu đặt một cờ như allow_parent_traversal on; trong location và biến nó thành hành vi chỉ bật khi khai báo rõ ràng
    • Mình đoán NginX có lẽ sẽ kiểm tra "/foo/bar/.." để chặn hoặc chuẩn hóa thành "/foo/". Nhưng "/foo/bar.." là tên file hoàn toàn hợp lệ, nên có vẻ nó lọt qua kiểu kiểm tra này và không bị bắt
    • Việc .. hoạt động khi nào trong đường dẫn hoàn toàn phụ thuộc vào quyền truy cập file
      Trong trường hợp này, giả sử bạn có quyền đọc file từ thư mục chỉ mục web (../index.html) cho đến thư mục gốc (/), thì vì bạn có quyền đi ngược lên đến root, giờ bạn cũng có thể xem mọi file world-readable có thể truy cập từ root, ví dụ /etc/passwd
      Hãy hình dung một cái dĩa ăn có ba nhánh, trong đó web server nằm ở nhánh ngoài cùng bên phải. Nếu phần tay cầm nơi các nhánh gặp nhau là filesystem, thì khi web server có quyền truy cập các file và thư mục nối từ nhánh bên phải đến tay cầm, một khi đã chạm được đến tay cầm, nó có thể tiếp tục truy cập file ở các nhánh khác
  • Mình không hiểu sao chuyện này lại không được xem là lỗ hổng của nginx. Hành vi này hoàn toàn vô lý, chẳng có vẻ phục vụ mục đích có ích nào, và có thể bị khai thác ngay lập tức

    • Làm vậy vì tốc độ. Thay thế chuỗi thuần túy nhanh hơn nhiều so với việc kiểm tra xem đường dẫn có kết thúc đúng bằng dấu gạch chéo hay không
      Cũng nên nhớ nginx trở nên nổi tiếng nhờ các benchmark cho thấy nó “web scale” hơn Apache2
  • Tôi thấy có thể thử thế này: thêm một tùy chọn kiểu Linux capability để loại bỏ .. khỏi trình phân tích tên tệp của nhân Linux
    Trong ứng dụng web, từ thời còn dùng modem quay số đã liên tục xuất hiện đủ kiểu lách để chèn hai dấu chấm bằng cách này hay cách khác. Cũng như nhân Linux từng xử lý nhiều loại lỗi không gian người dùng khác, có lẽ đã đến lúc tìm cách chặn hẳn vấn đề này một lần

    • RESOLVE_BENEATH trong https://man7.org/linux/man-pages/man2/openat2.2.html
      FreeBSD cũng có tính năng này trong openat(2) thông thường dưới dạng O_RESOLVE_BENEATH
    • Làm vậy sẽ phá vỡ quá nhiều thứ, không phải việc ai tỉnh táo lại đi làm
      Chỉ cần chạy nginx dưới một người dùng riêng với quyền cực kỳ hạn chế hoặc chạy trong Docker. Kèm thêm cập nhật định kỳ thì thường giải quyết được 90% vấn đề bảo mật
    • /some/../path gần như nên bị cấm 100%. Ngoài việc “ai đó đã viết mã xấu xí” ra thì không có ca sử dụng hợp lý nào cả
      ../some/path ít nhất thỉnh thoảng vẫn có ý nghĩa
      Tuy vậy có lẽ nó cũng không hữu ích như tưởng tượng. Nhiều ứng dụng tự diễn giải .. trước khi chuyển cho hệ điều hành
    • Khác biệt chẳng đáng kể. Mã nguồn thường chuẩn hóa đường dẫn trước khi chạm đến API hệ thống tệp
    • Ở phía nhân đã có một cơ chế khác, tức là hệ thống phân quyền mà chúng ta dựa vào
      Nếu bạn phục vụ tệp từ một thư mục lên web, framework web phải đảm bảo không thoát ra ngoài thư mục gốc công khai mà nó phụ trách. Nếu tự làm thì phải tính đến mọi tình huống, bao gồm cả chuyện này
  • “Google VRP Team thưởng 500 đô la cho việc phát hiện lỗ hổng này. Họ cho rằng tác động tới ứng dụng không đủ nghiêm trọng để nhận mức thưởng cao hơn” — vậy mà lộ email tài khoản GCP và khóa riêng tư bí mật chỉ đáng 500 đô thôi sao? Rốt cuộc là kiểu gì vậy. Đúng là Google

  • May là những thứ bị lộ vẫn còn được mã hóa. Ngay cả công ty chuyên về lĩnh vực này cũng không tránh khỏi rò rỉ, nên thành thật mà nói thì đây gần như là kịch bản tốt nhất có thể rồi

  • Tiêu đề đã bị biên tập khá nhiều. Tiêu đề gốc là Hunting for Nginx Alias Traversals in the wild
    Tiêu đề gửi lên HN nhấn mạnh lỗ hổng của Bitwarden, nhưng trong bài cũng nói cả về trường hợp của Google

    • Tốt, tôi đã đổi lại tiêu đề. Tiêu đề gửi lên là “Leaking Bitwarden's Vault with a Nginx vulnerability”
  • Nếu chỉ cần phục vụ tệp tĩnh an toàn một cách ổn định, với mức dùng tài nguyên tối thiểu, thì hiện nay lựa chọn tốt nhất là gì? Trước đây tôi sẽ chọn Nginx, nhưng xét về bảo mật thì tôi tự hỏi liệu một công cụ chuyên biệt hơn, ít khả năng cấu hình sai hơn có tốt hơn không

    • Tôi dùng https://static-web-server.net/
      Nó đa nền tảng, viết bằng Rust, cấu hình đơn giản và có mặc định an toàn. Cũng có image container được harden và mô-đun NixOS được harden
      Tôi không khuyến nghị Caddy. Image Docker chính thức mặc định chạy bằng root [1], và cũng không cung cấp file unit systemd được sandbox đúng cách [2]
      [1]: https://github.com/caddyserver/caddy-docker/issues/104
      [2]: https://github.com/caddyserver/dist/blob/master/init/caddy.s...
      Sửa: trau chuốt lại cách diễn đạt
    • Nói quảng bá một chút thì Caddy làm việc này rất tốt. Nó cung cấp HTTPS tự động, được viết bằng Go nên không phải lo lỗi an toàn bộ nhớ, và có mô-đun file_server rất chắc chắn
    • Tôi đã dùng Caddy vài năm nay. Nó cung cấp chứng chỉ SSL tự động, phục vụ tệp, reverse proxy, và cấu hình rất dễ, rất rõ ràng. Là một binary Go duy nhất nên cả việc “cài đặt” lẫn tệp cấu hình đều đơn giản
    • Caddy khá đơn giản để cấu hình phục vụ tệp tĩnh
    • Merecat: https://github.com/troglobit/merecat/
  • Có thể là câu hỏi ngớ ngẩn, nhưng tại sao Bitwarden lại cho phép yêu cầu không xác thực tới /attachments ngay từ đầu? Ngay cả khi có lỗi Nginx, nếu URL đó yêu cầu xác thực thì chẳng phải yêu cầu sẽ thất bại sao?

    • Đây là exploit nhắm vào cấu hình máy chủ web, nên mã xác thực của Bitwarden hay bất kỳ mã nào của Bitwarden đều không hề được thực thi. Việc dự án dùng cơ chế xác thực riêng thay vì Nginx hay mô-đun của nó không phải điều lạ hay sai
      Dù vậy, vì đã triển khai cấu hình rủi ro qua Docker nên đây đúng là trách nhiệm của Bitwarden. Có vẻ Bitwarden cũng thừa nhận điều đó và đã sửa sau đó
  • Xin thứ lỗi cho câu hỏi ngớ ngẩn này. Nếu có quyền sở hữu thư mục và tệp phù hợp thì kiểu truy cập vòng qua như thế này chẳng phải sẽ bị chặn sao?
    Nếu nginx không chạy bằng root, thì làm sao nó có thể đọc được các tệp khác ngoài những tệp được gán rõ ràng cho người dùng nginx?

    • Tất nhiên là có thể chặn được. Chạy ứng dụng bằng một người dùng, nginx bằng một người dùng khác, đặt go-rwx cho toàn bộ tệp của ứng dụng, rồi đặt nhóm của các tệp “static” thành www-data và chỉ cấp g+r, thì web server sẽ không thể truy cập các tệp của ứng dụng
      Đây đúng nghĩa là nhập môn hosting ứng dụng 101, và người ta đã làm như vậy từ 20 năm trước rồi
    • À, sự kỳ diệu của 022 umask. Cá nhân tôi luôn khuyên nên đặt để người dùng khác không thể đọc tệp. Nếu không làm được với mọi tệp, thì ít nhất cũng nên làm vậy với các thư mục quan trọng như dưới /home
      Có thể sẽ phải chỉnh thêm một chút về group membership, nhưng hoàn toàn đáng công
    • Không biết người khác thế nào, nhưng giờ tôi không còn cài nginx tử tế cho mục đích cá nhân nữa. Tôi chỉ đơn giản là chạy một Docker image. Và thực ra tôi cũng không kiểm tra xem nó có chạy bằng root hay không
      Có khi tôi đang làm hỏng mọi thứ một cách nghiêm trọng thật. Oái
    • Umask thông thường là 022, nên trong đa số trường hợp các worker của nginx có thể đọc nhưng không thể ghi. Thậm chí cũng không cần phải gán rõ ràng cho một người dùng như www-data. Nếu ứng dụng tạo ra dữ liệu nhạy cảm thì đương nhiên nên dùng 077 umask
    • Đúng vậy
      Đáng tiếc là nginx và các web server khác trong các ứng dụng web thông thường thường cần chạy bằng root. Vì chúng phải lắng nghe trên cổng 80 hoặc 443. Các cổng nhỏ hơn 1024 thì chỉ root mới có thể mở
      Giải thích chi tiết hơn ở đây: https://unix.stackexchange.com/questions/134301/why-does-ngi...