Клавиатура как способ взаимодействия с ботом – Создание Telegram Bots своими руками на PHP и Node.js

Содержание

«Социнженер»

Здесь сразу нужно оговориться, что этот способ годиться только для тех, кто мало что знает об интернете, но решил по каким-либо причинам зарегистрироваться в соц.сети. Как правило, это либо старики, либо дети.

Сбор информации

Прежде всего, нужно собрать как можно информации со страницы потенциальной жертвы, а именно:

«Умный» перебор

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

Взлом ВКонтакте страницы

На данном этапе мы будем пробовать получить доступ к аккаунту цели. Рассмотрим самые простые приёмы:

The Logout script

Finally, Let’s create a logout.php file with the following code in it.

Inlinekeyboardbutton

Это объект одной кнопки встроенной клавиатуры. Располагается набор таких кнопок сразу под сообщением. С помощью этого объекта можно передать кнопке url, или использовать его для голосования как одним из вариантов. В любом случае можно сделать много интересного и полезного.

  1. text– текст который будет на отображен на кнопке, обязательный параметр, поддерживает текст и смайлики (эмодзи)
  2. url– адрес на который будет направлен пользователь
  3. callback_data– строка 1-64 символа будет передан боту через объект CallbackQuery
  4. switch_inline_query– после нажатия будет предложен выбор чата где будет использован бот во встроенном режиме, пример @gif dog 
  5. switch_inline_query_current_chat – после нажатия вставит команду для использования бота во встроенном режиме в текущем чате
  6. callback_game– описание игры, которая будет запущена при нажатии пользователем кнопки.
  7. pay – кнопка будет использована как кнопка оплаты

Keyboardbutton

Этот объект представляет одну кнопку клавиатуры, располагающуюся  под текстовым полем для отправки сообщения. Поставляется она в интерфейс приложения в наборе с другими аналогичными  кнопками через объект ReplyKeyboardMarkup. В качестве параметров с ним можно передать:

  1. text – текст который будет на отображен на кнопке, обязательный параметр, поддерживает текст и смайлики (эмодзи)
  2. request_contact – если параметр установлен в true, пользователь отправит в бот свой номер телефона на который зарегистрирован аккаунт
  3. request_location – если параметр установлен в true, пользователь отправит в бот свое текущее местоположение

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

Помимо набора кнопок в объект ReplyKeyboardMarkup можно передать еще ряд параметров:

  1. keyboard – массив кнопок (объекты KeyboardButton)
  2. resize_keyboard – если предать true, то клавиатура подгонится по высоте до возможного минимума.
  3. one_time_keyboard – возможность скрывать клавиатуру после ее использования
  4. 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. 

  1. Предварительно получите ID всех тарифов. Например, для этого можно использовать функцию pricelist: 

  2. Используйте функцию 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:

Больше про Хуавей:  Лучшие смартфоны Honor 2021 года - рейтинг ТОП-6

Перенаправление на страницу заказа

Клиента можно перенаправлять на разные страницы заказа: 

  • страницу выбора тарифных планов. Клиенту остается выбрать тарифный план, период оплаты, параметры тарифа и произвести оплату;
  • страницу заказа конкретного тарифного плана с выбранным периодом оплаты. Клиенту остается выбрать параметры тарифа и произвести оплату;
  • страницу оплаты конкретного тарифа с выбранным периодом оплаты и выбранными параметрами. Клиенту остается произвести оплату.

Проверка промокода

На сайте можно разместить поле ввода промокода на скидку. Чтобы провести проверку наличия такого кода в биллинговой системе используется команда 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 кнопки, при нажатии на которые произойдет увеличения счетчика.

Больше про Хуавей:  Обзор Huawei Honor 5C — Качественный и удобный телефон, не уступающий конкурентам

Чтобы обрабатывать команды от всех типов кнопок нам надо составить условия проверки. В моем примере от кнопки KeyboardButton команда приходит в виде обычного текстового сообщения вместе с объектом Message под ключом text, а от кнопки InlineKeyboardButton в объекте CallbackQuery под ключом data.

Кнопки будем отправлять в параметре reply_markup в методе sendMessage. Набор кнопок идет в таком формате:

Массив данных (
    'Тип клавиатуры' => 
        Массив строк кнопок (
            Массив кнопок строки (
                Массив параметров кнопки (
            
                )
            )
         ),
    // если предусмотрено 
    'Дополнительный параметр',
)

Создадим два метода для разных клавиатур, на входе принимают массив строк кнопок, на выходе строку в формате JSON.

Сам набор кнопок будет выглядеть вот так. 

Во встроенных кнопках (inlineKeyboardButton) в качестве значения параметра callback_data будем передавать служебную информацию в виде action_type_count1_count2, где 

  1. action – действие, в нашем случае это vote
  2. type – тип кнопки: 1 – левая, 0 – правая
  3. count1 – текущее значение левой кнопки
  4. count2 – текущее состояние правой кнопки

В значении параметра text передаем бинарный код эмодзи в кодировке UTF-8 (список эмодзи), которые преобразуются из шестнадцатеричных данных в двоичные данные функцией hex2bin, и еще выведем текущее числовое значение счетчика кнопки.

Теперь нам остается только описать механизм определения команды и механизм ее обработки. Поэтому в методе init() создадим конструкцию оператора switch

Давайте разберемся, что в этом коде происходит. Оператор switch принимает значение $message, которое может быть, как просто текст сообщения (в том числе команды), так и значение объекта callbackQuery.

  1. При получении команды /start – мы выводим приветственное слово и набор кнопок: Голосовать, Помощь.
  2. При получении команды Помощь, выводим простое текстовое сообщение.
  3. При получении команды Голосовать, выводим текстовое сообщение и набор встроенных кнопок с параметром callback_data.
  4. При получении не запланированного значения, выводим текстовое сообщение.
  5. И самое интересное это когда принимаем значение в начале которого стоит 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.

1 Звездаслабоватона троечкухорошо!просто отлично! (1 оценок, среднее: 4,00 из 5)
Загрузка...

Расскажите нам ваше мнение:

Ваш адрес email не будет опубликован. Обязательные поля помечены *