[RUBY] Puisqu'une erreur de contre-mesure CSRF s'est produite dans une demande via ELB, jusqu'au débogage et à la résolution

Contexte

J'ai sauté la demande avec Proxy ELB-> Nginx-> ELB-> Taget Group-> ECS sur AWS et j'ai exécuté le service Rails, mais j'ai eu une erreur avec les contre-mesures de jeton CSRF, alors je suis allé au débogage et le résoudre.

Une erreur se produit en raison de contre-mesures de jeton CSRF

Résumé des erreurs

L'erreur qui se produisait était ʻActionController :: InvalidAuthenticityToken`.

Qu'est-ce que les mesures de jeton CSRF?

https://railsguides.jp/security.html#クロスサイトリクエストフォージェリ-csrf Il s'agit d'une mesure de sécurité fournie de série par Rails. Vérifiez que le jeton stocké dans la session correspond à ʻauthencity_token` au POST, et lancez une erreur s'ils ne correspondent pas.

Solution

Ajoutez proxy_set_header X-Forwarded-SSL on; à nginx.conf.

nginx.conf


#C'est en fait écrit, mais omis
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;
    #Ajoute ça
    proxy_set_header X-Forwarded-SSL on;
}

Vérification d'erreur

Le jeton est-il différent?

Vérifiez que le token stocké dans la session correspond à ʻauthencity_token` au POST, et lancez une erreur s'ils ne correspondent pas

Ensuite, j'ai regardé le code Rails qui est réellement vérifié en me demandant si le jeton stocké dans la session et ʻauthencity_token` sont différents, ou si cela peut arriver par un fonctionnement normal.

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

Lorsque cette vérification_requête? Est fausse, l'erreur ʻInvalidAuthenticityToken` est générée. Si les jetons sont différents, cela signifie que «any_authenticity_token_valid?» Est faux, donc j'ai essayé de déboguer avec cette attente. Cependant, «any_authenticity_token_valid?» Était vrai.

Est-ce que valid_request_origin? Est faux?

En regardant le code ci-dessus, même si valid_request_origin? Est faux, vérifié_request? Peut être faux, alors je l'ai vérifié. Alors, sûrement, valid_request_origin? Était faux.

Jetez un œil au contenu de 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

Pour rendre valid_request_origin? Faux, j'ai essayé de le sortir car il semble que la raison puisse être comprise si le contenu de request.origin et request.base_url est connu. Ensuite, request.origin était https: // ~, tandis que request.base_url était http: // ~.

En d'autres termes, il s'est avéré que c'était faux dans la partie vérification de request.origin == request.base_url dans le code ci-dessus.

Définir X-Forwarded-Proto dans la configuration Nginx?

À ce stade, j'ai beaucoup enquêté et j'ai trouvé que "Lorsqu'une requête est transmise de Nginx à Rails, même si vous accédez à Nginx via HTTPS, elle semble être transmise à Rails en tant que HTTP. Pour éviter cela, utilisez X-Forwarded-Proto dans la configuration Nginx. Utilisez pour indiquer à Rails qu'il s'agit de HTTPS. Je l'ai essayé.

nginx.conf


#C'est en fait écrit, mais omis
server {
    listen       80;
    server_name  hoge.jp;
    
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    #Ajoute ça
    proxy_set_header X-Forwarded-Proto https;
}

Mais ça n'a pas marché. Quand j'ai essayé de sortir l'en-tête de la requête avec Rails, c'était "" X-Forwarded-Proto ":" http "`.

Est-il écrasé de https vers http quelque part?

** C'est vrai, cela était dû à la nature de l'ELB. ** ** https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/userguide/how-elastic-load-balancing-works.html image.png

Cette fois, une demande est envoyée depuis l'ELB qui est le proxy vers l'ELB associé au service Rails en cours d'exécution, mais elle est envoyée par HTTP.

Application Load Balancer et Classic Load Balancer priorisent les en-têtes de connexion à partir des demandes d'entrée client après avoir transmis par proxy les réponses aux clients </ b>

En conséquence, la communication HTTP entre Nginx et ELB a été priorisée et les en-têtes de requête «X-Forwarded-For», «X-Forwarded-Proto» et «X-Forwarded-Port» ont été réécrits.

Alors que faire

L'objet de requête semble avoir été créé avec Rack, j'ai donc jeté un œil au code.

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
      
#réduction

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

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

De la façon dont base_url est créé, schema doit être https. Il y a certaines conditions pour que schema soit https, mais cette fois il semble que get_header (HTTP_X_FORWARDED_SSL) == 'on' devrait être défini! (HTTP_X_FORWARDED_SSL n'a pas besoin d'être réécrit en ELB) Donc, j'ai ajouté X_Forwarded_SSL à l'en-tête de requête de Nginx.

nginx.conf


#C'est en fait écrit, mais omis
server {
    listen       80;
    server_name  hoge.jp;
    
    proxy_set_header Host $host;
    #Les deux derniers seront réécrits en ELB
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    #Ajoute ça
    proxy_set_header X-Forwarded-SSL on;
}

résultat

Plus d'erreurs! Lorsque j'ai débogué, X_Forwarded_SSL a été ajouté à l'en-tête de la demande et schema est devenu https. image.png

Recommended Posts

Puisqu'une erreur de contre-mesure CSRF s'est produite dans une demande via ELB, jusqu'au débogage et à la résolution
Erreur de valeur de vérification survenue dans l'environnement de clustering