CVE-2024-53991 - Discourse Backup Disclosure: Rails send_file Quirk
文章探讨了Rack/Rails与Nginx之间的潜在安全漏洞,特别是在使用`send_file`方法和`X-Accel-Redirect`头时,可能导致敏感资源(如Discourse备份文件)泄露的问题。通过分析默认配置和文件命名模式,展示了攻击者如何利用此漏洞获取受限资源,并提供了修复建议和检测工具。 2025-3-20 11:29:6 Author: projectdiscovery.io(查看原文) 阅读量:21 收藏

Introduction

Modern web applications rely on middleware and web server configurations to efficiently handle file delivery while maintaining security. In the Ruby ecosystem, the send_file method in Rack and Rails is a widely used mechanism that can offload file serving to web servers like Nginx and Apache, improving performance and scalability. However, when used in conjunction with Nginx’s internal directive, a feature designed to restrict access to sensitive resources, unexpected security flaws can emerge.

In this blog, we explore a subtle yet impactful interaction between Rack/Rails and Nginx that can inadvertently expose restricted endpoints. Specifically, we examine how the send_file method, when paired with certain Nginx configurations, can bypass access controls under specific conditions, turning a security feature into a potential attack vector.

To highlight the real-world implications, we analyze Discourse, a popular Rails-based forum platform. We demonstrate how predictable backup file naming patterns, combined with this send_file behavior and a particular Nginx setup, can unintentionally expose sensitive data—such as Discourse database backups—posing a serious risk to affected deployments. This vulnerability has been assigned CVE-2024-53991.

What is X-Accel-Redirect in Nginx?

The X-Accel-Redirect header in Nginx enables controlled access to internal resources by leveraging specially designated location blocks. These location blocks are marked with the internal directive, which restricts them from being directly accessed by external clients.

The internal directive of Nginx is used to specify that a particular location block should only process requests generated by Nginx itself. This does not mean requests originating from localhost or specific IP addresses—it specifically refers to requests initiated internally by Nginx as a result of its processing logic.

Only requests generated by Nginx during its handling of another request (e.g., due to request rewrite or via the X-Accel-Redirect header) can access the internal location.

How Does X-Accel-Redirect Work?

The X-Accel-Redirect header is a mechanism for Nginx to route an incoming request to an internal location block. When Nginx encounters this header in a response from an upstream server (e.g., a web application or backend), it interprets the header’s value as the new URI and internally redirects the request to that URI. If the target URI corresponds to an internal location, the resources in that location can now be served.

An example configuration would be:

Example flow of requests:

  • A client sends a request to an external endpoint, for example, /download.
  • The /download endpoint is handled by an upstream server (e.g., a backend application) that processes the request and returns a response with the header:

    X-Accel-Redirect: /files/example.txt

  • Nginx sees the X-Accel-Redirect header and internally routes the request to /files/example.txt.
  • The location ~ /files/(.*) block is triggered, allowing Nginx to serve the file /var/www/example.txt from the filesystem.

When Rack Sends, Sh*t Breaks:

We were reviewing web frameworks that utilize X-Sendfile or X-Accel-Redirect headers in their file-serving functions to enable optimized file handling through web servers like Nginx or Apache.

One of the frameworks we analyzed was Rack, specifically its send_file function, which incorporates these headers for efficient file delivery. Since Rack middleware is a core component of Rails, we also examined its implementation in detail. During this review, we identified a notable issue—or perhaps a quirk.

Here's how the implementaiton for the same looks like:

Rack handles this by inspecting the value of the X-Sendfile-Type request header. At [1] in the implementation, if the header’s value is x-accel-redirect, the code logic proceeds to retrieve the full path to the file on the filesystem using body.to_path. It then calls the map_accel_path method to translate this filesystem path into an URL suitable for use with Nginx.

In the map_accel_path method, Rack processes another request header, X-Accel-Mapping. The value of this header is split at the = character, where the first part becomes the internal filesystem path and the second part becomes the external path. Rack then uses a regular expression substitution at [2] to replace the internal portion of the file’s full filesystem path with the corresponding external nginx location path.

The newly substituted path (or URL) is then used to set the X-Accel-Redirect response header. This header is intercepted by Nginx, which makes an internal request to the mapped URL and serves the corresponding file to the client, bypassing direct access to the original file path on the server.

Example:

If the following header is present, x-accel-mapping: .*=/secret, full path of the request file matched by /^.*/ will be replaced with /secret at line [2]. Then, at line [3], the value of x-accel-redirect response header is set to this new value /secret . Once, this is done, Nginx will automatically make an internal request to /secret endpoint and will respond back.

Therefore, the pre-requisite for this misconfiguration to be exploitable would be:

  • Rails' send_file or Rack's Rack::Sendfile method is used.
  • Nginx configuration with internal directive that leaks something sensitive.

Discourse: Backup File Disclosure Via Default Nginx Configuration

Discourse, a popular Ruby on Rails-based community discussion platform, uses the send_file method in various parts of its application to serve files efficiently. During our review, we explored potential unauthenticated routes utilizing send_file, as well as the default Nginx configuration provided by Discourse, to identify any potential misconfigurations.

While analyzing the source code, we found that the StylesheetsController uses the send_file method to serve CSS files. At first glance, this controller appeared interesting because it skips several authentication checks:

As seen above, the show action skips authentication checks, allowing unauthenticated users to access it. The show_resource method ultimately calls send_file at [5] to serve files, making this a potential candidate for file disclosure.

We reviewed Discourse’s default Nginx configuration to identify any sensitive or interesting configurations. Discourse places its backups directory within the Rails public directory, which is globally accessible. This directory holds Discourse’s database backups and is highly sensitive. However, the default Nginx configuration prevents direct access to the backups using the internal directive, which restricts the /backups/ route:

  • The /backups/ route is protected by the internal directive, ensuring that backup files cannot be accessed directly by external users.
  • The /downloads/ route, which maps to the public directory, is also marked as internal, further restricting access.

This satisfies the prerequisite for exploiting file disclosure: an Nginx configuration with internal directives protecting sensitive directories.

Exploiting Backup File Disclosure

While the Nginx configuration prevents direct access to backups, Discourse’s use of Rails’ send_file method for file-serving combined with predictable backup file naming conventions can still allow attackers to leak backup files. The second requirement for exploitation is the ability to bruteforce backup file names efficiently. Based on Discourse’s default behavior and naming patterns, backup file names can be derived using the following logic:

<site-name>-YYYY-MM-DD-HHMMSS-v<Migration_Stamp>.tar.gz

  • Site Name: This can be obtained from the website’s title, call Rail's parameterize method on the string.
  • Date of Backup (YYYY-MM-DD): Discourse by default creates backups every 7 days.
  • Time of Backup (HHMMSS): Time is in a 24-hour format with a specific timestamp (e.g., 002005). This can also be bruteforced with approximately 86,400 combinations (total requests for a day).
  • Migration Stamp (v<Migration_Stamp>): The migration stamp corresponds to the Git commit version and associated migration timestamp in the /db/migrate/ directory. Discourse publicly discloses the current Git commit hash on its homepage, which can be used to derive this value of the targeted discourse instance.
Proof of Concept:

curl -k -o db.tar.gz -H 'X-Sendfile-Type: X-Accel-Redirect' -H 'X-Accel-Mapping: .*=/downloads/backups/default/projectdiscovery-discourse-2024-11-15-002501-v20241112145744.tar.gz' -H 'Cache-Control: max-age=0' 'https://discourse.projectdiscovery.io/stylesheets/discourse-local-dates_2395204b3c92cea17bdcc4e554cc7d12e032b555.css?cb=1'

Nuclei Template

The Nuclei template used to detect this vulnerability is available on GitHub and the ProjectDiscovery Platform.

Disclosure Timeline

                    Date                     Event
17th November 2024 ProjectDiscovery discovers the misconfiguration and responsibly reported it to the Discourse team.
25th November 2024 Discourse team triaged the report and acknowledged the issue.
19th December 2024 The vulnerability was marked as resolved by the Discourse team.
20th March 2025 After the standard 90-day disclosure period, the blog post with vulnerability details was publicly disclosed.

Conclusion

This case study highlights how seemingly robust security mechanisms can introduce unintended vulnerabilities when their interactions are not fully understood. The combination of Rack’s send_file method and Nginx’s internal directive serves as a reminder that security is not just about enabling protective measures but ensuring they are configured correctly to prevent unintended access.

To help security teams identify and mitigate this issue in their Discourse deployment, we have created a Nuclei template that automates the detection of misconfigurations leading to file exposure. This template is also integrated into the ProjectDiscovery Cloud platform, enabling our customers to proactively scan for this vulnerability as part of their continuous security assessments.


文章来源: https://projectdiscovery.io/blog/discourse-backup-disclosure-rails-send_file-quirk
如有侵权请联系:admin#unsafe.sh