Исследование уязвимости PHP include. Основы работы с LongPoll сервером ВКонтакте Выход из дирректории

Основы работы с API (те, кто знаком с базовыми методами и запросами к ним, могут пропустить этот пункт)

Что же такое API ВКонтакте? Вот как трактует это официальная документация:

Callback API - это инструмент для отслеживания активности пользователей в Вашем сообществе ВКонтакте.

Другими словами, это способ отправлять запросы на серверы ВКонтакте, чтобы получить ответ, содержащий некоторую информацию. С помощью VK API можно сделать практически все: отправить сообщение пользователю, изменить пароль учетной записи, создать альбом с фотографиями и так далее. Лично я для работы с API использую библиотеку vk_api, но можно обойтись и библиотекой requests. Сейчас мы рассмотрим базовые принципы работы. Если вы решили использовать библиотеку vk_api, то пример запроса будет выглядеть так:

Import vk_api session = vk_api.VkApi(token="{ACCESS_TOKEN}") print(session.method("users.get", {"user_ids": 210700286, "fields": "photo_50, city, verified"}))
Разберем по строчкам:

Import vk_api Здесь импортируется нужная нам библиотека.

Session = vk_api.VkApi(token="{ACCESS_TOKEN}") Этой строкой мы авторизируемся через access token (способы его получения доступно описаны в документации и интернете, я не буду заострять на них внимание). Заметьте: не для всех методов нужна авторизация. О том, когда она требуется, будет написано в документации метода на сайте ВКонтакте.

Print(session.method("users.get", {"user_ids": 210700286, "fields": "photo_50, city"})) Эта часть кода заслуживает отдельного внимания. Что же здесь происходит? Мы вызываем метод method, передавая ему два аргумента: первый - название метода API, второй - словарь из параметров этого метода. Список всех методов и их параметров находится в документации API. В данном случае мы вызываем метод «users.get» и передаем следующие параметры: «user_ids» - список id пользователей, для которых мы хотим получить данные, «fields»: дополнительную информацию о пользователях: аватарку и город проживания. Результатом выполнения программы будет следующая выведенная строка: [{"id": 210700286, "first_name": "Lindsey", "last_name": "Stirling", "city": {"id": 5331, "title": "Los Angeles"}, "photo_50": "https://pp.userapi.com/c636821/v636821286/38a75/Ay-bEZoJZw8.jpg"}]

Если допустить какую-либо ошибку, например, указать неправильное название вызываемого метода, будет возбуждено исключение: vk_api.exceptions.ApiError: User authorization failed: no access_token passed. .
Примечание: если ошибка допущена в названии необязательного параметра, исключение сгенерировано не будет, а неизвестный параметр будет проигнорирован.

Если вы решили использовать библиотеку requests, придется немного углубиться в документацию API, чтобы узнать формат запроса и ответа.
Запрос должен выглядеть так: https://api.vk.com/method/{METHOD_NAME}?{PARAMS}&v={API_VERSION} , где {METHOD_NAME} - название метода, {PARAMS} - параметры вызываемого метода, а {API_VERSION} - версия API, которую должен использовать сервер при формировании ответа.
В качестве ответа нам будет возвращен JSON объект с информацией о результатах вызова метода. Давайте реализуем запрос к API с помощью библиотеки requests:

Import requests data = requests.get("https://api.vk.com/method/{METHOD_NAME}".format(METHOD_NAME="users.get"), params={"user_ids": 210700286, "fields": "photo_50, city"}).json() print(data)
Итак, разберем написанное.

Import requests Импортирование библиотеки requests

Data = requests.get("https://api.vk.com/method/{METHOD_NAME}".format(METHOD_NAME="users.get"), params={"user_ids": 210700286, "fields": "photo_50, city"}).json() Эта строка записывает в переменную data ответ сервера. Мы передаем функции get два аргумента: адрес, к которому нужно сделать запрос (в нашем случае - форматированную строку), и словарь из параметров этого метода. Если метод не вызывается без access token-а, нужно добавить его в словарь с ключем "access_token". Параметр «v» (версия API) не является обязательным, так как по умолчанию будет использоваться последняя версия, но, если нужно использовать иную версию, ее тоже нужно добавить в словарь с ключом "v". К пришедшему от сервера ответу применяется метод json(), который обрабатывает JSON-объекты.

Последняя строка выводит результат работы, в нашем случае - {"response": [{"uid": 210700286, "first_name": "Lindsey", "last_name": "Stirling", "city": 5331, "photo_50": "https://pp.userapi.com/c636821/v636821286/38a75/Ay-bEZoJZw8.jpg"}]} .

Если передать неправильное название метода, будет выведено следующее: {"error": {"error_code": 5, "error_msg": "User authorization failed: no access_token passed.", "request_params": [{"key": "oauth", "value": "1"}, {"key": "method", "value": "user.get"}, {"key": "user_ids", "value": "210700286"}, {"key": "fields", "value": "photo_50, city"}]}} .


Что такое LongPoll?

Для начала обратимся за помощью к документации:

Long Polling - это технология, которая позволяет получать информацию о новых событиях с помощью «длинных запросов». Сервер получает запрос, но отправляет ответ на него не сразу, а лишь тогда, когда произойдет какое-либо событие (например, поступит новое входящее сообщение), либо истечет заданное время ожидания.
Другими словами, получая от вас запрос, сервер ждет, когда произойдет событие, о котором он должен вас уведомить, и, когда оно происходит, Long Poll сервер отправляет ответ на ваш запрос, содержащий информацию о случившемся событии.
Мы напишем программу, которая будет уведомлять пользователя о некоторых изменениях в его аккаунте, которые мы будем получать от Long Poll сервера. Чтобы начать получать ответы от сервера, необходимо получить три обязательных параметра, необходимых для работы Long Poll-a: server, key и ts.
key - секретный ключ сессии;
server - адрес сервера;
ts - номер последнего события, начиная с которого нужно получать данные.

Их не нужно получать каждый раз, вызывая Long Poll, - будет достаточно одного вызова. Из документации следует, что эти значения можно получить, вызвав метод messages.getLongPollServer. Напишем программу, которая сделает запрос с этим методом и будем использовать полученные данные для получения к Long Poll.

Import requests token = "" # здесь вы должны написать свой access_token data = requests.get("https://api.vk.com/method/messages.getLongPollServer", params={"access_token": token}).json()["response"] # получение ответа от сервера print(data)
Если вы все сделали правильно, программа выведет словарь с тремя ключами: {"key": "###############################", "server": "imv4.vk.com/im####", "ts": 0000000000}


Запрос к Long Poll серверу

Теперь, используя значения, хранящиеся в словаре, который был создан в предыдущей части, мы можем сделать запрос к Long Poll серверу! Чтобы это сделать, нужно сделать запрос к следующему адресу: https://{$server}?act=a_check&key={$key}&ts={$ts}&wait=25&mode=2&version=2 , где {$server}, {$key} и {$ts}- значения из словаря с ключами "server", "key" и "ts" соответственно, wait - время, которое Long Poll сервер будет ожидать обновлений, а mode - дополнительные опции ответа. Напишем программу, которая сделает один запрос на Long Poll сервер

Import requests token = "" # здесь вы должны написать свой access_token params = requests.get("https://api.vk.com/method/messages.getLongPollServer", params={"access_token": token}).json()["response"] # получение ответа от сервера response = requests.get("https://{server}?act=a_check&key={key}&ts={ts}&wait=90&mode=2&version=2".format(server=data["server"], key=data["key"], ts=data["ts"])).json() # отправление запроса на Long Poll сервер со временем ожидания 90 секунд и опциями ответа 2 print(response)
Если за 90 секунд произошло какое-либо событие, которое попадает в список обрабатываемых Long Poll сервером, на экран выведется нечто похожее: {"ts": 0000000000, "updates": []} . Что же значит пришедший ответ и как с ним дальше можно работать?

Во-первых, вы могли заметить, что в ответе содержится параметр "ts", который мы использовали при отправлении запроса. Он здесь неспроста. Все события, попадающие в список обрабатываемых Long Poll сервера, нумеруются. Когда вы создаете новый аккаунт, первое такое событие имеет номер 1, второе - 2 и так далее. При отправлении запроса на Long Poll, нужно передавать этот параметр для того, чтобы сервер знал, начиная с какого номера нужно присылать обновления. В каждом ответе сервера также приходит этот параметр, чтобы вы использовали его при следующем вызове Long Poll сервера.

Во-вторых, в словаре содержится ключ "updates" с говорящим названием. Не трудно догадаться, что значение по этому ключу хранит обновления, случившиеся после отправления запроса. Формат обновлений - массив массивов. Каждый массив в массиве - произошедшее обновление, которое нужно обработать. Если в первом массиве массивов больше одного, это значит, что несколько событий произошли одновременно. Параметр "ts" содержит номер последнего из них. Если массив, доступный по ключу "updates", пуст, то за время wait не произошло ни одного события. Вы спросите: «А что это за непонятные цифры в массивах?». Ответ довольно прост - это информация о случившемся событии, которую можно преобразовать в более понятный вид. Их обработкой мы займемся позже, а в следующей части напишем программу, которая будет постоянно обращаться к Long Poll серверу.


Циклические запросы к Long Poll и коды событий

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

Import requests token = "" # здесь вы должны написать свой access_token data = requests.get("https://api.vk.com/method/messages.getLongPollServer", params={"access_token": token}).json()["response"] # получение ответа от сервера while True: response = requests.get("https://{server}?act=a_check&key={key}&ts={ts}&wait=20&mode=2&version=2".format(server=data["server"], key=data["key"], ts=data["ts"])).json() # отправление запроса на Long Poll сервер со временем ожидания 20 и опциями ответа 2 updates = response["updates"] if updates: # проверка, были ли обновления for element in updates: # проход по всем обновлениям в ответе print(element) data["ts"] = response["ts"] # обновление номера последнего обновления
Данная программа циклически посылает запросы к Long Poll-у, проверяет, были ли обновления, и выводит пришедшие обновления на экран. Но выходные данные этой программы в том виде, в котором они есть сейчас, никуда не годятся. Сейчас вывод программы выглядит подобным образом:



Первая цифра в каждом массиве означает код события. Используя ее, можно понять, какое событие произошло. Вот список кодов событий с кратким описанием (из официальной документации):
1 - Замена флагов сообщения (FLAGS:=$flags);
2 - Установка флагов сообщения (FLAGS|=$mask);
3 - Сброс флагов сообщения (FLAGS&=~$mask);
4 - Добавление нового сообщения;
6 - Прочтение всех входящих сообщений в $peer_id, пришедших до сообщения с $local_id.
7 - Прочтение всех исходящих сообщений в $peer_id, пришедших до сообщения с $local_id.
8 - Друг $user_id стал онлайн. $extra не равен 0, если в mode был передан флаг 64. В младшем байте (остаток от деления на 256) числа extra лежит идентификатор платформы $timestamp - время последнего действия пользователя $user_id на сайте;
9 - Друг $user_id стал оффлайн ($flags равен 0, если пользователь покинул сайт (например, нажал выход) и 1, если оффлайн по таймауту (например, статус away)). $timestamp - время последнего действия пользователя $user_id на сайте;
10 - Сброс флагов диалога $peer_id. Соответствует операции (PEER_FLAGS &= ~$flags). Только для диалогов сообществ;
11 - Замена флагов диалога $peer_id. Соответствует операции (PEER_FLAGS:= $flags). Только для диалогов сообществ;
12 - Установка флагов диалога $peer_id. Соответствует операции (PEER_FLAGS|= $flags). Только для диалогов сообществ;
13 - Удаление всех сообщений в диалоге $peer_id с идентификаторами вплоть до $local_id;
14 - Восстановление недавно (менее 20 дней назад) удаленных сообщений в диалоге $peer_id с идентификаторами вплоть до $local_id;
51 - Один из параметров (состав, тема) беседы $chat_id были изменены. $self - 1 или 0 (вызваны ли изменения самим пользователем);
61 - Пользователь $user_id набирает текст в диалоге. Событие приходит раз в ~5 секунд при постоянном наборе текста. $flags = 1;
62 - Пользователь $user_id набирает текст в беседе $chat_id;
70 - Пользователь $user_id совершил звонок с идентификатором $call_id;
80 - Счетчик непрочитанных в левом меню стал равен $count;
112 - Изменились настройки оповещений. $peer_id - идентификатор чата/собеседника.

Таким образом, если первое число в массиве - 8, то кто-то из ваших друзей стал онлайн, если 9 - оффлайн и так далее. Остальные числа в массиве тоже имеют значение, но к ним мы подберемся позже, так как их значение зависит от кода события.


Обработка приходящих событий

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

Первый параметр, как вы уже знаете, - код события. Остаются 2 элемента: 0 и 0 в первом случае и 1 0 во втором. Последний параметр при коде 80 должен быть всегда равен нулю, поэтому его можно игнорировать; для нас важен лишь второй. Во втором параметре указано, сколько на данный момент у пользователя непрочитанных сообщений. Минимальное значение - 0 (нет новых сообщений), максимальное - не ограничено. Этого достаточно, чтобы обрабатывать все события с кодом 80. Реализуем это в код:

# часть кода в этом примере опущена для сокращения места, без нее код работать не будет while True: response = requests.get("https://{server}?act=a_check&key={key}&ts={ts}&wait=20&mode=2&version=2".format(server=data["server"], key=data["key"], ts=data["ts"])).json() # отправление запроса на Long Poll сервер со временем ожидания 20 и опциями ответа 2 updates = response["updates"] if updates: # проверка, были ли обновления for element in updates: # проход по всем обновлениям в ответе action_code = element # запись в переменную кода события if action_code == 80: # проверка кода события print("количество непрочитанных сообщений стало равно", element) # вывод data["ts"] = response["ts"] # обновление номера последнего обновления
Если запустить эту программу, она будет выводить на экран сообщения об изменении количества непрочитанных сообщений. Следующие относительно несложные обновления имеют коды 8 и 9 - друг стал онлайн и оффлайн соответственно. Дополним нашу программу, чтобы она смогла обрабатывать и их. Начнем с кода 9. Напишем строку, которая будет проверять код события:

Elif action_code == 9:
Далее рассмотрим формат приходящих обновлений, имеющих код 9:
С индексом 0, как вы уже знаете, хранится код обновления - 9. Далее идет отрицательное id пользователя, ставшего оффлайн (чтобы получить действительное id, нужно умножить на -1, дабы избавиться от минуса). Элемент с индексом 3 может принимать лишь 2 значения: 0 или 1. 1 означает, что отметка «оффлайн» поставлена по истечении тайм-аута неактивности, а 0 - что пользователь покинул сайт явно, например, нажав кнопку «выход». Последнее значение в ответе - время последнего действия пользователя на сайте в Unix time. Запишем все полученные сведения в переменные:

User_id = element * -1 # id пользователя, ставшего оффлайн user = requests.get("https://api.vk.com/method/users.get", params={"user_ids": user_id, "fields": "sex"}).json()["response"] # имя, фамилия и пол пользователя с id = user_id timeout = bool(element) # был ли поставлен статус оффлайн по истечении тайм-аута last_visit = element # время последнего действия пользователя на сайте в Unix time
Как вы могли заметить, помимо имени и фамилии пользователя, мы получаем еще и его пол. Это нужно, чтобы программа правильно использовала рода в предложениях и не писала что-то вроде «Иван Иванов вышела из ВКонтакте». Переменная timeout хранит False, если пользователь явно покинул сайт, и True, если истекло время тайм-аута. Теперь можно вывести на экран полученные данные:

# не забудьте написать вначале "import time", здесь используется эта библиотека if user["sex"] == 1: verb = ["стала", "вышла"] else: verb = ["стал", "вышел"] if timeout: print(user["first_name"], user["last_name"], verb, "оффлайн по истечении тайм-аута. Время последнего действия на сайте:", time.ctime(last_visit).split()) else: print(user["first_name"], user["last_name"], verb, "из ВКонтакте. Время последнего действия на сайте:", time.ctime(last_visit).split())
Единственное, что я вижу неочевидным в этом коде - вывод времени:

Time.ctime(last_visit).split()
Давайте разберемся с этой частью кода. Функция ctime библиотеки time принимает в качестве аргумента число - время в Unix time и возвращает строку вида "Thu Jan 1 03:00:00 1970"
. Так как нам нужно лишь время последнего действия на сайте, мы разбиваем эту строку методом split по пробелам и получаем массив, где в индексе 0 хранится день недели, в индексе 1 - месяц, 2 - число, 3 - время, а 4 - год. Мы извлекаем элемент с индексом 3, то есть время.

Теперь напишем реализацию обработки обновлений с кодом 8. Их формат выглядит так: . Второй элемент в массиве - отрицательное id пользователя, ставшего онлайн, третий - платформа, с которой зашел пользователь, а четвертый - время последнего действия пользователя на сайте в Unix time. Реализуем полученные данные в код:

User_id = element * -1 # id пользователя, ставшего онлайн user = requests.get("https://api.vk.com/method/users.get", params={"user_ids": user_id, "fields": "sex"}).json()["response"] # имя, фамилия и пол пользователя с id = user_id platform = element # код платформы пользователя last_visit = element # время последнего визита в Unix time if user["sex"] == 1: verb = "стала" else: verb = "стал"
Здесь мы записали данные из ответа в переменные, а также поставили глагол в нужный род. Параметр platform сейчас содержит число в диапазоне от 1 до 7 включительно. Каждое число обозначает платформу, с которой пользователь совершил действие. Переведем это число в текст:

# определение платформы по ее коду if platform == 1: platform = "официальную мобильную версию web-сайта VK" elif platform == 2: platform = "официальное приложение VK для iPhone" elif platform == 3: platform = "официальное приложение VK для iPad" elif platform == 4: platform = "официальное приложение VK для Android" elif platform == 5: platform = "официальное приложение VK для Windows Phone" elif platform == 6: platform = "официальное приложение VK для Windows" elif platform == 7: platform = "официальную web-версию VK"
Теперь можно вывести информацию на экран:

Print(user["first_name"], user["last_name"], verb, "онлайн через", platform, "в", time.ctime(last_visit).split())
Далее мы рассмотрим коды 61 и 62. Они сообщают о том, что кто-то набирает сообщение. Разница в том, что обновления с кодом 61 оповещают о наборе текста в личных сообщениях, а 62 - в беседах. Обновления с кодом 62 выглядят так: . Второй элемент массива - id пользователя, набирающего сообщения, а третий - идентификатор беседы. Обновления с кодом 61 имеют следующий вид: . Здесь значение имеет лишь второй элемент - id пользователя, набирающего сообщение. Напишем обработчик этих событий:

Elif action_code == 61: user = requests.get("https://api.vk.com/method/users.get", params={"user_ids": element}).json()["response"] # получение имени и фамилии пользователя print(user["first_name"], user["last_name"], "набирает сообщение") elif action_code == 62: user = requests.get("https://api.vk.com/method/users.get", params={"user_ids": element}).json()["response"] # получение имени и фамилии пользователя, набирающего сообщение chat = requests.get("https://api.vk.com/method/messages.getChat", params={"chat_id": element, "access_token": token}).json()["response"]["title"] # получение названия беседы print(user["first_name"], user["last_name"], "набирает сообщение в беседе "{}"".format(chat))
В следующей теме мы напишем обработчик входящих сообщений и рассмотрим флаги сообщений.


Обработка сообщений и флаги

Наиболее интересные обновления, как мне кажется, - добавление сообщений. Эти обновления имеют код 4 и возвращают информацию о входящих и исходящих сообщений. Вот пример обновления с кодом 4: . Здесь $ts - номер пришедшего события, $flag - флаги сообщения, $id - id собеседника или 2000000000 + id беседы (в случае с коллективными диалогами), $unixtime - время добавления сообщения в Unix time, а последний элемент - словарь, содержащий информацию о вложениях, отправителе и изменениях в настройках беседы. Для начала разберемся с тем, где было добавлено сообщение: в личной переписке или беседе. Если сообщение было отправлено в беседе, то, как я уже написал, в поле $id будет указано число, получившиеся при сложении 2000000000 и chat_id (идентификатор беседы). Если сообщение было добавлено в личной переписке, в поле $id будет значиться id собеседника, которое всегда меньше, чем 2000000000 + chat_id любой беседы. Из этого можно сделать вывод, что, если $id - 2000000000 > 0, то сообщение было отправлено в беседе, если меньше, то в личной переписке. Если сообщение было отправлено в беседе, то id пользователя, написавшего сообщение, будет указано в словаре под ключем "from". Напишем обработчик сообщений:

Elif action_code == 4: if element - 2000000000 >
Однако, у данной программы довольно много недостатков. Вот некоторые из них:

  • Неумение отличить исходящие сообщения от входящих;
  • игнорирования медиа-вложений;
  • игнорирование сообщений, сигнализирующих об изменении настроек беседы;
  • неумение обработать специальные сиволы в сообщениях
Две из четырех проблем можно решить, используя флаги сообщений, которые хранятся в массиве обновления под индексом 2. Этот элемент массива - число, получившиеся в результате сложения некоторых параметров, приведенных ниже (из официальной документации):
+1: сообщение не прочитано
+2: исходящее сообщение
+4: на сообщение был создан ответ
+8: помеченное сообщение
+16: сообщение отправлено через чат
+32: сообщение отправлено другом. Не применяется для сообщений из групповых бесед
+64: сообщение помечено как «Спам»
+128: сообщение удалено (в корзине)
+256: сообщение проверено пользователем на спам
+512: сообщение содержит медиаконтент
+65536: приветственное сообщение от сообщества. Диалог с таким сообщением не нужно поднимать в списке (отображать его только при открытии диалога напрямую). Флаг недоступен для версий <2.

Теперь нужно научить программу отличать исходящие сообщения от входящих. Чтобы проверить, есть ли среди слагаемых флага сообщения нужное нам число, мы будем использовать побитовое И. Создадим массив, в котором будут храниться слагаемые флага:

Summands = # массив, где мы будем хранить слагаемые flag = element # флаг сообщения for number in : # проходим циклом по возможным слагаемым if flag & number: # проверяем, является ли число слагаемым с помощью побитового И summands.append(number) # если является, добавляем его в массив
Выводить на экран информацию об исходящих сообщениях, как мне кажется, бессмысленно, поэтому мы добавим строку

If 2 not in summands:
Сейчас часть программы, работающая с сообщениями, выглядит так:

Elif action_code == 4: summands = # массив, где мы будем хранить слагаемые flag = element # флаг сообщения for number in : # проходим циклом по возможным слагаемым if flag & number: # проверяем, является ли число слагаемым с помощью побитового И summands.append(number) # если является, добавляем его в массив if 2 not in summands: if element - 2000000000 > 0: # проверяем, было ли отправлено сообщение в беседе user_id = element["from"] # id отправителя chat_id = element - 2000000000 # id беседы chat = requests.get("https://api.vk.com/method/messages.getChat", params={"chat_id": chat_id, "access_token": token}).json()["response"]["title"] # получение названия беседы user = requests.get("https://api.vk.com/method/users.get", params={"user_ids": user_id, "name_case": "gen"}).json()["response"] # получение имени и фамилии пользователя, отправившего сообщение time_ = element # время отправления сообщения text = element # текст сообщения if text: # проверяем, что сообщение содержит текст print(time.ctime(time_).split() + ":", "Сообщение от", user["first_name"], user["last_name"], "в беседе "{}"".format(chat) + ":", text) else: user_id = element # id собеседника user = requests.get("https://api.vk.com/method/users.get", params={"user_ids": user_id, "name_case": "gen"}).json()["response"] # получение имени и фамилии пользователя, отправившего сообщение time_ = element # время отправления сообщения text = element # текст сообщения if text: # проверяем, что сообщение содержит текст print(time.ctime(time_).split() + ":", "Сообщение от", user["first_name"], user["last_name"] + ":", text)
Теперь научимся работать с медиа-вложениями. Вот пример обновления, говорящего о том, что пришло сообщение с прикрепленными фотографией, песней, видео, документом и гео-позицией: [ . Чтобы получить доступ к этим вложениям, нужно будет обращаться к API для получения ссылки на файл. Методы для выполнения этого действия (photos.getById и docs.getById) нашлись лишь для фотографий и документов (аудиосообщения приходят как документы, поэтому ссылку для их прослушивания получить удастся). Для музыки с недавних пор недоступен ни один метод из-за авторских прав, а для видео нужный метод попросту отсутствует. Для дальнейшей работы с вложениями напишем код, который создаст два массива (для фото и для документов) со ссылками на просмотр вложений.

If 512 in summands: # проверка, есть ли медиа-вложения index = 1 photos = # массив для хранения id фотографий docs = # массив для хранения id документов media_type = "attach1_type" while media_type in element.keys(): # проверка, существует ли медиа-вложение с таким индексом media_type = element["attach{}_type".format(index)] # если существует, сохраняем его тип if media_type == "photo": # является ли вложение фотографией photos.append(element["attach{}".format(index)]) # добавляем id фотографии в массив elif media_type == "doc": # является ли вложение документом docs.append(element["attach{}".format(index)]) # добавляем id документа в массив index += 1 # увеличиваем индекс media_type = "attach{}_type".format(index) change = lambda ids, type_: requests.get("https://api.vk.com/method/{}.getById".format(type_), params={type_: ids, "access_token": token}).json() # функция, возвращающаяся ссылки на объекты if photos: # проверка, были ли во вложениях фотографии photos = change(", ".join(photos), "photos") # если были, то перезаписываем переменную photos на словарь if "response" in photos.keys(): photos = for attachment in photos["response"]] # перезаписываем на ссылки print("сообщение содержит следующие фотографии:", ", ".join(photos)) else: pass # скорее всего, возникла ошибка доступа if docs: # проверка, были ли во вложениях документы docs = change(", ".join(docs), "docs") # если были, то перезаписываем переменную docs на словарь if "response" in docs.keys(): docs = for attachment in docs["response"]] # перезаписываем на ссылки print("сообщение содержит следующие документы:", ", ".join(docs)) else: pass # скорее всего, возникла ошибка доступа
Уберем строки

If text: , чтобы при выводе данных было понятно, какие медиа-вложения к каким сообщениям относятся.

Теперь научим нашу программу реагировать на изменения в беседе: создание новой беседы, обновления фотографии, изменения названий, добавления и исключения людей. Информация о подобных событиях хранится в словаре, находящемся под индексом 6 в обновлениях с кодом 4. Ниже приведены примеры событий:

  • Изменение названия беседы:
  • Обновление фотографии беседы:
  • Добавление пользователя в беседу:
  • Исключение пользователя из беседы (выход пользователя из беседы):
  • Создание новой беседы:
Вот программа, которая обрабатывает изменения такого вида:

Elif action_code == 4: if "source_act" not in element.keys(): # <код, обрабатывающий сообщения> else: source_act = element if source_act["source_act"] == "chat_title_update": # было ли обновление вызвано изменением названия беседы changer_id = source_act["from"] # id человека, изменившего названия source_text = source_act["source_text"] # новое название беседы source_old_text = source_act["source_old_text"] # старое название беседы changer = requests.get("https://api.vk.com/method/users.get", params={"user_ids": changer_id, "fields": "sex"}).json()["response"] # получение имени и фамилии пользователя, изменившего название if changer["sex"]: verb = "изменила" else: verb = "изменил" print(changer["first_name"], changer["last_name"], verb, "название беседы с "{}" на "{}"".format(source_old_text, source_text)) elif source_act["source_act"] == "chat_photo_update": chat_id = element - 2000000000 # id беседы chat = requests.get("https://api.vk.com/method/messages.getChat", params={"chat_id": chat_id, "access_token": token}).json()["response"]["title"] # получение названия беседы user_id = source_act["from"] # id пользователя, обновившего фото photo_id = source_act["attach1"] # id фотографии photo = requests.get("https://api.vk.com/method/photos.getById", params={"photos": photo_id, "access_token": token}).json() # ссылка на фотографию user = requests.get("https://api.vk.com/method/users.get", params={"user_ids": user_id, "fields": "sex"}).json()["response"] # имя и фамилия пользователя, обновившего фото if "error" not in photo.keys(): # не возникло ли ошибок при получении ссылки if user["sex"]: verb = "обновил" else: verb = "обновила" print(user["first_name"], user["last_name"], verb, "фотографию беседы "{}" на".format(chat), photo["response"]["src_xbig"]) else: pass # вероятнее всего, отсутствуют права для выполнения запроса elif source_act["source_act"] == "chat_invite_user": chat_id = element - 2000000000 # id беседы chat = requests.get("https://api.vk.com/method/messages.getChat", params={"chat_id": chat_id, "access_token": token}).json()["response"]["title"] # получение названия беседы invited_id = source_act["source_mid"] # id приглашенного inviter_id = source_act["from"] # id пригласившего if invited_id == inviter_id: # вернулся ли пользователь в беседу или был добавлен кем-то из участников user = requests.get("https://api.vk.com/method/users.get", params={"user_ids": inviter_id, "fields": "sex"}).json()["response"] # имя и фамилия вернувшегося if user["sex"]: verb = "вернулась" else: verb = "вернулся" print(user["first_name"], user["last_name"], verb, "в беседу "{}"".format(chat)) else: inviter_user = requests.get("https://api.vk.com/method/users.get", params={"user_ids": inviter_id, "fields": "sex"}).json()["response"] # имя и фамилия добавившего invited_user = requests.get("https://api.vk.com/method/users.get", params={"user_ids": invited_id, "name_case": "acc"}).json()["response"] # имя и фамилия добавленного if inviter_user["sex"]: verb = "добавила" else: verb = "добавил" print(inviter_user["first_name"], inviter_user["last_name"], verb, "в беседу "{}"".format(chat), invited_user["first_name"], invited_user["last_name"]) elif source_act["source_act"] == "chat_kick_user": chat_id = element - 2000000000 # id беседы chat = requests.get("https://api.vk.com/method/messages.getChat", params={"chat_id": chat_id, "access_token": token}).json()["response"]["title"] # получение названия беседы removed_id = source_act["source_mid"] # id исключенного remover_id = source_act["from"] # id исключившего if removed_id == remover_id: # вышел ли пользователь сам или был исключен user = requests.get("https://api.vk.com/method/users.get", params={"user_ids": remover_id, "fields": "sex"}).json()["response"] # имя и фамилия вышедшего if user["sex"]: verb = "вышла" else: verb = "вышел" print(user["first_name"], user["last_name"], verb, "из беседы "{}"".format(chat)) else: remover_user = requests.get("https://api.vk.com/method/users.get", params={"user_ids": remover_id, "fields": "sex"}).json()["response"] # имя и фамилия исключившего removed_user = requests.get("https://api.vk.com/method/users.get", params={"user_ids": removed_id, "name_case": "acc"}).json()["response"] # имя и фамилия исключенного if remover_user["sex"]: verb = "исключила" else: verb = "исключил" print(remover_user["first_name"], remover_user["last_name"], verb, "из беседы "{}"".format(chat), removed_user["first_name"], removed_user["last_name"]) elif source_act["source_act"] == "chat_create": chat = source_act["source_text"] # название беседы creator_id = source_act["from"] # id создателя creator = requests.get("https://api.vk.com/method/users.get", params={"user_ids": creator_id, "fields": "sex"}).json()["response"] # имя, фамилия и пол создателя if creator["sex"]: verb = "создала" else: verb = "создал" print(creator["first_name"], creator["last_name"], verb, "беседу "{}"".format(chat))


Наводим красивости. Заключительная часть

В этой части мы рассмотрим лишь две детали: ошибку при запросе к Long Poll серверу и отображение спец. символов в сообщениях.

Об ошибке: если запустить программу в том виде, в котором она есть сейчас, через некоторое время возникнет ошибка KeyError на строке 8: response = requests.get("https://{server}?act=a_check&key={key}&ts={ts}&wait=20&mode=2&version=2".format(server=data["server"], key=data["key"], ts=data["ts"])).json()["response"] # отправление запроса на Long Poll сервер со временем ожидания 20 и опциями ответа 2 . Дело в том, что на наш запрос сервер вернул ошибку «error 2», что означает, что используемый параметр &key устарел и нужно получить новый. Для этого мы несколько изменим существующий код на:

Response = requests.get("https://{server}?act=a_check&key={key}&ts={ts}&wait=20&mode=2&version=2".format(server=data["server"], key=data["key"], ts=data["ts"])).json() # отправление запроса на Long Poll сервер со временем ожидания 20 и опциями ответа 2 try: updates = response["updates"] except KeyError: # если в этом месте возбуждается исключение KeyError, значит параметр key устарел, и нужно получить новый data = requests.get("https://api.vk.com/method/messages.getLongPollServer", params={"access_token": token}).json()["response"] # получение ответа от сервера continue # переходим на следующую итерацию цикла, чтобы сделать повторный запрос
Теперь проблема решена! Остается последнее: спец. символы в сообщениях. Дело в том, что некоторые символы ВК возвращает не в привычном для нас виде. Так, например, если в сообщении есть амперсант, он будет заменен на &_amp (нижнее подчеркивание нужно, чтобы Хабр не заменил эту надпись на амерсант). Подобных символов много и всех их нужно вывести правильно. Для этого сохраним подобные символы и их коды в словарь, а затем заменим коды в сообщении на символы с помощью функции sub библиотеки re (не забудьте ее импортировать!).

Import re # <...> symbols = {"
": "\n", "&_amp;": "&", "&_": """, "&_lt;": "<", "&_gt;": ">", "&_tilde;": "~", "&_circ;": "ˆ", "&_ndash;": "–", "&_mdash;": "-", "&_euro;": "€", "&_permil;": "‰"} # из каждого ключа уберите нижнее подчеркивание for code, value in symbols.items(): text = re.sub(code, value, text)

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

Import re import time import requests token = "" # здесь вы должны написать свой access_token data = requests.get("https://api.vk.com/method/messages.getLongPollServer", params={"access_token": token}).json()["response"] # получение ответа от сервера while True: response = requests.get("https://{server}?act=a_check&key={key}&ts={ts}&wait=20&mode=2&version=2".format(server=data["server"], key=data["key"], ts=data["ts"])).json() # отправление запроса на Long Poll сервер со временем ожидания 20 и опциями ответа 2 try: updates = response["updates"] except KeyError: # если в этом месте возбуждается исключение KeyError, значит параметр key устарел, и нужно получить новый data = requests.get("https://api.vk.com/method/messages.getLongPollServer", params={"access_token": token}).json()["response"] # получение ответа от сервера continue # переходим на следующую итерацию цикла, чтобы сделать повторный запрос if updates: # проверка, были ли обновления for element in updates: # проход по всем обновлениям в ответе action_code = element if action_code == 80: # проверка кода события print("количество непрочитанных сообщений стало равно", element) # вывод elif action_code == 9: user_id = element * -1 # id пользователя, ставшего оффлайн user = requests.get("https://api.vk.com/method/users.get", params={"user_ids": user_id, "fields": "sex"}).json()["response"] # имя и фамилия пользователя с id = user_id timeout = bool(element) # был ли поставлен статус оффлайн по истечении тайм-аута last_visit = element # дата последнего действия пользователя на сайте if user["sex"] == 1: verb = ["стала", "вышла"] else: verb = ["стал", "вышел"] if timeout: print(user["first_name"], user["last_name"], verb, "оффлайн по истечении тайм-аута. Время последнего действия на сайте:", time.ctime(last_visit).split()) else: print(user["first_name"], user["last_name"], verb, "из ВКонтакте. Время последнего действия на сайте:', time.ctime(last_visit).split()) elif action_code == 8: user_id = element * -1 # id пользователя, ставшего онлайн user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'fields': 'sex'}).json()['response'] # имя и фамилия пользователя с id = user_id platform = element # код платформы пользователя last_visit = element # время последнего визита в Unix time if user['sex'] == 1: verb = 'стала' else: verb = 'стал' # определение платформы по ее коду if platform == 1: platform = 'официальную мобильную версию web-сайта VK' elif platform == 2: platform = 'официальное приложение VK для iPhone' elif platform == 3: platform = 'официальное приложение VK для iPad' elif platform == 4: platform = 'официальное приложение VK для Android' elif platform == 5: platform = 'официальное приложение VK для Windows Phone' elif platform == 6: platform = 'официальное приложение VK для Windows' elif platform == 7: platform = 'официальную web-версию VK' print(user['first_name'], user['last_name'], verb, 'онлайн через', platform, 'в', time.ctime(last_visit).split()) elif action_code == 61: user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': element}).json()['response'] # получение имени и фамилии пользователя print(user['first_name'], user['last_name'], 'набирает сообщение') elif action_code == 62: user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': element}).json()['response'] # получение имени и фамилии пользователя, набирающего сообщение chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': element, 'access_token': token}).json()['response']['title'] # получение названия беседы print(user['first_name'], user['last_name'], 'набирает сообщение в беседе "{}"'.format(chat)) elif action_code == 4: if 'source_act' not in element.keys(): summands = # массив, где мы будем хранить слагаемые flag = element # флаг сообщения for number in : # проходим циклом по возможным слагаемым if flag & number: # проверяем, является ли число слагаемым с помощью побитового И summands.append(number) # если является, добавляем его в массив if 2 not in summands: if element - 2000000000 > 0: # проверяем, было ли отправлено сообщение в беседе user_id = element['from'] # id отправителя chat_id = element - 2000000000 # id беседы chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': chat_id, 'access_token': token}).json()['response']['title'] # получение названия беседы user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'name_case': 'gen'}).json()['response'] # получение имени и фамилии пользователя, отправившего сообщение time_ = element # время отправления сообщения text = element # текст сообщения symbols = {'
<', '&_gt;': '>', '&_tilde;': '~', '&_circ;': 'ˆ', '&_ndash;': '–', '&_mdash;': '-', '&_euro;': '€', '&_permil;': '‰'} # из каждого ключа уберите нижнее подчеркивание for code, value in symbols.items(): text = re.sub(code, value, text) print(time.ctime(time_).split() + ':', 'Сообщение от', user['first_name'], user['last_name'], 'в беседе "{}"'.format(chat) + ':', text) else: user_id = element # id собеседника user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'name_case': 'gen'}).json()['response'] # получение имени и фамилии пользователя, отправившего сообщение time_ = element # время отправления сообщения text = element # текст сообщения symbols = {'
': '\n', '&_amp;': '&', '&_': '"', '&_lt;': '<', '&_gt;': '>', '&_tilde;': '~', '&_circ;': 'ˆ', '&_ndash;': '–', '&_mdash;': '-', '&_euro;': '€', '&_permil;': '‰'} # из каждого ключа уберите нижнее подчеркивание for code, value in symbols.items(): text = re.sub(code, value, text) print(time.ctime(time_).split() + ':', 'Сообщение от', user['first_name'], user['last_name'] + ':', text) if 512 in summands: # проверка, были ли медиа-вложения index = 1 photos = # массив для хранения id фотографий docs = # массив для хранения id документов media_type = 'attach1_type' while media_type in element.keys(): # проверка, существует ли медиа-вложение с таким индексом media_type = element['attach{}_type'.format(index)] # если существует, сохраняем его тип if media_type == 'photo': # является ли вложение фотографией photos.append(element['attach{}'.format(index)]) # добавляем id фотографии в массив elif media_type == 'doc': # является ли вложение документом docs.append(element['attach{}'.format(index)]) # добавляем id документа в массив index += 1 # увеличиваем индекс media_type = 'attach{}_type'.format(index) change = lambda ids, type_: requests.get('https://api.vk.com/method/{}.getById'.format(type_), params={type_: ids, 'access_token': token}).json() # функция, возвращающаяся ссылки на объекты if photos: # проверка, были ли во вложениях фотографии photos = change(', '.join(photos), 'photos') # если были, то перезаписываем переменную photos на словарь if 'response' in photos.keys(): photos = for attachment in photos['response']] # перезаписываем на ссылки print('сообщение содержит следующие фотографии:', ', '.join(photos)) else: pass # скорее всего, возникла ошибка доступа if docs: # проверка, были ли во вложениях документы docs = change(', '.join(docs), 'docs') # если были, то перезаписываем переменную docs на словарь if 'response' in docs.keys(): docs = for attachment in docs['response']] # перезаписываем на ссылки print('сообщение содержит следующие документы:', ', '.join(docs)) else: pass # скорее всего, возникла ошибка доступа else: source_act = element if source_act['source_act'] == 'chat_title_update': # было ли обновление вызвано изменением названия беседы changer_id = source_act['from'] # id человека, изменившего названия source_text = source_act['source_text'] # новое название беседы source_old_text = source_act['source_old_text'] # старое название беседы changer = requests.get('https://api.vk.com/method/users.get', params={'user_ids': changer_id, 'fields': 'sex'}).json()['response'] # получение имени и фамилии пользователя, изменившего название if changer['sex']: verb = 'изменила' else: verb = 'изменил' print(changer['first_name'], changer['last_name'], verb, 'название беседы с "{}" на "{}"'.format(source_old_text, source_text)) elif source_act['source_act'] == 'chat_photo_update': chat_id = element - 2000000000 # id беседы chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': chat_id, 'access_token': token}).json()['response']['title'] # получение названия беседы user_id = source_act['from'] # id пользователя, обновившего фото photo_id = source_act['attach1'] # id фотографии photo = requests.get('https://api.vk.com/method/photos.getById', params={'photos': photo_id, 'access_token': token}).json() # ссылка на фотографию user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'fields': 'sex'}).json()['response'] # имя и фамилия пользователя, обновившего фото if 'error' not in photo.keys(): # не возникло ли ошибок при получении ссылки if user['sex']: verb = 'обновил' else: verb = 'обновила' print(user['first_name'], user['last_name'], verb, 'фотографию беседы "{}" на'.format(chat), photo['response']['src_xbig']) else: pass # вероятнее всего, отсутствуют права для выполнения запроса elif source_act['source_act'] == 'chat_invite_user': chat_id = element - 2000000000 # id беседы chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': chat_id, 'access_token': token}).json()['response']['title'] # получение названия беседы invited_id = source_act['source_mid'] # id приглашенного inviter_id = source_act['from'] # id пригласившего if invited_id == inviter_id: # вернулся ли пользователь в беседу или был добавлен кем-то из участников user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': inviter_id, 'fields': 'sex'}).json()['response'] # имя и фамилия вернувшегося if user['sex']: verb = 'вернулась' else: verb = 'вернулся' print(user['first_name'], user['last_name'], verb, 'в беседу "{}"'.format(chat)) else: inviter_user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': inviter_id, 'fields': 'sex'}).json()['response'] # имя и фамилия добавившего invited_user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': invited_id, 'name_case': 'acc'}).json()['response'] # имя и фамилия добавленного if inviter_user['sex']: verb = 'добавила' else: verb = 'добавил' print(inviter_user['first_name'], inviter_user['last_name'], verb, 'в беседу "{}"'.format(chat), invited_user['first_name'], invited_user['last_name']) elif source_act['source_act'] == 'chat_kick_user': chat_id = element - 2000000000 # id беседы chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': chat_id, 'access_token': token}).json()['response']['title'] # получение названия беседы removed_id = source_act['source_mid'] # id исключенного remover_id = source_act['from'] # id исключившего if removed_id == remover_id: # вышел ли пользователь сам или был исключен user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': remover_id, 'fields': 'sex'}).json()['response'] # имя и фамилия вышедшего if user['sex']: verb = 'вышла' else: verb = 'вышел' print(user['first_name'], user['last_name'], verb, 'из беседы "{}"'.format(chat)) else: remover_user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': remover_id, 'fields': 'sex'}).json()['response'] # имя и фамилия исключившего removed_user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': removed_id, 'name_case': 'acc'}).json()['response'] # имя и фамилия исключенного if remover_user['sex']: verb = 'исключила' else: verb = 'исключил' print(remover_user['first_name'], remover_user['last_name'], verb, 'из беседы "{}"'.format(chat), removed_user['first_name'], removed_user['last_name']) elif source_act['source_act'] == 'chat_create': chat = source_act['source_text'] # название беседы creator_id = source_act['from'] # id создателя creator = requests.get('https://api.vk.com/method/users.get', params={'user_ids': creator_id, 'fields': 'sex'}).json()['response'] # имя, фамилия и пол создателя if creator['sex']: verb = 'создала' else: verb = 'создал' print(creator['first_name'], creator['last_name'], verb, 'беседу "{}"'.format(chat)) data['ts'] = response['ts'] # обновление номера последнего обновления
В этом руководстве я рассмотрел не все возможности Long Poll-a, такие интересные вещи, как замена флагов сообщений или отметки «прочитано» и «непрочитанно» остались незатронутыми, но вы всегда можете дописать программу.
На этом у меня все, надеюсь, вы узнали для себя что-то новое. До скорых встреч!

ГРЕХОВНЫЙ, ая, ое; вен, вна. То же, что грешный (в 1 знач.). Греховные мысли. Толковый словарь Ожегова. С.И. Ожегов, Н.Ю. Шведова. 1949 1992 … Толковый словарь Ожегова

Ж. отвлеч. сущ. по прил. греховный Толковый словарь Ефремовой. Т. Ф. Ефремова. 2000 … Современный толковый словарь русского языка Ефремовой

Грех прямое или косвенное нарушение религиозных догм (заветов Бога или Богов, предписаний и традиций); реже нарушение доминантных морально этических правил, норм, установившихся в обществе. Определение гласит, что грех является следствием акта… … Википедия

греховность - грех овность, и … Русский орфографический словарь

греховность - см. греховный; и; ж. Грехо/вность помыслов … Словарь многих выражений

Всеобщая греховность - ♦ (ENG universal depravity) понимание того, что все люди грешники (Рим. 3:23) … Вестминстерский словарь теологических терминов

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

- (‘Un positivo esistenzialism’, 1948) работа Аббаньяно. Экзистенциализм трактуется не как ‘философия отчаяния’, сосредоточившаяся на кризисных состояниях человеческого существования, но как ‘позитивная’, дающая возможность человеку осуществить… …

Беспутство, распутство, разврат, распущенность, разнузданность (нравов), порок, непотребство, греховность, сладострастие, срам. Ср … Словарь синонимов

- (1844) сочинение Кьеркегора. Стремясь опровергнуть философскую систему Гегеля, во введении к П.С. Кьеркегор писал: Шлейермахер говорил только о том, что знал, тогда как Гегель, несмотря на все свои превосходные качества и исполинскую ученость, во … История Философии: Энциклопедия

Книги

  • Общие аспекты православной психологии , Добросельский Петр Владимирович , В настоящей работе сделана попытка рассмотреть с позиции библейской, святоотеческой и научной антропологии, общие аспекты психической жизни человека. В книге представлены различные точки… Категория: Религиоведение Серия: Очерки православной антропологии Издатель: Грифон ,
  • Тавро Кассандры , Чингиз Айтматов , В книгу известного кыргызского писателя Чингиза Айтматова вошли два романа. Первый "И дольше века длится день" написан пятнадцать лет назад и уже знаком многим читателям. В центре романа -… Категория: Классическая и современная проза Издатель:

Voka ,

"; echo "Украсти анкету купите зверька!

"; echo "
"; echo "Симпатичный мамонтенок (100 балов)
"; if ($user["balls"] >= 100) { echo "Купить зверька

"; } echo "
"; echo "Позитивный кошак (80 балов)
"; if ($user["balls"] >= 80) { echo "Купить зверька
"; }else{ echo "Купить зверька
"; } echo "
"; echo "Веселый краб (50 балов)
"; if ($user["balls"] >= 50) { echo "Купить зверька
"; }else{ echo "Купить зверька
"; } echo "
"; echo "Нарик черепах))) (50 балов)
"; if ($user["balls"] >= 50) { echo "Купить зверька
"; }else{ echo "Купить зверька
"; } echo "
"; echo "Злой крокодил (50 балов)
"; if ($user["balls"] >= 50) { echo "Купить зверька
"; }else{ echo "Купить зверька
"; } echo "
"; echo "Пингвине (50 балов)
"; if ($user["balls"] >= 50) { echo "Купить зверька
"; }else{ echo "Купить зверька
"; } echo "
"; echo "Голубой слон (50 балов)
"; if ($user["balls"] >= 50) { echo "Купить зверька
"; }else{ echo "Купить зверька
"; } echo "

\n"; echo "«Назад
\n"; echo "
\n"; break; case "ok": $select = abs(intval($_GET["select"])); if ($select == 1) $price = $user["balls"] - 100; if ($select == 2) $price = $user["balls"] - 80; if ($select == 3) $price = $user["balls"] - 50; if ($select == 4) $price = $user["balls"] - 50; if ($select == 5) $price = $user["balls"] - 50; if ($select == 6) $price = $user["balls"] - 50; if ($select == 7) $price = $user["balls"] - 50; if ($select >= 1 AND $select <= 7) { if ($price >= 0) { echo "Зверек успешно куплен! Загляните в свою анкету
В анкету →"; mysql_query("UPDATE `pets` SET `pet_id` = "$select", `time` = "$realtime" WHERE `user_id` = "".$user["id"]."""); mysql_query("UPDATE `user` SET `balls` = "$price" WHERE `id` = "".$user["id"]."""); }else{ echo "Хакер ***!"; } }else{ echo "Такого зверька не существует!"; } break; default: echo "Хотите завести себе симпатичную зверушку в анкете?
Купить зверька"; echo "
\n"; echo "«Назад
\n"; echo "
\n"; if (!isset($fobian["user_id"])) mysql_query("INSERT INTO `pets` SET `user_id` = "".$user["id"]."""); break; } include_once "../sys/inc/tfoot.php"; ?>

Таблица еще к коду имелась с таким запросом:

CREATE TABLE IF NOT EXISTS `pets` (`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `user_id` INT(10) UNSIGNED NOT NULL, `pet_id` INT(10) NOT NULL DEFAULT "0", `time` text NOT NULL, PRIMARY KEY (`id`)) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

Ну и вот еще справка была в архиве по установке скрипта, что я делал:О скрипте - Скрипт прикольных зверушек в анкете для Dcms 6.*.* от fobian aka Dozz. Функционал: -- покупка зверушек за балы сайта -- просмотр зверушек других пользователей -- по истечению 7 дней зверушка пропадает из анкеты ICQ: 4726991 E-mail: Установка: 1. Залить таблицу из файла base.sql 2. Прописать в файле /info.php примерно на 251 строке данный код. $fobian = mysql_fetch_array(mysql_query("SELECT * FROM `pets` WHERE `user_id` = "".$ank["id"].""")); if (isset($fobian["user_id"]) AND $fobian["pet_id"] >= 1) { $ost = $realtime - $fobian["time"]; if ($ost > 604800) mysql_query("UPDATE `pets` SET `pet_id` = "0", `time` = "0" WHERE `user_id` = "".$ank["id"]."""); echo ""; } 3. Прописать в файле sys/inc/umenu.php в нужном месте echo "Изменит зверька
\n";Пользуйтесь на здоровье=)Dmitry , То есть он как бы пустой получается был!? А как же выход найти из положения?

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

Что такое PHP-include

Проведем маленький ликбез по этой уязвимости. PHP-include — уязвимость которая позволяет «приинклудить» произвольный файл, например такой код:

$module=$_REQUEST["module"]; include("modules/".$module);

И так как в файле «/etc/pаsswd» обычно нет php тегов (), то он выведется в браузер, как вывелся бы html код вынесенный за php теги в обычном php скрипте. Конечно чтение файлов всего лишь одна из возможных реализаций этой атаки. Основная же все таки это инклудинг нужных файлов с нужным php кодом.

Вернемся к примеру. Усложним его:

$module=$_REQUEST["module"]; include("modules/".$module."/module.class.php");

$module = $_REQUEST [ "module" ] ;

include ("modules/" . $module . "/module.class.php" ) ;

Как видите теперь в конце к нашей переменной добавляется строка, которая нам мешает приинклудить любой файл. Так вот, многие функции php не являются бинарно безопасными, т.е такие функции считают NULL-байт за конец строки. Обращаемся к скрипту так:

script.php?module=../../../../../../../../../../../etc/pаsswd%00

И если дирректива magic_quotes отключенна, то мы снова увидим содержимое /etc/pаsswd

Есть ли уязвимость?

Вернемся к нашему коду:

$module=addslashes($_REQUEST["module"]); include("modules/".$module."/module.class.php");

$module = addslashes ($_REQUEST [ "module" ] ) ;

include ("modules/" . $module . "/module.class.php" ) ;

Как видно, наша переменная принудительно проходит через «addslashes» и если мы попытаемся использовать NULL-байт то он будет преобразован в «\0» и инклуда не выйдет.

Но прогресс не стоит на месте! Оказывается некие ребята из USH нашли в PHP интересную фичу PHP filesystem attack vectors (англ.). Если в кратце пересказать суть статьи, то php обрабатывает пути с использованием нескольких особенностей:

  • Усечение пути — php обрезает строку пути до заданной длины MAXPATHLEN (В Windows до 270 символов, в NIX — обычно 4096, в BSD — обычно 1024)
  • Нормализация пути — php обрабатывает путь специальным образом, удаляя лишние символы «/» и «/.» и их различные комбинации
  • Приведение к каноническому виду — убираются лишние переходы, например «dir1/dir2/../dir3» приводится к «dir1/dir3/» при этом существование дирректории «dir2» не проверяется, и прочие похожие преобразования (т.е продолжение нормализации)

Теперь по порядку что происходит с переданным путем:

  1. Если путь передан относительный, то к нему вначале подставляются значения из диррективы include_path
  2. Далее путь обрезается до определенной длины в зависимости от платформы
  3. Проводится нормализация пути
  4. Путь приводится к каноническому виду

Теперь попробуем воспользоваться этим. Попробуем приинклудить некий файл «test.php» который находится в дирректории «modules/». Для этого добавляем в конец симолы «/.» таким образом чтобы общая длина, вместе с именем файла, значением из include_path была заведомо больше 4096 символов.
script.php?module=test.php/././.[...]/././.

При этом необходимо подгадать так, чтобы вся строка пути (уже обрезанная) заканчивалась на точку (важно!), а не на слеш. Для этого можно добавить один слеш вот так:

И один из этих вариантов сработает точно.

Анализируем

Смотрим по порядку какие преобразования произойдут с путем
modules/test.php//././.[...]/./././module.class.php
4200 символов

Первое что происходит со строкой, это к ней добавляется значение из include_path:
/home/site/public_html/modules/test.php//././.[...]/./././module.class.php
4223 символа

Затем строка ускается до MAXPATHLEN (допустим 4096):
/home/site/public_html/modules/test.php//././.[...]/./.
4096 символов

Здесь видно зачем нужно было добавлять еще один слеш (иначе бы строка обрезалась до слеша). Теперь производится нормализация этой строки, сначала убераются лишние слеши:
/home/site/public_html/modules/test.php/././.[...]/./.
4095 символов

В итоге получаем правильный путь до нужного нам файла, и этот путь уже передастся в инклуд, и приинклудится нужный нам файл.

То есть вот так мы приинклудим наш файл «test.php» успешно.
script.php?module=test.php//././.[...]/././.

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

Заметки

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

Выход из дирректории

Рассмотрим такой код:

) ;

Опустим тот момент, что можно вопсользоваться RFI и приинклудить файл с удаленного сервера. Допустим на сервере «allow_url_include=OFF».

Рассмотрим ситуацию когда нам надо приинклудить файл из дирректории ниже:
script.php?module=../test.php/././.[...]/././.

Такое обращение выдаст ошибку, типа файл не найден. И для того чтобы это обойти нам надо обратится вот так:
script.php?module=blabla/../../test.php/././.[...]/././.

Я не зря описывал про канонизацию путей. Благодаря ей дирректория «blabla» не обязательно должна существовать.

Добавление просто слешей

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

Все дело в алгоритмах, то есть, слеш с точкой «/.» убирается полностью. А вот с просто слешами дело обстоит немного сложнее, при нормализации каждые два слеша заменяются на один до тех пор пока не останется один(!) слеш, пример:

/home/site/public_html/modules/test.php//////////////////
57 символов

/home/site/public_html/modules/test.php/////////
48 символов

/home/site/public_html/modules/test.php/////
44 символов

/home/site/public_html/modules/test.php///
42 символов

/home/site/public_html/modules/test.php//
41 символов

/home/site/public_html/modules/test.php/
40 символов

Небольшое отступление:

Причем если обратить внимание на многие, популярные хак ресурсы, то можно заметить эту ошибку. Я так понимаю эта ошибка началась со статьи некоего Raz0r где он предложил вектор:
index.php?act=../../../../../etc/pаsswd/////[…]/////

И обратите внимание даже журнал ][акер повторил эту ошибку в своей статье . При этом даже в оригинальной статье USH было четко написанно что использовать просто слеши не желательно, и необходимо чтобы в конце перед нормализацией остался символ точки. А просто слеши (даже без точки на конце) работают только в PHP c Suhosin.

То есть использовать слеш с точкой «/.» — более универсальный метод, так как, в отличие от слешей «/», он работает для всех версий php.

Заключение

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

 
Статьи по теме:
Куда ехать за исполнением желаний в Курской области
Отец Вениамин служит в одном из храмов Коренной пустыни. Несколько раз в неделю священник проводит молебны, на которые съезжается множество людей. Летом службы часто проходят на улице, так как все желающие не умещаются в крохотной церквушке. Прихожане уве
Когда включают-отключают фонтаны в петергофе Включили ли фонтаны на поклонной горе
Фонтан Дубая: музыкальный и танцующий фонтан Дубая, часы работы, мелодии, видео. Туры на Новый год в ОАЭ Горящие туры в ОАЭ Предыдущая фотография Следующая фотография Дубайский музыкальный фонтан - поистине феерическая композиция из светы, звука и вод
Уральский федеральный университет им
schedule Режим работы:Пн., Вт., Ср., Чт. c 09:00 до 17:00Пт. c 09:00 до 16:00 Последние отзывы УрФУ Анонимный отзыв 11:11 25.04.2019 Учусь на 3 курсе в Высшей школе экономики и менеджмента – все нравится. Преподаватели хорошо объясняют изучаемый матер
Джозайя уиллард гиббс биография
] Перевод с английского под редакцией В.К. Семенченко.(Москва - Ленинград: Гостехиздат, 1950. - Классики естествознания)Скан: AAW, обработка, формат Djv: mor, 2010 СОДЕРЖАНИЕ:Предисловие редактора (5).Джосиа Виллард Гиббс, его жизненный путь и основные