Firefox против CSP

Есть такая штука Content-Security-Policy — набор заголовков HTTP, описывающий, чего не должен делать браузер на этой конкретной странице. Очень полезная, надо сказать, штука, если хочется в безопасность как-то.

Например, мы хотим у себя на сайте внедрить JavaScript. Ну, то есть мы его, конечно, не хотим (потому что ну его нафиг), но без него как-то никак пока. Даже у меня вот тут в блоге есть два: один показывает webmention’ы, другой — симпатичные иконки из Font Awesome. Понятно, что JavaScript, особенно в сочетании со сторонним контентом (теми же webmention’ами) — потенциальная дыра в безопасности, поэтому очень хочется, чтобы никакой лишний-посторонний JavaScript у нас на сайте не запускался… Для этого, собственно, и придумали CSP.

На сегодняшний день в статусе рекомендованных CSP версии 2, и все основные бреузеры в неё умеют. Достаточно добавить HTTP-заголовок типа

Content-Security-Policy:
    script-src 'self' https://use.fontawesome.com;

— и всё, браузер будет загружать скрипты только с нашего домена или с Font Awesome, которым мы доверяем.

Но если с нашим собственным доменом (в условиях TLS, разумеется) душа на этом более или менее успокаивается, то со сторонним, будь он хоть трижды уважаемым, есть две проблемы: во-первых, где гарантия, что их не взломают и не подменят скрипт, а во-вторых, где гарантия, что DNS не подменят? Для этого существует SRI: мы указываем браузеру хеш проверенного скрипта, и если скрипт окажется подменён, браузер его не запустит. В CSP второй версии можно указывать разрешённые хеши, но только для локальных скриптов; с третьей версии (она пока в состоянии черновика, но браузеры начинают потихоньку поддерживать) в CSP разрешается полноценно использовать хеши и для сторонних скриптов.

Но если просто добавить хеши наших двух скриптов,

Content-Security-Policy:
script-src 'self' https://use.fontawesome.com
'sha384-VVrFY3uko2fhW05NnpTajhCiU9sSLrjZhyJ5K7w0mMphl16HrWuxyfAfMhC4G+xK'
'sha384-ZbbbT1gw3joYkKRqh0kWyRp32UAvdqkpbLedQJSlnI8iLQcFVxaGyrOgOJiDQTTR';

ничего не поменяется: браузер ведь не знает, что мы имеем в виду CSP третьей версии. Для второй версии такой заголовок тоже вполне валиден, и переводится как «загружать локальные скрипты только с этими двумя хэшами, и любые скрипты с Font Awesome». Разумеется, пока вторая версия — рекомендованная, нормальный браузер предпочтёт ошибиться в её пользу, а не сломать сайт, пытаясь проверить сторонние скрипты на соответствие хешам.

Как быть? Элементарно: добавим в заголовок атрибут, который есть только в третьей версии CSP — тогда браузер, который третью версию поддерживает, игнорирует наши указания на сайты (обязан по стандарту), и загрузятся только те два скрипта, которые нам нужны.

Content-Security-Policy:
script-src 'self' https://use.fontawesome.com
'sha384-VVrFY3uko2fhW05NnpTajhCiU9sSLrjZhyJ5K7w0mMphl16HrWuxyfAfMhC4G+xK'
'sha384-ZbbbT1gw3joYkKRqh0kWyRp32UAvdqkpbLedQJSlnI8iLQcFVxaGyrOgOJiDQTTR'
'strict-dynamic';

Сам по себе атрибут 'strict-dynamic' погоды не сделает: он означает, что тем скриптам, которые прошли проверку хеша, можно загружать другие скрипты — иногда это может быть даже полезно. Дополнительной дыры в безопасности тут нет: мы ведь читали скрипт перед тем, как вычислили хеш, и знаем, что он будет делать — неожиданности исключены.

Одна беда: с таким заголовком скрипт с Font Awesome в Firefox не загрузится. Почему? Потому, что разработчики Firefox думают жопой.

Firefox, видите ли, поддерживает CSP версии 3 только частично. В том смысле, что 'strict-dynamic' он понимает, и догадывается, что имеет дело с CSP третьей версии. И знает, что в третьей версии указания на сайты надо игнорировать. А вот проверять хеши сторонних скриптов, заявленные в CSP, Firefox пока не научился. Поэтому он и по адресу сайта скрипт не разрешит, и по хешу не пропустит — и не будет нашему пользователю иконок!

У них этот баг три года в трекере висит. Три месяца назад кто-то там даже догадался, что это ломает сайты, придерживающиеся стандарта, и что надо с этим что-то делать. Но на большее, чем перевести баг из класса “defect” в класс “task”, их пока не хватило.

При Айке у них такой фигни не было…