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.
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.
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:
/download
./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
/files/example.txt
.~ /files/(.*)
block is triggered, allowing Nginx to serve the file /var/www/example.txt
from the filesystem.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:
send_file
or Rack's Rack::Sendfile
method is used. internal
directive that leaks something sensitive. 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:
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
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'
The Nuclei template used to detect this vulnerability is available on GitHub and the ProjectDiscovery Platform.
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. |
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.