Содержание
«Социнженер»
Здесь сразу нужно оговориться, что этот способ годиться только для тех, кто мало что знает об интернете, но решил по каким-либо причинам зарегистрироваться в соц.сети. Как правило, это либо старики, либо дети.
Сбор информации
Прежде всего, нужно собрать как можно информации со страницы потенциальной жертвы, а именно:
«Умный» перебор
Метод, рассчитанный на среднестатистических пользователей. Для него нам надо будет специальный словарь, заточенный под конкретного человека. Давайте разберёмся со структурой нашего словаря:
Взлом ВКонтакте страницы
На данном этапе мы будем пробовать получить доступ к аккаунту цели. Рассмотрим самые простые приёмы:
The Logout script
Finally, Let’s create a logout.php file with the following code in it.
Это объект одной кнопки встроенной клавиатуры. Располагается набор таких кнопок сразу под сообщением. С помощью этого объекта можно передать кнопке url, или использовать его для голосования как одним из вариантов. В любом случае можно сделать много интересного и полезного.
- text– текст который будет на отображен на кнопке, обязательный параметр, поддерживает текст и смайлики (эмодзи)
- url– адрес на который будет направлен пользователь
- callback_data– строка 1-64 символа будет передан боту через объект CallbackQuery
- switch_inline_query– после нажатия будет предложен выбор чата где будет использован бот во встроенном режиме, пример @gif dog
- switch_inline_query_current_chat – после нажатия вставит команду для использования бота во встроенном режиме в текущем чате
- callback_game– описание игры, которая будет запущена при нажатии пользователем кнопки.
- pay – кнопка будет использована как кнопка оплаты
Этот объект представляет одну кнопку клавиатуры, располагающуюся под текстовым полем для отправки сообщения. Поставляется она в интерфейс приложения в наборе с другими аналогичными кнопками через объект ReplyKeyboardMarkup. В качестве параметров с ним можно передать:
- text – текст который будет на отображен на кнопке, обязательный параметр, поддерживает текст и смайлики (эмодзи)
- request_contact – если параметр установлен в true, пользователь отправит в бот свой номер телефона на который зарегистрирован аккаунт
- request_location – если параметр установлен в true, пользователь отправит в бот свое текущее местоположение
Если будет заполнен только обязательный параметр text, то при нажатии на кнопку будет отправлено текстовое сообщение с содержанием из этого параметра. Если будет включено дополнительно одно из необязательных параметров или локация, или контакт, то будет отправлен или контакт, или локация соответственно. Необязательные параметры взаимоисключающие.
Помимо набора кнопок в объект ReplyKeyboardMarkup можно передать еще ряд параметров:
- keyboard – массив кнопок (объекты KeyboardButton)
- resize_keyboard – если предать true, то клавиатура подгонится по высоте до возможного минимума.
- one_time_keyboard – возможность скрывать клавиатуру после ее использования
- selective – если надо показать клавиатуру только определенным пользователям
При необходимости удалить клавиатуру используйте объект ReplyKeyboardRemove.
Keycloak: настройки
В этой статье я не буду описывать, что такое KeyCloak и как он работает, предполагается что читатель все это знает, и его задача схожая с моей – использовать уже настроенный KeyCloak в своем веб-приложении в качестве решения для авторизации.
В рамках статьи основные исходные настройки KeyCloak следующие:
realm – myproject (Зарегистрированное в KeyCloak имя проекта для которого разрабатывается приложение)
client_id – myproject-app (Зарегистрированное в KeyCloak имя приложения)
Более подробно о этих параметрах – ниже.
Step 1: creating a login form in html
Below is the Login Form in HTML. Paste it in a file named login.php
Step 1: creating registration form in html
We will create a PHP file named register.php with the following code in it. This is a simple HTML form with some basic validation. If you are not familiar with HTML then you can get it from many online sites who give ready-made html5 login form templates.
Авторизация
Для авторизации клиента в BILLmanager используется функция auth со следующими параметрами:
Взаимодействие фронта с микросервисами. axios
Для взаимодействия с микросервисами будем использовать библиотеку AXIOS.
Поскольку у нас будут 2 типа запросов:авторизированные (для работы с микросервисами) и неавторизированные (для самой авторизации и refresh токена) – для каждого типа запроса будем использовать отдельный инстанс axios.
Итак, создадим утилитарный класс с нашими axios – AxiosInstance.js и добавим в него экземпляр axios для авторизированных запросов:
export const axiosInstance = axios.create({
headers: { Authorization: `Bearer ${getToken()}` }
});
Здесь мы добавили в заголовок параметр авторизации со значением – bearer-token. Он будет посылаться в каждом запросе.
Кроме того, перед каждым запросом с фронта нам придется проверять валидность токена и, в случае если он невалиден (истек срок действия например) – делать его refresh. После этого в запросе уйдет уже обновленный валидный token.
Итак, добавим вызов метода verifyToken в axios request interceptor:
axiosInstance.interceptors.request.use(request => {
verifyToken().catch(() => refreshToken(getRefreshToken()));
return request;
});
В случае, если токен невалиден, метод кидает исключение, в обработке которого вызываем обновление токена методом refreshToken, а в качестве его аргумента передаем refresh_token который хранится в нашем localStorage и доступный через утилитарный метод getRefreshToken.
Теперь осталось только проверить статус ответа в авторизированных запросах. Сделаем это через axios response interceptors:
axiosInstance.interceptors.response.use(response => {
if (response?.headers?.authorization) {
setToken(response.headers.authorization);
}
return response;
}, error => {
if (error?.response?.status === 401) {
logout();
}
return Promise.reject(error);
});
Детали
Что ж, с предысторией покончено, переходим к самому интересному — разбору уязвимости. В качестве подопытной версии я установил Joomla 3.6.3, поэтому все номера строк будут актуальны именно для этой версии. А все пути до файлов, которые ты увидишь далее, будут указываться относительно корня установленной CMS.
Заказ доменного имени
Ссылка на заказ доменного имени содержит обязательные параметры, размещаемые в ‘redirect’:
- checked_domain — список проверенных доменов с дополнительными параметрами. Формат записи следующий:
- В доменном имени знак ‘-‘ заменяется на ‘_’, знак ‘.’, заменяется на ‘____________’
- К полученному значению, с использованием разделителя ‘:’, добавляется статус домена:
- 0 — не проверен,
- 1 — свободен,
- 2 — занят,
- 3 — ошибка определения статуса,
- 4 — домен присутствует в BILLmanager.
- Для регистрации домена, в качестве статуса следует использовать значение — 1, для трансфера — 3
- К полученной строке добавляется код тарифного плана, с использованием разделителя ‘:’
К полученной строке добавляется флаг выбора доменного имени (0 — не выбран, 1 — выбран), с использованием разделителя ‘:’
- startform=domain.order.contact — перенаправление в BILLmanager для заполнения информации о контакте (паспортные данные, ИНН и т.п.).
- domain_action — действие с доменом. ‘register’ — регистрация нового домена, ‘transfer’ — трансфер домена.
- domain_name — имя регистрируемого домена.
- selected_domain — закодированное по вышеописанному алгоритму доменное имя.
- selected_pricelist — код выбранного тарифного плана.
При передаче нескольких значений в ‘checked_domain’, ‘selected_domain’ и ‘selected_pricelist’ значения разделяются через знак ‘,’.
Все указанные параметры следует экранировать при подстановке в параметр ‘redirect’:
Как получить информацию о всех тарифах
Чтобы получить информацию о всех тарифах, в том числе неактивных, используйте функцию pricelist.export с параметром pricelist.
Предварительно получите ID всех тарифов. Например, для этого можно использовать функцию pricelist:
Используйте функцию pricelist.export с параметром pricelist:
Клавиатуры и их возможности
Для управления ботом вы как создатель бота предполагаете какой-то сценарий. Пользователю чтобы получить от бота желаемый результат нужно отправить команду. Бот, когда получит команду обработает ее и отреагирует по установленному вами сценарию. Но представьте, что пользователь написал команду с ошибкой, бот в этом случае должен предупредить что команда не распознана и попросить попробовать еще раз.
В этом случае нам помогут клавиатуры. Ее нужно просто после старта диалога с ботом вывести на экран пользователю, и когда тот нажмет на нужную ему кнопку, боту отправиться заложенная вами правильная команда. В этом случае уходим от возможных ошибок, интерфейс бота выглядит более приветливо и профессионально. Качество бота повышается.
С каждым сообщением бот может отправлять разный набор кнопок как по количеству, так и по назначению. Кнопки можно расположить непосредственно под сообщением (InlineKeyboardButton), или зафиксировать под тестовым полем для ввода сообщения (KeyboardButton). Различия в них очень существенные на мой взгляд. Давайте рассмотрим некоторые их возможности.
Клиент для auth server
Создадим класс AuthClient.js с методом аутентификации и refresh токена:
Клиент для keycloak
Клиент для KeyCloak будет содержать 2 метода:
Оба метода будут возвращать класс org.keycloak.representations.AccessTokenResponse.class из подключенной ранее библиотеки. Структура класса соответствует спецификации OpenId Connect для Successful Token Response.
Код клиента будет выглядеть так:
Контроллеры
Далее нам нужно создать котроллер с методом для аутентификации AuthenticateController.java:
@RestController
@RequiredArgsConstructor
@RequestMapping("/authenticate")
public class AuthenticateController {
private final KeyCloakClient keyCloakClient;
@PostMapping
public ResponseEntity<AccessTokenResponse> authenticate(@RequestBody AuthRequestDto request) {
return ResponseEntity.ok(keyCloakClient.authenticate(request));
}
}
Конфигурация сервиса
Создадим проект auth server и добавим в него необходимые зависимости. Конфигурационный файл pom.xml будет выглядеть так:
Конфигурирование nginx
Поскольку фронт будет обращаться к нескольким микросервисам, нужна единая точка для взаимодействия фронта с ними. В качестве единой точки был выбран nginx.
Для того чтобы наш фронт смог общаться к микросервисами, нужно прописать для каждого из них location. В нашем случае приложение общается с двумя микросервисами:
Обход ограничения на загрузку неугодных файлов
Не могу не упомянуть о способе загрузки PHP-файлов, который был найден ребятами из Xiphos Research.
Исследуя описанные выше уязвимости, они столкнулись с такой проблемой: Joomla отклоняет загруженные файлы, содержащие <?php и файлы c опасными расширениями. Полный кусок кода, который проверяет файлы на вшивость, можно посмотреть в /libraries/joomla/filter/input.php:
Перенаправление на страницу заказа
Клиента можно перенаправлять на разные страницы заказа:
- страницу выбора тарифных планов. Клиенту остается выбрать тарифный план, период оплаты, параметры тарифа и произвести оплату;
- страницу заказа конкретного тарифного плана с выбранным периодом оплаты. Клиенту остается выбрать параметры тарифа и произвести оплату;
- страницу оплаты конкретного тарифа с выбранным периодом оплаты и выбранными параметрами. Клиенту остается произвести оплату.
Проверка промокода
На сайте можно разместить поле ввода промокода на скидку. Чтобы провести проверку наличия такого кода в биллинговой системе используется команда promotion.promocode.check:
Работа с токенами
Полученные от KeyCloak access_token и refresh_token будем хранить в localStorage. Создадим для работы с токенами и localStorage утилитарный класс TokenUtils.js:
Разграничение интерфейса по ролям
Для того чтобы предоставить на фронте доступ к интерфейсу/функционалу соответствующий каждой роли, нам остается вызвать в соответствующих местах ранее созданный метод TokenUtils.hasRole(‘role_name’).
Например, отрисовка элементов Tab будет выглядеть следующим образом:
Расчет стоимости заказа
Функция pricelist.calculate возвращает стоимость услуги на указанный период, учитывает все выбранные дополнения:
Чтобы включить стоимость дополнения в итоговую сумму заказа, в запрос добавляется параметр addon:
addon_XXX=VALUE — дополнительные ресурсы, где:
- XXX — Id дополнения к тарифному плану, ADDON_ID.
- VALUE — количество выделяемого ресурса.
Регистрация
Чтобы зарегистрировать клиента в BILLmanager, выполняется перенаправление пользователя с сайта на форму регистрации:
Для регистрации клиента на сайте, без перехода в BILLmanager, используется функция register, удаленный вызов которой зарегистрирует клиента в биллинговой системе. Функция имеет следующие параметры:
Сервис авторизации
Итак, первое что нужно для нашей задачи – сервис авторизации (далее auth server) осуществляющий авторизацию пользователей (предоставление access_token), продление токена (refresh_token) и валидацию токена. Сам сервис будет максимально простым и содержать минимум boilerplate кода, потому что большая его частьбудет переложена на “плечи” KeyCloak.
Поскольку сам auth server будет написан на Spring Boot, для его интеграции с KeyCloak будем использовать Spring Boot KeyCloak Adapter. Данное решение рекомендуют сами разработчики KeyCloak.
Ссылка на выбор тарифного плана
Ссылка на выбор тарифного плана по услуге выглядит следующим образом:
Параметры запроса:
- startpage — страница/список, которая будет открыта после регистрации/авторизации
- startform — форма заказа услуги, которая будет открыта после регистрации/авторизации
- pricelist — код тарифного плана
- project — код провайдера
Пример ссылки на выбор тарифного плана виртуального хостинга (startform=vhost.order):
Ссылка на заказ конкретного тарифа с выбранным периодом оплаты
Ссылки на заказ тарифных планов BILLmanager генерирует автоматически, их можно найти в разделе Тарифные планы → Изменить →вкладка Ссылки.
Так же можно передать значение дополнительно заказанных ресурсов в параметрах вида addon_N=xxx — где N — код дополнения, xxx — значение ресурса, смысл которого отличается в зависимости от типа дополнения:
- целочисленные дополнения — целочисленное значение заказываемого ресурса;
- дополнения, заданные перечислениями — код элемента перечисления;
- дополнения с типом учета “по выбору клиента” — код дополнения, входящего в состав основного дополнения. При этом значение заказываемого ресурса для этого дополнения так же передается в параметре вида addon_N
Код дополнения можно найти на странице Тарифные планы → Конфиг. → поле Id.
Ссылка на заказ тарифного плана с выбранными параметрами выглядит следующим образом:
Пример заказа тарифа виртуального хостинга (startform=vhost.order.param) с кодом 2 (pricelist=2). При этом значение дополнения “Лимит Web доменов” выставляем равным 15 (addon_5=15), значение дополнения “Дисковое пространство” выставляем равным 2048 (addon_3=2048)
Ссылка на оплату услуги
Если необходимо переадресовать клиента сразу в корзину или на оплату сделанного заказа, необходимо изменить следующие параметры: значение параметра startform меняется на quickorder, добавляется параметр redirect со значением basket (переадресация в корзину), или payment (переадресация на оплату заказа)
Ссылка, переадресовывающая клиента в корзину:
Ссылка, переадресовывающая клиента на страницу выбора метода оплаты услуги:
Страница авторизации
На странице авторизации создадим метод login и повесим его в качестве обработчика на кнопку «Войти».
AuthPage.js:
const login = async () => {
await authenticate(credentials.login, credentials.password)
.then(() => {
window.location.href = '/';
}).catch(() => setError(true));
};
Здесь мы вызываем созданный ранее метод authenticate, а в качестве обработчика успешного выполнения метода передаем редирект на корневой маппинг приложения (‘/’). В качестве обработчика ошибки выполнения метода передаем вызов функции setError, в моем случае – это просто установка хука error, означающего, что надо показать сообщение об ошибке.
Теперь немного практики
Предлагаю рассмотреть поближе возможности клавиатур. Для примера я сделаю 4 кнопки: 2 KeyboardButton и 2 InlineKeyboardButton. Для этого будем работать с теми же инструментами, которые были в предыдущей статье. Добавим только несколько новых методов.
Логика скрипта будет простая, при старте бот будет выводить сообщение о готовности и 2 кнопки Голосовать и Помощь. При нажатии на кнопку Помощь – выведется сообщение с небольшой инструкцией, а при нажатии на кнопку Голосовать на экране появиться сообщение и под ним 2 кнопки, при нажатии на которые произойдет увеличения счетчика.
Чтобы обрабатывать команды от всех типов кнопок нам надо составить условия проверки. В моем примере от кнопки KeyboardButton команда приходит в виде обычного текстового сообщения вместе с объектом Message под ключом text, а от кнопки InlineKeyboardButton в объекте CallbackQuery под ключом data.
Кнопки будем отправлять в параметре reply_markup в методе sendMessage. Набор кнопок идет в таком формате:
Массив данных (
'Тип клавиатуры' =>
Массив строк кнопок (
Массив кнопок строки (
Массив параметров кнопки (
)
)
),
// если предусмотрено
'Дополнительный параметр',
)
Создадим два метода для разных клавиатур, на входе принимают массив строк кнопок, на выходе строку в формате JSON.
Сам набор кнопок будет выглядеть вот так.
Во встроенных кнопках (inlineKeyboardButton) в качестве значения параметра callback_data будем передавать служебную информацию в виде action_type_count1_count2, где
- action – действие, в нашем случае это vote
- type – тип кнопки: 1 – левая, 0 – правая
- count1 – текущее значение левой кнопки
- count2 – текущее состояние правой кнопки
В значении параметра text передаем бинарный код эмодзи в кодировке UTF-8 (список эмодзи), которые преобразуются из шестнадцатеричных данных в двоичные данные функцией hex2bin, и еще выведем текущее числовое значение счетчика кнопки.
Теперь нам остается только описать механизм определения команды и механизм ее обработки. Поэтому в методе init() создадим конструкцию оператора switch
Давайте разберемся, что в этом коде происходит. Оператор switch принимает значение $message, которое может быть, как просто текст сообщения (в том числе команды), так и значение объекта callbackQuery.
- При получении команды /start – мы выводим приветственное слово и набор кнопок: Голосовать, Помощь.
- При получении команды Помощь, выводим простое текстовое сообщение.
- При получении команды Голосовать, выводим текстовое сообщение и набор встроенных кнопок с параметром callback_data.
- При получении не запланированного значения, выводим текстовое сообщение.
- И самое интересное это когда принимаем значение в начале которого стоит
action
, в нашем случае это vote. Мы формируем новую клавиатуру изменяем в ней текстовое значение, увеличиваем счетчик у нажатой кнопки и подставляем новую служебную информацию в параметрах callback_data.
Функция и параметры
Чтобы загружать информацию о тарифных планах автоматически из BILLmanager на сайт, используется функция pricelist.export, со следующими параметрами:
- elid=PROJECT_ID — Id провайдера в BILLmanager;
onlyavailable — вывести активные тарифы, которые доступны для заказа текущему пользователю. Может принимать значения “On” и “Off”.
- pricelist — Id тарифов через запятую, указывается только если нужно получить данные не всех тарифов;
- addonitemtype — Id типов продуктов дополнений к тарифным планам, указывается, если в выводе нужны не все дополнения;
- itemtype — Id типа продукта тарифов, которые нужно вывести, указывается, когда нужны тарифы только определенного типа;
- exclude_pricelist — Id тарифов через запятую, которые нужно исключить из вывода функции;
- othercurrency — ISO код валюты, в которую нужно пересчитать цены тарифов и дополнений, если требуется вывод в валюте отличной от валюты провайдера. Пересчет идет по текущему курсу в BILLmanager;
- out — формат вывода информации. Может принимать значения “json” и “xml”.
Функция возвращает стоимость тарифов и дополнений в выбранном формате.
В заключение
Для лаконичности статьи я максимально упростил технические решения, пытаясь акцентировать внимание на самом подходе.
Стоит отметить, что в случае взаимодействия фронта только с одним сервисом, стоит пересмотреть решение, исключив nginx, как инструмент для предварительной проверки авторизованности запроса (метод /validate вызываемый каждый раз перед вызовом сервиса). В таком случае весь механизм работы с spring-keycloak-adapter переносится в единственный сервис (back).
Также, возможно, полезно будут следующие материалы:
Заключение
Найденная уязвимость еще раз подтверждает, что иногда баги могут годами лежать на самом видном месте и не быть обнаруженными. Добавлю, что оперативно обратить внимание на эту уязвимость мне помог проект Vulners. Если вдруг кто не знает — это поисковик по всевозможному контенту, связанному с безопасностью. Вот, например, вся информация по обнаруженным уязвимостям Joomla.