Rails CSRF Protection + Angular.js: protect_from_forgery, POST'ta oturumu kapatmama neden oluyor


129

Eğer protect_from_forgeryseçenek application_controller'da belirtilmişse, o zaman oturum açabilir ve herhangi bir GET isteğini gerçekleştirebilirim, ancak ilk POST isteğinde Rails oturumu sıfırlayarak oturumu kapatır.

Bu protect_from_forgeryseçeneği geçici olarak kapattım, ancak Angular.js ile kullanmak istiyorum. Bunu yapmanın bir yolu var mı?


Bunun yardımcı olup olmadığına bakın, bu HTTP üstbilgilerini ayarlamakla ilgili stackoverflow.com/questions/14183025/…
Mark Rajcok

Yanıtlar:


276

DOM'den CSRF değerini okumak iyi bir çözüm değil, sadece geçici bir çözüm olduğunu düşünüyorum.

İşte bir belge formu angularJS resmi web sitesi http://docs.angularjs.org/api/ng.$http :

Yalnızca etki alanınızda çalışan JavaScript çerezi okuyabildiğinden, sunucunuz XHR'nin etki alanınızda çalışan JavaScript'ten geldiğinden emin olabilir.

Bundan (CSRF Koruması) yararlanmak için, sunucunuzun ilk HTTP GET isteğinde XSRF-TOKEN adlı JavaScript tarafından okunabilir oturum tanımlama bilgisinde bir belirteç ayarlaması gerekir. Sonraki GET dışı isteklerde sunucu, tanımlama bilgisinin X-XSRF-TOKEN HTTP başlığıyla eşleştiğini doğrulayabilir.

İşte bu talimatlara dayalı çözümüm:

Önce çerezi ayarlayın:

# app/controllers/application_controller.rb

# Turn on request forgery protection
protect_from_forgery

after_action :set_csrf_cookie

def set_csrf_cookie
  cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
end

Ardından, GET olmayan her istekte belirteci doğrulamalıyız.
Rails zaten benzer yöntemle oluşturduğundan, mantığımızı eklemek için onu basitçe geçersiz kılabiliriz:

# app/controllers/application_controller.rb

protected
  
  # In Rails 4.2 and above
  def verified_request?
    super || valid_authenticity_token?(session, request.headers['X-XSRF-TOKEN'])
  end

  # In Rails 4.1 and below
  def verified_request?
    super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
  end

18
Herhangi bir istemci tarafı kodu değiştirmeniz gerekmediği için bu tekniği seviyorum.
Michelle Tilley

11
Bu çözüm CSRF korumasının yararlılığını nasıl koruyor? Tanımlama bilgisini ayarlayarak, işaretlenen kullanıcının tarayıcısı bu tanımlama bilgisini siteler arası istekler dahil olmak üzere sonraki tüm isteklerde gönderir. Kötü niyetli bir istek gönderen kötü niyetli bir üçüncü taraf site kurabilirdim ve kullanıcının tarayıcısı sunucuya 'XSRF-TOKEN' gönderebilir. Görünüşe göre bu çözüm, CSRF korumasını tamamen kapatmakla eşdeğer.
Steven

9
Angular belgelerden: "Yalnızca alanınızda çalışan JavaScript çerezi okuyabildiğinden, sunucunuz XHR'nin etki alanınızda çalışan JavaScript'ten geldiğinden emin olabilir." @StevenXu - Üçüncü taraf sitesi çerezi nasıl okur?
Jimmy Baker

8
@JimmyBaker: evet, haklısın. Belgeleri inceledim. Yaklaşım kavramsal olarak sağlam. Tanımlama bilgisinin ayarını doğrulama ile karıştırdım, Angular çerçevesinin tanımlama bilgisinin değerine göre özel bir başlık ayarladığını fark etmedim!
Steven

5
form_authenticity_token, Rails 4.2'deki her aramada yeni değerler üretir, bu nedenle bu artık çalışmıyor gibi görünmektedir.
Dave

78

Varsayılan Rails CSRF korumasını ( <%= csrf_meta_tags %>) kullanıyorsanız, Angular modülünüzü şu şekilde yapılandırabilirsiniz:

myAngularApp.config ["$httpProvider", ($httpProvider) ->
  $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
]

Veya CoffeeScript kullanmıyorsanız (ne !?):

myAngularApp.config([
  "$httpProvider", function($httpProvider) {
    $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content');
  }
]);

İsterseniz başlığı yalnızca GET dışı isteklerde aşağıdaki gibi bir şeyle gönderebilirsiniz:

myAngularApp.config ["$httpProvider", ($httpProvider) ->
  csrfToken = $('meta[name=csrf-token]').attr('content')
  $httpProvider.defaults.headers.post['X-CSRF-Token'] = csrfToken
  $httpProvider.defaults.headers.put['X-CSRF-Token'] = csrfToken
  $httpProvider.defaults.headers.patch['X-CSRF-Token'] = csrfToken
  $httpProvider.defaults.headers.delete['X-CSRF-Token'] = csrfToken
]

Ayrıca, HungYuHei'nin istemciden ziyade sunucudaki tüm temelleri kapsayan cevabını kontrol ettiğinizden emin olun .


Açıklamama izin ver. Temel belge düz bir HTML, .erb değil, bu nedenle kullanamıyorum <%= csrf_meta_tags %>. Sadece bahsetmeye yetecek kadar olması gerektiğini düşündüm protect_from_forgery. Ne yapalım? Temel belge düz bir HTML olmalıdır (burada seçen kişi ben değilim).
Paul

3
protect_from_forgerySöylediğiniz şeyi kullandığınızda "JavaScript kodum Ajax isteklerinde X-CSRF-Tokenbulunduğunda, başlıkta mevcut CSRF belirtecine karşılık gelen bir tane göndereceğime söz veriyorum ." Bu belirteci almak için, Rails onu DOM'a enjekte eder <%= csrf_meta_token %>ve Ajax isteklerinde her zaman jQuery ile meta etiketin içeriğini alır (varsayılan Rails 3 UJS sürücüsü bunu sizin için yapar). ERB kullanmıyorsanız, geçerli jetonu Rails'den sayfaya ve / veya JavaScript'e almanın bir yolu yoktur ve bu nedenle protect_from_forgerybu şekilde kullanamazsınız .
Michelle Tilley

Açıklama için teşekkür ederim. Klasik bir sunucu tarafı uygulamasında csrf_meta_tags, sunucu her yanıt oluşturduğunda istemci tarafının aldığını ve bu etiketlerin öncekilerden farklı olduğunu düşündüm . Dolayısıyla, bu etiketler her istek için benzersizdir. Soru şudur: uygulama bir AJAX isteği için bu etiketleri nasıl alır (açısal olmadan)? JQuery POST isteklerinde protect_from_forgery kullandım, bu CSRF belirtecini almakla hiç uğraşmadım ve işe yaradı. Nasıl?
Paul

1
Rails UJS sürücüsü jQuery.ajaxPrefilterburada gösterildiği gibi kullanır : github.com/indirect/jquery-rails/blob/c1eb6ae/vendor/assets/… Bu dosyayı inceleyebilir ve Rails'in atladığı tüm çemberleri görebilirsiniz. bunun için endişelen.
Michelle Tilley

@BrandonTilley o anlamda sadece bu yaparsınız yapmak olmaz putve postyerine üzerinde common? Gönderen raylar güvenlik rehberi :The solution to this is including a security token in non-GET requests
christianvuerings

29

Angular_rails_csrf mücevher otomatik açıklanan model için destek ekler HungYuHei cevabı tüm denetleyicileri için:

# Gemfile
gem 'angular_rails_csrf'

angular_rails_csrf'yi doğru kullanmak için uygulama denetleyicinizi ve diğer csrf / sahtecilikle ilgili ayarları nasıl yapılandırmanız gerektiğine dair bir fikriniz var mı?
Ben Wheeler

Bu yorumun yapıldığı sırada angular_rails_csrfmücevher Rails 5 ile çalışmaz. Ancak, Angular istek başlıklarını CSRF meta etiketinden gelen değerle yapılandırmak işe yarar!
bideowego

Rails 5'i destekleyen
gem'in

4

Önceki tüm cevapları birleştiren cevap, Devisekimlik doğrulama cevheri kullandığınıza bağlıdır.

Her şeyden önce mücevheri ekleyin:

gem 'angular_rails_csrf'

Ardından, rescue_fromapplication_controller.rb'ye blok ekleyin :

protect_from_forgery with: :exception

rescue_from ActionController::InvalidAuthenticityToken do |exception|
  cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
  render text: 'Invalid authenticity token', status: :unprocessable_entity
end

Ve son olarak, durdurucu modülünü size açısal uygulamaya ekleyin.

# coffee script
app.factory 'csrfInterceptor', ['$q', '$injector', ($q, $injector) ->
  responseError: (rejection) ->
    if rejection.status == 422 && rejection.data == 'Invalid authenticity token'
        deferred = $q.defer()

        successCallback = (resp) ->
          deferred.resolve(resp)
        errorCallback = (resp) ->
          deferred.reject(resp)

        $http = $http || $injector.get('$http')
        $http(rejection.config).then(successCallback, errorCallback)
        return deferred.promise

    $q.reject(rejection)
]

app.config ($httpProvider) ->
  $httpProvider.interceptors.unshift('csrfInterceptor')

1
$injectorDoğrudan enjekte etmek yerine neden enjekte $httpediyorsunuz?
whitehat101

Bu işe yarıyor, ancak yalnızca isteğin zaten tekrarlanıp tekrarlanmadığını kontrol ettiğimi düşünüyorum. Tekrarlandığında sonsuza kadar döneceği için tekrar göndermiyoruz.
duleorlovic

1

Diğer cevapları gördüm ve harika olduklarını ve iyi düşünülmüş olduklarını düşündüm. Raylar uygulamamı daha basit bir çözüm olduğunu düşündüğüm şekilde çalıştırdım, bu yüzden paylaşacağımı düşündüm. Raylar uygulamam bu varsayılan olarak geldi,

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception
end

Yorumları okudum ve açısal olarak kullanmak ve csrf hatasını önlemek istediğim şey bu gibi görünüyordu. Ben buna değiştirdim

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :null_session
end

Ve şimdi çalışıyor! Bunun işe yaramaması için herhangi bir neden göremiyorum, ancak diğer posterlerden biraz fikir almak isterim.


6
Bu, rayların 'oturumlarını' kullanmaya çalışıyorsanız sorunlara neden olacaktır çünkü sahtecilik testini geçemezse sıfıra ayarlanacaktır, bu her zaman olacaktır, çünkü csrf belirtecini istemci tarafından göndermezsiniz.
hajpoj

Ancak Rails oturumlarını kullanmıyorsanız, her şey yolunda; teşekkür ederim! Buna en temiz çözümü bulmak için uğraşıyorum.
Morgan

1

HungYuHei'nin cevabındaki içeriği başvurumda kullandım. Bununla birlikte, bazıları kimlik doğrulama için Devise'ı kullanmam ve bazıları da uygulamamla aldığım varsayılan nedeniyle birkaç ek sorunla uğraştığımı fark ettim:

protect_from_forgery with: :exception

İlgili yığın taşma sorusunu ve oradaki cevapları not ediyorum ve çeşitli hususları özetleyen çok daha ayrıntılı bir blog yazısı yazdım . Bu çözümün burada ilgili kısımları, uygulama denetleyicisinde:

  protect_from_forgery with: :exception

  after_filter :set_csrf_cookie_for_ng

  def set_csrf_cookie_for_ng
    cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
  end

  rescue_from ActionController::InvalidAuthenticityToken do |exception|
    cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
    render :error => 'Invalid authenticity token', {:status => :unprocessable_entity} 
  end

protected
  def verified_request?
    super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
  end

1

Bunun için çok hızlı bir hack buldum. Tek yapmam gereken şuydu:

a. Benim görüşüme göre, $scopebelirteci içeren bir değişkeni başlatıyorum, diyelim ki formdan önce veya daha iyisi denetleyici başlatmada:

<div ng-controller="MyCtrl" ng-init="authenticity_token = '<%= form_authenticity_token %>'">

b. AngularJS denetleyicimde, yeni girdimi kaydetmeden önce belirteci hash'e ekliyorum:

$scope.addEntry = ->
    $scope.newEntry.authenticity_token = $scope.authenticity_token 
    entry = Entry.save($scope.newEntry)
    $scope.entries.push(entry)
    $scope.newEntry = {}

Daha fazlasına gerek yok.


0
 angular
  .module('corsInterceptor', ['ngCookies'])
  .factory(
    'corsInterceptor',
    function ($cookies) {
      return {
        request: function(config) {
          config.headers["X-XSRF-TOKEN"] = $cookies.get('XSRF-TOKEN');
          return config;
        }
      };
    }
  );

Angularjs tarafında çalışıyor!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.