puma / puma

A Ruby/Rack web server built for parallelism

Home Page:https://puma.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Option to hide "Server: Puma PUMA_VERSION" header

daisy1754 opened this issue · comments

Is your feature request related to a problem? Please describe.
Currently we use Puma 5.6.4. Our security auditor asks if we can remove Server: Puma 5.6.4 in HTTP response header because it can potentially give bad actor hints about exploiting our server, if vulnerability is found in certain puma version.

Describe the solution you'd like
Add configuration to allow suppressing "server" response header

Describe alternatives you've considered
Remove this header on our load balancer level could be another option but I prefer if we can omit on puma-level

Additional context
Add any other context or screenshots about the feature request here.

I am also working through a penetration test where this was flagged by a tester. While this seems like security through obscurity, the OWASP project recommends removing the header.

In my current Rails app with puma 6.0.0 it seems like the Sever header is not generally being sent, but I found a couple error responses where it is sent (my tester flagged this on a 408 error):

404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,

I am considering starting a PR for this, but I wanted to ask the maintainers if you have a preference for handling this:

  • remove the header completely
  • remove the version from the header completely
  • honor an optional ENV VAR that removes the header (or version)
  • ?

Maybe a config option to hide the version from the header? I've seen other web servers doing that

I'm ok with adding this as a configuration option.

Hi @nateberkopec , I've got a PR that addresses this here #3125 (if I'm on the right track).

I think the header should be hidden by default, following a "secure by default" mentality... Otherwise, this will be raised in every penetration test ever done on Puma.

I spent some time this afternoon looking at this issue. For a 'good' reponse from the app, Puma does not send a response server header. The only place it's defined are the two below header values (status of 404 & 408) in Puma::Const::ERROR_RESPONSE:

puma/lib/puma/const.rb

Lines 126 to 129 in 904b47a

# The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND",
# The standard empty 408 response for requests that timed out.
408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n",

Both of those strings date back ten plus years.

Puma::Const::ERROR_RESPONSE is only used in Puma::Client#write_error:

puma/lib/puma/client.rb

Lines 285 to 294 in 9436322

write_error(408) if in_data_phase
raise ConnectionError
end
def write_error(status_code)
begin
@io << ERROR_RESPONSE[status_code]
rescue StandardError
end
end

One call to Client#write_error is in Client (408, as shown above), and three calls are in Puma::Server#client_error as shown below:

puma/lib/puma/server.rb

Lines 503 to 510 in 9436322

when HttpParserError
client.write_error(400)
@log_writer.parse_error e, client
when HttpParserError501
client.write_error(501)
@log_writer.parse_error e, client
else
client.write_error(500)

So, we have two key/value pairs in Puma::Const::ERROR_RESPONSE with a server header, but it appears that 404 isn't used, so the only response that is a problem is a 408 ('Request Timeout').

Re adding code to make this an option, it's messy. They are part of a constant hash, which could be modified in Puma::Launcher and then the hash could be frozen. Client doesn't have access to options, so it can't modify the values or have any conditionals based on a config option.

Hence, I think we should remove the server header from the 408 response, which is used, and the 404 response, which isn't used.

@dentarg & @nateberkopec thoughts?

Hence, I think we should remove the server header from the 408 response, which is used, and the 404 response, which isn't used.

Either that or just remove the version info in the header (less intrusive change). I support both :)

I'm wondering about an option for this. Some sites use a non-informative value, most GitHub responses set the server header value to 'GitHub.com'.

The issue here is what Puma returns in the response if it is generated wholly by Puma. The code indicates that may happen with various 'errors', and any of the following may be generated by Puma:

400 Bad Request
408 Request Timeout
500 Internal Server Error
501 Not Implemented

So, given that ENV values are used for config and the Puma::Const::ERROR_RESPONSE values are set 'on load', I propose using an ENV variable PUMA_SERVER_HEADER_VALUE, which, if it is set, is used in the responses defined in Puma::Const::ERROR_RESPONSE. If not set, no server header is used.

This allows Puma generated responses to match responses generated by the app.

I'll submit a PR shortly.