#ruby-on-rails #api
#ruby-on-rails #API
Вопрос:
Я реализую аутентификацию / авторизацию КЛЮЧА API для проекта API, который я создаю. Я использую authenticate_or_request_with_http_token
вызов метода и защищаю от атак по времени. Я впервые прочитал о временных атаках и попытался реализовать эту безопасную защиту в моем ApplicationController
. Я считаю, что механизм выполнен правильно, но хотел бы получить подтверждение. Вот фрагмент кода.
before_action :authenticate_request
def authenticate_request
authenticate_or_request_with_http_token do |token, options|
api_key = ApiKey.find_by(access_token: token)
secret_key = ApiKey.find(api_key.id).secret_key
api_key.present? amp;amp; (api_key.active != false) amp;amp; secure_compare_with_hashing(api_key.secret_key, secret_key)
end
end
def secure_compare_with_hashing(key1, key2)
ActiveSupport::SecurityUtils.secure_compare(key1, key2)
end
В принципе, я 1). Поиск ключа Api по токену, представленному в заголовке, 2). Затем я нахожу использование идентификатора api_key для извлечения секретного ключа. 3). Я сравниваю секретный ключ обоих значений для сравнения постоянной времени.
Если я не ошибаюсь, я считаю, что это должно обеспечить успешное смягчение временных атак. Я прав в этом?
====== обновление ======
Для контекста я использую этот ресурс для решения проблемы предотвращения атак с ошибкой синхронизации. Вот небольшая цитата из онлайн-книги.
Способ, которым мы реализовали ключ API, уязвим для атак по времени. Вот почему многие веб-API поставляются с ключом доступа и секретным ключом. Ключ доступа можно использовать для поиска записи в базе данных перед выполнением безопасного сравнения секретных ключей.
Чтобы проверить, действителен ли ключ API безопасным способом, нам нужно получить запись базы данных без использования фактического токена. Действительно, выполнение SQL-запроса типа ApiKey.where(ключ: ‘abc’) уязвимо для атак с синхронизацией.
Чтобы избежать этого, мы можем попросить клиента предоставить нам другой способ поиска записи, например, отправив идентификатор ключа. В итоге мы получим api_key=1:abc вместо api_key=abc, где 1 — идентификатор ключа API.
Для реального приложения мы должны были создать новое поле для сохранения ключа доступа вместо использования идентификатора.
Вот код, который я пытаюсь адаптировать.
module Authentication
include ActiveSupport::SecurityUtils
# Hidden Code
included # Hidden Code
private
def validate_auth_scheme # Hidden Code
def unauthorized! # Hidden Code
def authorization_request # Hidden Code
def credentials
@credentials ||= Hash[authorization_request.scan(/(w )[:=] ?"?([w|:] )"?/)]
end
def api_key
@api_key ||= compute_api_key
end
def compute_api_key
return nil if credentials['api_key'].blank?
id, key = credentials['api_key'].split(':')
api_key = id amp;amp; key amp;amp; ApiKey.activated.find_by(id: id)
return api_key if api_key amp;amp; secure_compare_with_hashing(api_key.key, key)
end
def secure_compare_with_hashing(a, b)
secure_compare(Digest::SHA1.hexdigest(a), Digest::SHA1.hexdigest(b))
end
end
Мой код неправильно следует этой процедуре? Игнорируя проблемный случай, когда мой код не возвращает ключ, имеет ли смысл сравнение secret_key ?
Например,
api_key = ApiKey.find_by(access_token: token) #Timing attack :(
Я получаю секретный ключ, используя api_key, который я нашел.
secret_key = ApiKey.find(api_key.id).secret_key
Атака по времени обойдена.
secure_compare_with_hashing(api_key.secret_key, secret_key)
Ответ №1:
secure_compare
предотвращает временные атаки, преобразуя ваши строки в одинаково длинные хэши, так что все должно быть в порядке.
Тем не менее, мне интересно, что делает предыдущий код.
Этот фрагмент не извлекает одно и то же ApiKey
дважды? Также в вашей реализации есть ошибка, когда токен не существует.
api_key = ApiKey.find_by(access_token: token)
secret_key = ApiKey.find(api_key.id).secret_key # this will crash if token does not exist
secure_compare_with_hashing(api_key.secret_key, secret_key) # is always true, no?
Кроме того, это приведет к досрочному возврату. Хотя невозможно угадать весь пароль, все равно можно получить информацию, если, например, существует api_key .
api_key.present? amp;amp; (api_key.active != false)
## Редактировать
По сравнению с предоставленным вами руководством вы сделали именно то, что не должны делать.
def authenticate_request
authenticate_or_request_with_http_token do |token, options|
api_key = ApiKey.find_by(access_token: token) # <- TIMING ATTACK ON DB
# ...
end
end
Вместо этого вам следует указать идентификатор пользователя / ключа в параметрах и использовать его для извлечения ключа из базы данных. Я также изменил ваше условие, чтобы всегда выполнять безопасное сравнение и выполнять активную проверку в конце.
def authenticate_request
authenticate_or_request_with_http_token do |token, options|
api_key = ApiKey.find_by(id: options[:key_id])
secure_compare_with_hashing(api_keyamp;.secret_key.to_s, token) amp;amp;
api_key.active
end
end
Комментарии:
1. Привет, Кристиан, спасибо, что ответил на мой вопрос, не могли бы вы взглянуть на мое обновление? Помимо ошибки, из-за которой ключ не найден или отключен, я обошел атаку по времени? Я беру ключ, указанный в токене, ищу секретный ключ, а затем сравниваю секретный ключ. Я заметил, что безопасное сравнение всегда возвращает true, что показалось мне странным в примере, который я адаптирую.
2. В качестве еще одного обновления, по сути, я хочу, чтобы это было идеально изложено в книге. <«Вот почему многие веб-API поставляются с ключом доступа и секретным ключом. Ключ доступа можно использовать для поиска записи в базе данных перед выполнением безопасного сравнения секретных ключей «>
3. Вы в основном просто c amp; p фрагмент из книги, так что да, похоже, он не уязвим для временных атак. Если это ответило на ваш вопрос, пожалуйста, подумайте о принятии ответа.
4. Обновил мой первоначальный ответ.