14.03.2016

5 советов пентестерам, планирующим перейти на Python 3

image

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

Автор: Spencer McIntyre

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

Совет №1. Как совместить типы, возвращаемые методами items(), keys() и values()

В Python 3 методы items(), keys() и values() возвращают специальные типы вместо списков как в Python 2. Следовательно, если использовать возвращаемый объект как изменяемый тип (список), в Python 3 возникнет ошибка. Пример:

my_teams_scores = {'alice': 0, 'bob': 0}
my_team = my_teams_scores.keys()
# my_team – это список в Python 2 и тип dict_keys в Python 3 (метод append работать не будет)
my_team.append('spencer')
 

Чтобы код, приведенный  выше, был совместим с обеими версиями, необходимо сконвертировать тип dict_keys в список:

my_teams_scores = {'alice': 0, 'bob': 0}
my_team = list(my_teams_scores.keys())
# теперь my_team является списком в обеих версиях, и вызов метода list() не вызовет ошибки в Python 2
my_team.append('spencer')

Совет №2. Как совместить использование модулей с измененными именами

В Python 3 несколько модулей, именованных ранее по стандарту CamelCase, стали называться по стандарту snake_case. Некоторые имена не претерпели сильных изменений, и можно пользоваться обеими версиями. Переименованные модули: ConfigParser, Queue, SimpleHTTPServer и BaseHTTPServer. Два модуля типа HTTPServer стали именоваться чуть по-другому, поскольку основные классы находятся в модуле http.server.

import sys
 
if sys.version_info.major < 3:
    # import the Python 2 ConfigParser module and rename it
    import ConfigParser as configparser
else:
    import configparser
 
parser = configparser.ConfigParser()
parser.read('some_config.ini')

Совет №3. Как совместить использование функции печати

Один из наиболее быстрых способов узнать, будет ли работать скрипт в Python 3 – обратить внимание, используется ли «print» как ключевое слово или как функция. В Python 3 ключевое слово «print» мигрировало в функцию, и, следовательно, для корректной работы скрипта нужны скобки.

print 'this will only work in python 2!'
print('this will work in either python 2 or python 3!')

Печать в одну линию:

# будет работать только в Python 2.x
print 'waiting for something...',
print 'done!'
from __future__ import print_function
# будет работать в Python 2.7 и Python 3.x
print('waiting for something...', end='')
print(' done!')

Совет №4. Как добиться совместимости при выполнении web-запросов

В большинстве случаев, если в скрипте необходимо сделать web-запрос, используется модуль urllib2. Однако в Python 3 это один из многих модулей, которые были переименованы и перемещены в другие пакеты. В Python 3 появился пакет urllib.request, и чтобы совместить выполнение базовых задач, связанных с web-запросами, понадобится переименование импортированных модулей (см. Совет 2). Однако можно пойти более простым путем и для выполнения web-запросов использовать сторонний пакет, совместимый с Python 2 и Python 3. Этот пакет доступен через pip (система управления пакетами), и один и тот же код можно использовать в различных версиях Python.

# будет работать только в Python 2
import urllib2
url_h = urllib2.urlopen('https://warroom.securestate.com')
 
# будет работать в Python 2.6 - Python 3.4 (начиная с версии пакета 2.8.1)
import requests
resp = requests.get('https://warroom.securestate.com')
 

Совет №5. Как совместить использование байтового и строкового типа

Вероятно, при адаптации скриптов к Python 3 наибольшие сложности возникнут c новым байтовым типом, который похож на строку с префиксом «b»:

b'these are bytes'
 

Использование этого типа наиболее уместно при работе с сырыми сокетами (raw sockets) или кодировкой base64. Важно отметить, что байтовый тип наиболее схож со строковым типом. Когда функция возвращает значение в байтовом типе, и пользователь ожидает текстовые данные, то преобразование в строковой тип возможно при помощи конструкции .decode(‘utf-8’). В Python 2 эта конструкция будет конвертировать строковой объект в объект типа unicode. В Python 3 байтовый объект будет конвертироваться в строковой объект. Важно отметить, что Python 2 поддерживает больше операций между строковыми объектами и объектами типа unicode, чем Python 3 - между байтовыми объектами и строковыми объектами.

# код ниже будет работать только в Python 2.x
# sock – установленное соединение через сокет
data = sock.recv(4)
if data == 'exit':
    sys.exit(0)
 

В этом примере предполагается, что переменная «data» - текстового типа, поскольку будет использоваться в командной оболочке.

# код ниже преобразует переменную data в utf-8 и будет работать в Python 2.7 & Python 3.x
# sock – установленное соединение через сокет
data = sock.recv(4)
data = data.decode('utf-8')
if data == 'exit':
    sys.exit(0)

Следует отметить, что в Python 2.7 строки с префиксом b остаются строкового типа, что позволяет выполнять совместимые операции сравнения и конкатенации.

# sock – установленное соединение через сокет
data = sock.recv(4)
# вместо декодирования сравнивается один байтовый тип с другим байтовым типом
if data == b'exit':
    sys.exit(0)