TLS v1.3 0-RTT replay attack
Nov. 10th, 2018 02:56 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Зашёл почитать, что новенького есть в новой версии шифровального протокола TLS, в девичестве SSL (которая скоро будет использоваться всякий раз, когда мы жмём на линк с https:// и во многих, многих других случаях) - и был крайне сильно шокирован. Прямо в RFC, официальном документе, описывающем протокол, открытым текстом признаётся, что он подвержен replay attack, и что борьба с этим - ответственность разработчиков серверного приложения: вот вы, мол, и постройте систему так, чтобы это было неважно.
Replay attack - это такая штука: допустим, клиент посылает серверу какую-то команду. Я в качестве хакера не могу её прочесть (она зашифрована), не могу её подменить на лету (сервер это обнаружит при расшифровке) - но зато я могу её записать и затем повторить серверу несколько раз, добиваясь непредусмотренных эффектов. Например, чтобы инструкция "перечислить на этот счёт N сантиков" выполнилась много раз. Или инструкция "стереть последнюю запись в таблице базы данных". Или чтобы сервер решил, что это некое нечестное поведение со стороны клиента и заблокировал его. Возможны многие варианты.
Вкратце, потому что её авторы решили добиться, чтобы протокол работал быстрее. Во всех предыдущих версиях при установлении новой сессии клиент и сервер менялись случайными значениями и вычисляли общий ключ на основе их обоих - и числа, выбранного клиентом, и числа, выбранного сервером. То же самое происходило в случае, если клиент и сервер некоторое время не общались и затем хотели восстановить уже когда-то установленную сессию - это требовало меньшего количества сообщений между ними, могло происходить быстрее и с меньшими вычислениями в процессе, но принцип оставался тот же: каждый из них посылал новое случайное число другому, на основе этих чисел и предыдущего ключа рассчитывался на обеих сторонах новый ключ, и дальше использовался для шифрования посылаемых байтов.
И лишь после этого шёл обмен сообщениями на уровне приложения - будь то HTTP, SMTP или что угодно.
Это позволяло и добиться большей степени случайности, что важно для стойкости шифрования (мы не полагаемся на генератор случайных чисел лишь одной из сторон), и предотвращало replay-атаки. Каким образом? А вот каким: допустим, у нас не один сервер, а много, целая ферма, кластер. Если клиент послал запрос на установление TLS-сессии, а хакер этот запрос размножил и послал нескольким серверам, то каждый сервер выберет своё случайное число, рассчитает свой ключ и будет ожидать поток байт, зашифрованных этим ключом. И если после того, как клиент пошлёт запрос уровня приложения, хакер его тоже размножит - то все сервера, кроме одного, обнаружат, что их ключ не подходит, и выполнять этот запрос не станут.
Но это требовало большей задержки: вначале должен был состояться этот обмен, и лишь затем можно было посылать собственно полезную нагрузку - скажем, HTTP-запрос.
В TLS v1.3 решили это оптимизировать: допустим, наш клиент с сервером некогда уже общались, у них уже есть некий общий ключ, и сейчас они хотят восстановить общение. А давайте мы будем разом посылать и запрос на восстановление TLS-сессии, и запрос на уровне приложения, зашифрованный этим старым ключом! Одним пакетом! А там уже ключ новый выберем.
Называется эта фича 0-RTT (от zero round trip time). Прекрасная идея. Да только это значит, что теперь сервер, получивший такой пакет, должен сразу же приложению передать запрос на исполнение. И если хакер такой пакет размножил и послал многим серверам - то все они должны сделать то же. Вот вам и replay attack.
IETF предлагают перевалить ответственность на уровень приложения: пусть, мол, сервера между собой координируются на уровне идеальном, либо пусть апликативный протокол с реплеем борется (скажем, каждый запрос к приложению со своим уникальным идентификатором идёт), либо клиент пусть посылает таким образом лишь те запросы, которым реплей не страшен.
В дополнении к стандарту это оформлено в категоричном тоне:
- аппликативный протокол ОБЯЗАН использовать 0-RTT лишь для тех сообщений, реплей которых не опасен,
- участники TLS-сессии ОБЯЗАНЫ использовать 0-RTT лишь в случае, если приложение (клиентское либо серверное) этого требует в явном виде.
Ну во-первых, она ломает принцип разделения и взаимной прозрачности протокольных уровней, благодаря которому Интернет и состоялся. Протокол локальной сети носит фреймы между компьютерами, протоколу IP над ним неинтересно, как именно работает локальная сеть, он глобальной доставкой занимается, а протокол TCP обеспечивает надёжную передачу потока байт поверх IP, не заморачиваясь, как именно работает IP, какой он версии, и так далее. Вся идея SSL состояла в том, что мы можем взять любое TCP-соединение и превратить его в защищённое, и для аппликаций-потребителей этого соединения это будет прозрачно. А теперь здрасте: весь дизайн аппликативного уровня, серверная архитектура, поведение клиентских приложений - всё должно зависеть от таких нюансов TLS, которые без поллитры не объяснишь! Спасибо, родные, что хоть не от Ethernet!
Во-вторых, это крайне малореально воплотить:
клиенты и сервера разрабатываются, как правило, совершенно разными людьми, и разработчики клиента, как правило, просто понятия не имеют, и не могут иметь, как сервер справляется с replay-атаками и справляется ли вообще.
и сервер, и клиент могут представлять собой очень многослойные приложения, где разные слои пишутся опять таки совершенно разными людьми. Браузер пишут одни, а HTML-страницы с Джаваскриптом -другие. Веб-сервер, такой как IIS или Tomcat, пишут третьи, а конкретное веб-приложеньице на нём - четвёртые. У разработчиков Джаваскрипта или кода ASP.NET, бегущего поверх IIS, нет и не может быть понимания, как клиентские запросы посылаются поверх TLS - каждый через отдельную сессию, или по множеству через одну, или параллельно через многие, и используется ли быстрое восстановление сессий или нет.
да и разработчикам достаточно тонких приложений это знание взять как правило неоткуда - они просто используют готовые библиотеки и не заморачиваются.
да и вообще требовать от программистов, не специализирующихся на секьюрити, чтобы они держали в голове такую штуку, как реплей-атака, и любой запрос проверяли на её возможность - совершенно бесполезное зверство.
И ты ды. В лучшем случае это категоричное "ОБЯЗАНЫ" просто приведёт к тому, что 0-RTT использоваться не будет практически никогда - что лишь к лучшему. Но я боюсь худшего: что она будет по дефолту включена и в серверах, и в браузерах, и разных клиентских библиотеках - потому как performance же! скорость же! - и в большинстве случаев так включённой и останется.
В обсуждениях этой дырки довольно часто звучит умное слово "идемпотентность", что означает свойство запросов не изменять состояние сервера. Типа, запрос только на чтение не опасен - ну выполнится он несколько раз и выполнится. No big deal.
Подобным образом подошли к вопросу CloudFlare (CDN, сеть-посредник между сайтами и их клиентами). Они включили 0-RTT для всех бесплатных клиентов (без возможности отмены, как я понял) - но зато принимают они лишь посланные таким HTTP-запросы типа GET и без параметров. Из расчёта, что реплей таких запросов не опасен.
Но логика эта очень сомнительна. Что с того, что спецификации типа REST требуют, чтобы запросы GET были лишь на чтение? Тьма сайтов и приложений могут руководствоваться другими принципами и нажатие на URL вида /path/to/object/delete может запросто изменять состояние сервера - вот вам чисто GET без параметров. Да и даже если "реального" изменения не происходит - могут быть эффекты второго порядка: скажем, юзер может быть заблокирован за повторные обращения, может быть включена ложная тревога, да мало ли.
Ну и наконец, не стоит забывать, что хотя HTTP - наиболее частый потребитель TLS, он далеко не единственный. Как насчёт POP3, IMAP, LDAP, SMTP? Протоколов аппликативного уровня может быть бесконечное множество, перелагать на них ответственность за дырку - феерическая безответственность.
В общем, брат-админ, где фичу 0-RTT увидишь - там её и прибей.
P.S. Полезные ссылочки: 1, 2, 3.
О чём вообще речь?
Replay attack - это такая штука: допустим, клиент посылает серверу какую-то команду. Я в качестве хакера не могу её прочесть (она зашифрована), не могу её подменить на лету (сервер это обнаружит при расшифровке) - но зато я могу её записать и затем повторить серверу несколько раз, добиваясь непредусмотренных эффектов. Например, чтобы инструкция "перечислить на этот счёт N сантиков" выполнилась много раз. Или инструкция "стереть последнюю запись в таблице базы данных". Или чтобы сервер решил, что это некое нечестное поведение со стороны клиента и заблокировал его. Возможны многие варианты.
Почему это возможно в новой версии TLS?
Вкратце, потому что её авторы решили добиться, чтобы протокол работал быстрее. Во всех предыдущих версиях при установлении новой сессии клиент и сервер менялись случайными значениями и вычисляли общий ключ на основе их обоих - и числа, выбранного клиентом, и числа, выбранного сервером. То же самое происходило в случае, если клиент и сервер некоторое время не общались и затем хотели восстановить уже когда-то установленную сессию - это требовало меньшего количества сообщений между ними, могло происходить быстрее и с меньшими вычислениями в процессе, но принцип оставался тот же: каждый из них посылал новое случайное число другому, на основе этих чисел и предыдущего ключа рассчитывался на обеих сторонах новый ключ, и дальше использовался для шифрования посылаемых байтов.
И лишь после этого шёл обмен сообщениями на уровне приложения - будь то HTTP, SMTP или что угодно.
Это позволяло и добиться большей степени случайности, что важно для стойкости шифрования (мы не полагаемся на генератор случайных чисел лишь одной из сторон), и предотвращало replay-атаки. Каким образом? А вот каким: допустим, у нас не один сервер, а много, целая ферма, кластер. Если клиент послал запрос на установление TLS-сессии, а хакер этот запрос размножил и послал нескольким серверам, то каждый сервер выберет своё случайное число, рассчитает свой ключ и будет ожидать поток байт, зашифрованных этим ключом. И если после того, как клиент пошлёт запрос уровня приложения, хакер его тоже размножит - то все сервера, кроме одного, обнаружат, что их ключ не подходит, и выполнять этот запрос не станут.
Но это требовало большей задержки: вначале должен был состояться этот обмен, и лишь затем можно было посылать собственно полезную нагрузку - скажем, HTTP-запрос.
В TLS v1.3 решили это оптимизировать: допустим, наш клиент с сервером некогда уже общались, у них уже есть некий общий ключ, и сейчас они хотят восстановить общение. А давайте мы будем разом посылать и запрос на восстановление TLS-сессии, и запрос на уровне приложения, зашифрованный этим старым ключом! Одним пакетом! А там уже ключ новый выберем.
Называется эта фича 0-RTT (от zero round trip time). Прекрасная идея. Да только это значит, что теперь сервер, получивший такой пакет, должен сразу же приложению передать запрос на исполнение. И если хакер такой пакет размножил и послал многим серверам - то все они должны сделать то же. Вот вам и replay attack.
Что предлагается?
IETF предлагают перевалить ответственность на уровень приложения: пусть, мол, сервера между собой координируются на уровне идеальном, либо пусть апликативный протокол с реплеем борется (скажем, каждый запрос к приложению со своим уникальным идентификатором идёт), либо клиент пусть посылает таким образом лишь те запросы, которым реплей не страшен.
В дополнении к стандарту это оформлено в категоричном тоне:
- аппликативный протокол ОБЯЗАН использовать 0-RTT лишь для тех сообщений, реплей которых не опасен,
- участники TLS-сессии ОБЯЗАНЫ использовать 0-RTT лишь в случае, если приложение (клиентское либо серверное) этого требует в явном виде.
Почему это скверная идея?
Ну во-первых, она ломает принцип разделения и взаимной прозрачности протокольных уровней, благодаря которому Интернет и состоялся. Протокол локальной сети носит фреймы между компьютерами, протоколу IP над ним неинтересно, как именно работает локальная сеть, он глобальной доставкой занимается, а протокол TCP обеспечивает надёжную передачу потока байт поверх IP, не заморачиваясь, как именно работает IP, какой он версии, и так далее. Вся идея SSL состояла в том, что мы можем взять любое TCP-соединение и превратить его в защищённое, и для аппликаций-потребителей этого соединения это будет прозрачно. А теперь здрасте: весь дизайн аппликативного уровня, серверная архитектура, поведение клиентских приложений - всё должно зависеть от таких нюансов TLS, которые без поллитры не объяснишь! Спасибо, родные, что хоть не от Ethernet!
Во-вторых, это крайне малореально воплотить:
И ты ды. В лучшем случае это категоричное "ОБЯЗАНЫ" просто приведёт к тому, что 0-RTT использоваться не будет практически никогда - что лишь к лучшему. Но я боюсь худшего: что она будет по дефолту включена и в серверах, и в браузерах, и разных клиентских библиотеках - потому как performance же! скорость же! - и в большинстве случаев так включённой и останется.
Частный случай
В обсуждениях этой дырки довольно часто звучит умное слово "идемпотентность", что означает свойство запросов не изменять состояние сервера. Типа, запрос только на чтение не опасен - ну выполнится он несколько раз и выполнится. No big deal.
Подобным образом подошли к вопросу CloudFlare (CDN, сеть-посредник между сайтами и их клиентами). Они включили 0-RTT для всех бесплатных клиентов (без возможности отмены, как я понял) - но зато принимают они лишь посланные таким HTTP-запросы типа GET и без параметров. Из расчёта, что реплей таких запросов не опасен.
Но логика эта очень сомнительна. Что с того, что спецификации типа REST требуют, чтобы запросы GET были лишь на чтение? Тьма сайтов и приложений могут руководствоваться другими принципами и нажатие на URL вида /path/to/object/delete может запросто изменять состояние сервера - вот вам чисто GET без параметров. Да и даже если "реального" изменения не происходит - могут быть эффекты второго порядка: скажем, юзер может быть заблокирован за повторные обращения, может быть включена ложная тревога, да мало ли.
Ну и наконец, не стоит забывать, что хотя HTTP - наиболее частый потребитель TLS, он далеко не единственный. Как насчёт POP3, IMAP, LDAP, SMTP? Протоколов аппликативного уровня может быть бесконечное множество, перелагать на них ответственность за дырку - феерическая безответственность.
В общем, брат-админ, где фичу 0-RTT увидишь - там её и прибей.
P.S. Полезные ссылочки: 1, 2, 3.