D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Приветствую вас
После прочтения статьи https://xss.is/threads/113044/ я решил проверить свои навыки и решить задачи которые предоставил Тинькофф.
Первая задача, довольно легкая решается за 15 минут, все дело в пути картинки он содержит SHA-256 шифрование, он же и ID, генерируем SHA-256 значение 3 и получаем флаг, это не интересно
Решил задачи Проклятый старый сайт и Мисс Фрод, там надо кодить и это не интересно, банальные задачи для кодера, мало хакерства.
А вот задача Дип-взлом уже из разряда "Хакера"
В чем суть надо получить доступ в профиль преподавателя для этого нам дают исходный код платформы.
После регистрации нас перебросит суда https://t-cutaway-o78hk6as.spbctf.net/card
Для идентификации пользователя используется метод JWT
Код: Скопировать в буфер обмена
Внутри мы видим
Код: Скопировать в буфер обмена
Сам JWT использует алгоритм HS256. Первая мысль найти токен JWT в исходниках.
Посмотрев исходники находим такой файл config.py
Код: Скопировать в буфер обмена
SECRET_KEY использует рандомный ключ 10 байт. Эти байты могут содержать любые значения от 0 до 255. Взлом ключа пару десятков лет, явно вариант не наш.
Дальше анализируем код и находим файл для регистрации.
А именно вот это строка
Код: Скопировать в буфер обмена
Эта строка пропускает почти любые символы без экранизации, а значит можно вставить XSS
Спойлер: XSS
У нас есть хранимая XSS и если профиль можно было просматривать, то мы бы скормили наш профиль преподавателю и украли бы его JWT сессию, но увы ищем дальше.
Как мы теперь поняли поле descriptions является уязвимым местом, но попытки провести SQL инъекции не привели к чтению базы данных. И стоит еще посмотреть исходники.
После не долгих поисков находим тот самый заветный файл
Спойлер: Много кода
Код: Скопировать в буфер обмена
В этом коде очень много интересного.
1.
Код: Скопировать в буфер обмена
Это строка содержит профиль преподавателя , в нем есть его логин, ID и наш флаг.
2. Теперь мы знаем как мы записываемся в базе данных
3. А вот и уязвимая строка
Эта строка использует метод write объекта файла для записи данных в файл базы данных без какой-либо проверки или обработки. Поля first_name, second_name, username, hashed_password, profile_id и descriptions вставляются в файл в том виде, в котором они были получены, без фильтрации или экранирования.
Как видно, у нас есть ID преподавателя e44341f22af741240fea9c98277e1430 если мы сможем зарегистрироваться с таким ID то получим доступ в его профиль.
Атака на JWT не пройдет, а значит мы должны записать в базу данных нового пользователя с нужным ID.
Если еще не забыли поле descriptions не фильтрует данные и записывает все напрямую в базу данных, таким образом мы можем записать нового пользователя в базу данных.
Приступим к реализации.
Name:secondName:LoginHackersXSSis:$2a$12$iA3ISW2OwVh2TzngJI3RfOjM9EpOixPtqauhtpDsdhYOyVR0O6V/S:e44341f22af741240fea9c98277e1430:XSSis
Что мы сделали. Написали первое имя , второе имя указали логин LoginHackersXSSis потом зашифровали наш пароль в формате bcrypt, есть много сайтов для шифрования онлайн, взяли ID преподавателя и заполнили поле profile.descriptions, всё через разделитель. Пора пробовать в деле.
Спойлер: Регистрация
В поле регистрации указываем любые данные для регистрации, они нам не нужны.
Авторизуемся мы уже по другим данным логин LoginHackersXSSis пароль LoginHackersXSSis
Спойлер: Флаг получен
Я считаю, что эта задача была интересная и действительно встречается на сайтах. Такая уязвимость была на сайте 4lapy.ru во время регистрации мы могли указать произвольный ID в адресной строке и активировать профиль, таким образом получить доступ в любой профиль.
Что думаете по поводу этого задания, пиши в комментариях.
После прочтения статьи https://xss.is/threads/113044/ я решил проверить свои навыки и решить задачи которые предоставил Тинькофф.
Первая задача, довольно легкая решается за 15 минут, все дело в пути картинки он содержит SHA-256 шифрование, он же и ID, генерируем SHA-256 значение 3 и получаем флаг, это не интересно
Решил задачи Проклятый старый сайт и Мисс Фрод, там надо кодить и это не интересно, банальные задачи для кодера, мало хакерства.
А вот задача Дип-взлом уже из разряда "Хакера"
В чем суть надо получить доступ в профиль преподавателя для этого нам дают исходный код платформы.
После регистрации нас перебросит суда https://t-cutaway-o78hk6as.spbctf.net/card
Для идентификации пользователя используется метод JWT
Код: Скопировать в буфер обмена
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9maWxlX2lkIjoiNzZlYzEwYTY3ZDkxNWVlMjBhMjYzNWFmNTM1YTNkZDMiLCJleHAiOjE3MTQ1MDAyMzR9.RWsCLkdicYJ1zJrq0pgSpmyy0cINHGUr-j-3LevDkpI
Внутри мы видим
Код: Скопировать в буфер обмена
Код:
{
"profile_id": "76ec10a67d915ee20a2635af535a3dd3",
"exp": 1714500234
}
Посмотрев исходники находим такой файл config.py
Код: Скопировать в буфер обмена
Код:
import os
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
BASE_DIR: str = os.path.abspath(os.path.dirname(__file__))
DATABASE_FILE: str = os.getenv("DATABASE_FILE", "database/profiles.txt")
SECRET_KEY: bytes = os.getenv("SECRET_KEY", os.urandom(10))
RECAPTCHA_SECRET_KEY: str = os.getenv("RECAPTCHA_SECRET_KEY")
settings = Settings()
Дальше анализируем код и находим файл для регистрации.
А именно вот это строка
Код: Скопировать в буфер обмена
Код:
@field_validator('descriptions')
def validate_descriptions(cls, v):
pattern = re.compile(r"^[\S ]*$")
if not pattern.match(v):
raise PydanticCustomError(
"string_pattern_mismatch",
"Поле должно соответствовать шаблону '{pattern}'",
dict(pattern=pattern.pattern))
Спойлер: XSS
У нас есть хранимая XSS и если профиль можно было просматривать, то мы бы скормили наш профиль преподавателю и украли бы его JWT сессию, но увы ищем дальше.
Как мы теперь поняли поле descriptions является уязвимым местом, но попытки провести SQL инъекции не привели к чтению базы данных. И стоит еще посмотреть исходники.
После не долгих поисков находим тот самый заветный файл
Спойлер: Много кода
Код: Скопировать в буфер обмена
Код:
import re
import hashlib
from typing import Optional
from datetime import datetime, timedelta, UTC
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from passlib.context import CryptContext
from jose import JWTError, jwt
from app.config import settings
from app.schemas import ProfileCreateSchema, ProfileLoginSchema, ProfileDataSchema
ALGORITHM = "HS256"
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/login")
class Database:
def __init__(self, file_path):
self.file_path = file_path
with open(self.file_path, 'a') as file:
pass
if not self.check_exists('prepRod1970'):
with open(self.file_path, 'a') as file:
file.write("Антон:Александрович:prepRod1970:<bcrypt-hash-REDACTED>:e44341f22af741240fea9c98277e1430:Мужчина в полном рассвете сил. ПрепРодаватель. Продам диплом на любую тему. Недорого. Ваш промокод на первую покупку: tctf{REDACTED}\n")
def register_profile(self, profile: ProfileCreateSchema) -> str:
hashed_password = pwd_context.hash(profile.password)
profile_id = hashlib.md5(profile.username.encode()).hexdigest()
with open(self.file_path, 'a') as file:
file.write(f"{profile.first_name}:{profile.second_name}:{profile.username}:{hashed_password}:{profile_id}:{profile.descriptions or ''}\n")
return profile_id
def login_profile(self, profile: ProfileLoginSchema) -> Optional[str]:
with open(self.file_path, 'r') as file:
for line in file.read().splitlines():
match = re.search(rf"^.*?:.*?:{profile.username}:(.*?):(.*?):.*?$", line)
if match:
hashed_password, profile_id = match.groups()
try:
if pwd_context.verify(profile.password, hashed_password):
return profile_id
except:
return None
return None
def check_exists(self, username: str) -> bool:
with open(self.file_path, 'r') as file:
for line in file.read().splitlines():
match = re.match(rf"^.*?:.*?:{username}:.*?:.*?:.*?$", line)
if match: return True
return False
def get_profile_data(self, profile_id: str) -> Optional[ProfileDataSchema]:
with open(self.file_path, 'r') as file:
for line in file.read().splitlines():
match = re.search(rf"^(.*?):(.*?):.*?:.*?:{profile_id}:(.*?)$", line)
if match:
first_name, second_name, descriptions = match.groups()
return ProfileDataSchema(first_name=first_name, second_name=second_name, descriptions=descriptions, profile_id=profile_id)
return None
db = Database(settings.DATABASE_FILE)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
to_encode = data.copy()
if expires_delta:
expire = datetime.now(UTC) + expires_delta
else:
expire = datetime.now(UTC) + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def validate_token(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM])
profile_id: str = payload.get("profile_id")
if profile_id is None:
raise credentials_exception
except JWTError:
raise credentials_exception
profile = db.get_profile_data(profile_id)
if not profile:
raise credentials_exception
return profile
1.
Код: Скопировать в буфер обмена
Код:
if not self.check_exists('prepRod1970'):
with open(self.file_path, 'a') as file:
file.write("Антон:Александрович:prepRod1970:<bcrypt-hash-REDACTED>:e44341f22af741240fea9c98277e1430:Мужчина в полном рассвете сил. ПрепРодаватель. Продам диплом на любую тему. Недорого. Ваш промокод на первую покупку: tctf{REDACTED}\n")
2. Теперь мы знаем как мы записываемся в базе данных
{profile.first_name}:{profile.second_name}:{profile.username}:{hashed_password}:{profile_id}:{profile.descriptions or ''}
3. А вот и уязвимая строка
file.write(f"{profile.first_name}:{profile.second_name}:{profile.username}:{hashed_password}:{profile_id}:{profile.descriptions or ''}\n"
Эта строка использует метод write объекта файла для записи данных в файл базы данных без какой-либо проверки или обработки. Поля first_name, second_name, username, hashed_password, profile_id и descriptions вставляются в файл в том виде, в котором они были получены, без фильтрации или экранирования.
Как видно, у нас есть ID преподавателя e44341f22af741240fea9c98277e1430 если мы сможем зарегистрироваться с таким ID то получим доступ в его профиль.
Атака на JWT не пройдет, а значит мы должны записать в базу данных нового пользователя с нужным ID.
Если еще не забыли поле descriptions не фильтрует данные и записывает все напрямую в базу данных, таким образом мы можем записать нового пользователя в базу данных.
Приступим к реализации.
Name:secondName:LoginHackersXSSis:$2a$12$iA3ISW2OwVh2TzngJI3RfOjM9EpOixPtqauhtpDsdhYOyVR0O6V/S:e44341f22af741240fea9c98277e1430:XSSis
Что мы сделали. Написали первое имя , второе имя указали логин LoginHackersXSSis потом зашифровали наш пароль в формате bcrypt, есть много сайтов для шифрования онлайн, взяли ID преподавателя и заполнили поле profile.descriptions, всё через разделитель. Пора пробовать в деле.
Спойлер: Регистрация
В поле регистрации указываем любые данные для регистрации, они нам не нужны.
Авторизуемся мы уже по другим данным логин LoginHackersXSSis пароль LoginHackersXSSis
Спойлер: Флаг получен
Я считаю, что эта задача была интересная и действительно встречается на сайтах. Такая уязвимость была на сайте 4lapy.ru во время регистрации мы могли указать произвольный ID в адресной строке и активировать профиль, таким образом получить доступ в любой профиль.
Что думаете по поводу этого задания, пиши в комментариях.