Защита рельсов CSRF с одностраничным приложением (реактивная, угловая, тлеющая) - PullRequest
0 голосов
/ 01 мая 2018

Хорошо. Я официально сошел с ума с этой проблемой.

Давайте возьмем приложение Rails по умолчанию (5, но я попробовал также с приложением 4 по умолчанию).

Я пытаюсь использовать простой код javascript для отправки ajax-запроса POST одному действию контроллера.

В моем ApplicationController у меня есть этот код:

class ApplicationController < ActionController::Base

  after_action :set_csrf_cookie

  protected

    def set_csrf_cookie
      cookies["X-CSRF-Token"] = form_authenticity_token
    end

end

, который устанавливает cookie "X-CSRF-Token" со значением form_authenticity_token.

После этого я могу прочитать этот файл cookie в своем SPA (одностраничном приложении), используя этот код:

<script>
function readCookie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(";");
    for (var i = 0; i < ca.length; i++) {
      var c = ca[i];
      while (c.charAt(0) === " ") c = c.substring(1, c.length);
      if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
  }

// var token = document.getElementsByName('csrf-token')[0].content; // this works!
const token = readCookie("X-CSRF-Token"); // this doesn't work!

fetch('/api/v1', {
  method: 'POST',
  body: {""},
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': token
  },
  credentials: 'include'
}).then(function(response) {
  return response.json();
});
</script>

Когда я использую эту строку:

var token = document.getElementsByName('csrf-token')[0].content;

работает, потому что читает то, что Rails вставляет на html-страницу:

<%= csrf_meta_tags %>

<meta name="csrf-param" content="authenticity_token">
<meta name="csrf-token" content="VXaKlO+/Gr/8pGhr5y0bThQ5L/0IDiznMR/9SpaoI6vOoF9KtmB5/9ka+Hz+zjyssNRi/Em/Ye27C+E5pl3odg==">

Таким образом, содержимое "csrf-token" работает, и мое приложение Rails может проверять CSRF.

Это код из источника Rails: https://github.com/rails/rails/blob/v5.2.0/actionpack/lib/action_controller/metal/request_forgery_protection.rb

Когда вместо этого я использую эту строку:

const token = readCookie("X-CSRF-Token");

это не работает, и я получаю эту ошибку:

Started POST "/api/v1" for 172.18.0.1 at 2018-05-01 18:52:56 +0000
Processing by MyController#action as */*
  Parameters: {"body"=>{}}
Can't verify CSRF token authenticity.
Completed 422 Unprocessable Entity in 2ms (ActiveRecord: 0.0ms)
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):

Также, если я использую другую страницу с другим сервером (npm http-сервер или Microsoft IIS или другие) с тем же сценарием, проблема остается той же.

Если я скопирую содержимое "csrf-token" со html-страницы Rails и использую эту строку в моем скрипте Javascript:

const token = "VXaKlO+/Gr/8pGhr5y0bThQ5L/0IDiznMR/9SpaoI6vOoF9KtmB5/9ka+Hz+zjyssNRi/Em/Ye27C+E5pl3odg==";

РАБОТАЕТ!

Итак, мой вопрос: ПОЧЕМУ?


То, что я прочитал (ничто!):

Ответы [ 3 ]

0 голосов
/ 02 июля 2018

вы проверили readCookie("X-CSRF-Token") значение? значение, вероятно, отличается при хранении в cookie, возможно, значение экранировано

0 голосов
/ 09 ноября 2018

Tnx, у меня твой код работает. Мне нужно было только добавить decodeURIComponent()

const token = decodeURIComponent(readCookie("X-CSRF-Token"));

Я использовал этот подход для Progressive Web App с кэшированным html. Метатег rails по умолчанию (<%= csrf_meta_tags %>) не работает с кэшированным html.

Этот пост в блоге также дает некоторые другие альтернативы: https://www.fastly.com/blog/caching-uncacheable-csrf-security

0 голосов
/ 08 мая 2018

Имя заголовка, которое ожидает Rails, равно X_CSRF_TOKEN (обратите внимание на подчеркивание). Я не вижу проблемы с остальным кодом, которым вы поделились, за исключением того, что токен из cookie должен быть декодирован по URI (decodeURIComponent), поэтому проверьте это, если вы все еще получаете предупреждение.

...