[RUBY] A CSRF countermeasure error occurred in a request via ELB, so debugging and resolution

background

I skipped the request with Proxy ELB-> Nginx-> ELB-> Taget Group-> ECS on AWS and ran the Rails service, but I got an error with CSRF token countermeasures, so I got a way to debug and solve it.

Error due to CSRF token measures

Error summary

The error that was occurring was ʻActionController :: InvalidAuthenticityToken`.

What is CSRF token measures?

https://railsguides.jp/security.html#クロスサイトリクエストフォージェリ-csrf This is a security measure that Rails comes standard with. Verify that the token stored in the session and ʻauthencity_token` at POST match, and if they do not match, throw an error.

solution

Add proxy_set_header X-Forwarded-SSL on; to nginx.conf.

nginx.conf


#It's actually written, but omitted
server {
    listen       80;
    server_name  hoge.jp;
    
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    #Add this
    proxy_set_header X-Forwarded-SSL on;
}

Error verification

Is the token different?

Verify that the token stored in the session matches ʻauthencity_token` at POST, and throw an error if they do not match

Then, I looked at the Rails code that is actually verified by wondering if the token stored in the session and ʻauthencity_token` are different, or if it may happen by normal operation.

rails/actionpack/lib/action_controller/metal/request_forgery_protection.rb


def verified_request? # :doc:
!protect_against_forgery? || request.get? || request.head? ||
  (valid_request_origin? && any_authenticity_token_valid?)
end

https://github.com/rails/rails/blob/98a4c0c76938e46009cca668da9c3b584a9e9e74/actionpack/lib/action_controller/metal/request_forgery_protection.rb#L289-L292

When this verified_request? is false, the error ʻInvalidAuthenticityTokenis thrown. If the tokens are different, it means that ʻany_authenticity_token_valid?Is false, so I tried debugging with that expectation. However, ʻany_authenticity_token_valid?` was true.

Is valid_request_origin? false?

Looking at the above code, verified_request? Can be false even when valid_request_origin? is false, so I checked it. Indeed, valid_request_origin? Was false.

Take a look at the contents of valid_request_origin?.

rails/actionpack/lib/action_controller/metal/request_forgery_protection.rb


def valid_request_origin? # :doc:
    if forgery_protection_origin_check
      # We accept blank origin headers because some user agents don't send it.
      raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null"
      request.origin.nil? || request.origin == request.base_url
    else
      true
    end
end

https://github.com/rails/rails/blob/98a4c0c76938e46009cca668da9c3b584a9e9e74/actionpack/lib/action_controller/metal/request_forgery_protection.rb#L455-L463

To make valid_request_origin? False, I tried to output it because it seems that the reason can be understood if the contents of request.origin and request.base_url are known. Then, request.origin washttps: // ~, while request.base_url was http: // ~.

In other words, it turned out that the verification part of request.origin == request.base_url in the above code is false.

Set X-Forwarded-Proto in Nginx conf?

When I investigated variously at this point, "When a request is passed from Nginx to Rails, it seems that even if you access Nginx with HTTPS, it will be passed to Rails as HTTP, and to prevent this, X-Forwarded-Proto in the Nginx conf Use to let Rails know that it's HTTPS. " I tried it.

nginx.conf


#It's actually written, but omitted
server {
    listen       80;
    server_name  hoge.jp;
    
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    #Add this
    proxy_set_header X-Forwarded-Proto https;
}

But it didn't work. When I tried to output the request header with Rails, it was " X-Forwarded-Proto ":" http ".

Is it overwritten from https to http somewhere?

** That's right, this was due to the nature of ELB. ** ** https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/userguide/how-elastic-load-balancing-works.html image.png

This time, a request is sent from the ELB that is the Proxy to the ELB associated with the Rails service that is running, but this is sent by HTTP.

Application Load Balancer and Classic Load Balancer prefer connection headers from client input requests after proxying replies to clients </ b>

As a result, HTTP communication between Nginx and ELB was prioritized, and the request headers X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Port were rewritten.

Then what to do

The request object seems to be made with Rack, so I took a look at the code there.

rack/lib/rack/request.rb


def scheme
if get_header(HTTPS) == 'on'
  'https'
elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
  'https'
elsif forwarded_scheme
  forwarded_scheme
    else
      get_header(RACK_URL_SCHEME)
    end
end
      
#abridgement

def base_url
    "#{scheme}://#{host_with_port}"
end

https://github.com/rack/rack/blob/649c72bab9e7b50d657b5b432d0c205c95c2be07/lib/rack/request.rb

From the way base_url is created, scheme should be https. There are some conditions for schema to be https, but this time it seems that get_header (HTTP_X_FORWARDED_SSL) =='on' should be set! (HTTP_X_FORWARDED_SSL does not have to be rewritten to ELB) So I added X_Forwarded_SSL to the Nginx request header.

nginx.conf


#It's actually written, but omitted
server {
    listen       80;
    server_name  hoge.jp;
    
    proxy_set_header Host $host;
    #The bottom two will be rewritten to ELB
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    #Add this
    proxy_set_header X-Forwarded-SSL on;
}

result

No more errors! When I debugged, X_Forwarded_SSL was added to the request header and scheme became https. image.png

Recommended Posts

A CSRF countermeasure error occurred in a request via ELB, so debugging and resolution
Verification value error that occurred in a clustering environment