Пример атаки CSRF (подделка межсайтовых запросов) и предотвращение в PHP - PullRequest
48 голосов
/ 26 марта 2010

У меня есть веб-сайт, на котором люди могут голосовать вот так:

http://mysite.com/vote/25

Это сделает голосование по пункту 25. Я хочу сделать это доступным только для зарегистрированных пользователей, и только если они хотят это сделать. Теперь я знаю, когда кто-то занят на сайте, и кто-то дает ему такую ​​ссылку:

http://mysite.com/vote/30

тогда голосование будет отдано за него по этому пункту, и он не захочет этого делать.

Я прочитал объяснение на веб-сайте OWASP , но на самом деле я его не понимаю

Это пример CSRF и как я могу предотвратить это. Лучшее, что я могу придумать, это добавить что-то в ссылку, например, хэш. Но это будет довольно раздражающим, чтобы положить что-то в конце всех ссылок. Разве нет другого способа сделать это.

Может кто-то еще может дать мне какой-то другой пример, потому что мне кажется, что веб-сайт довольно неприятен.

Ответы [ 4 ]

91 голосов
/ 26 марта 2010

Это может стать примером CSRF, если:

  • эта ссылка выбрана (например, с помощью тега <img>) : подделка
  • с другого сайта: кросс-сайт


Например, если бы я мог вставить этот тег <img> в исходный HTML-код stackoverflow (и я могу, поскольку stackoverflow позволяет использовать теги <img> в своих сообщениях) :

<img src="http://mysite.com/vote/30" />

Вы бы просто проголосовали за этот пункт; -)


Решение, которое обычно используется, заключается в том, чтобы поместить токен с ограниченным сроком службы в URL-адрес и при получении URL-адреса проверить, что этот токен все еще действителен.

Основная идея будет:

  • При создании страницы:
    • генерирует уникальный токен
    • сохранить в сеансе пользователя
    • и поместите его в ссылки на странице - это будет выглядеть так: http://mysite.com/vote/30?token=AZERTYUHQNWGST
  • Когда вызывается страница голосования:
    • Проверьте, присутствует ли токен в URL
    • Проверьте, присутствует ли он в сеансе пользователя
    • Если нет => не регистрировать голос

Идея есть:

  • Жетоны не имеют большой продолжительности жизни, и их трудно угадать
  • Что означает ваш атакующий :
    • имеет только окно в несколько минут, в течение которого его инъекция будет действительна
    • должен уметь угадывать ^^
    • придется создавать разные страницы для каждого пользователя.


Также обратите внимание, что чем короче сеанс пользователя остается активным после того, как он покинул ваш сайт, тем меньше риск того, что он остается действительным, когда он посещает плохой сайт.

Но здесь вы должны выбрать между безопасностью и простотой использования ...


Другая идея (это не совсем безопасно, но помогает против парней, которые не знают, как заставить запрос POST) , будет принимать запросы POST только тогда, когда люди голосуют:

  • Браузер отправляет GET-запросы на введенные теги
  • Поскольку этот URL изменяет некоторые данные, он не должен работать с GET, а только с POST

Но учтите, что это не совсем безопасно: (возможно?) можно принудительно / подделать POST-запрос с небольшим количеством Javascript.

19 голосов
/ 26 марта 2010

Во-первых, запрос GET не должен использоваться для изменения состояний на сервере, поэтому для вашей службы голосования я бы порекомендовал POST / PUT. Это только руководство, но умное.

Итак, к вашему вопросу, CSRF - это проблема клиента, поэтому не имеет значения, какой язык сервера вы используете (PHP в вашем случае). Стандартное исправление такое же и выглядит так: иметь случайное значение в URI / POST-данных и такое же значение в заголовке Cookie. Если эти совпадения, вы можете быть уверены, что нет CSRF. Есть много информации о том, как это можно сделать здесь, на StackOverflow, например. это .
Удачи!

5 голосов
/ 04 февраля 2013

OWASP имеет CSRFGuard для PHP и ESAPI для PHP, которые я написал давно для XMB -> UltimaBB -> GaiaBB.

http://code.google.com/p/gaiabb-olpc/source/search?q=function+get_new_token&origq=function+get_new_token&btnG=Search+Trunk

Кажется, что некоторые другие очистили этот код и учли более сильные токены:

https://www.owasp.org/index.php/PHP_CSRF_Guard

спасибо, Andrew

1 голос
/ 28 декабря 2018

В CSRF-атаке 3 игрока

  1. веб-сайт жертвы (ваш голосующий веб-сайт в вашем примере) [ знает, что он вошел в файлы cookie пользователей ]
  2. браузер вашего клиента (пока он вошел в систему) [ знает свои куки ]
  3. веб-сайт злоумышленника [ Не знает cookie-файлы зарегистрированных пользователей ]

CSRF-атаки зависят от 2 фактов

  1. браузеры отправляют куки автоматически при каждом запросе
  2. мы используем файлы cookie для идентификации наших вошедших в систему пользователей (например, setcookie("sessionID", "0123456789ABCDEF", time()+3600);)

Если злоумышленник может на месте или другой сделать вошедшего в систему пользователя запрашивает это

// http://victim.website/vote/30

например, разместив ссылку на веб-сайте злоумышленника или отправив ее по электронной почте, клиентский браузер, вошедший в систему, отправит идентифицирующие файлы cookie (sessionID) вместе с этим запросом, который заставит веб-сайт жертвы думать, что он вошел пользователь действительно хочет проголосовать!

Но если веб-сайт жертвы более умный и проверяет запросы своих вошедших в систему пользователей с дополнительным параметром GET или POST (не cookie), у злоумышленника возникла проблема, поскольку параметры GET и POST не отправляются браузерами автоматически. и он должен угадать это.

// http://victim.website/vote/30?csrfSecret=0123456789ABCDEF

Злоумышленнику не известен параметр csrfSecret, который является секретом между веб-сайтом жертвы и его клиентом (как и токен сеанса), поэтому злоумышленник не может создать URL-адрес, по которому он хочет подделать запрос. .

Точно так же, если голосование выполняется с помощью запросов POST, злоумышленник не сможет создать форму на своем веб-сайте (или стороннем веб-сайте), поскольку он не знает секрета между веб-сайтом жертвы и его пользователями.

<form method="post" action="http://victim.website/vote" >
    <input type="hidden" name="vote" value="30">
    <input type="hidden" name="csrfSecret" value="????? I don't know it :(">
</form>
...