Язык программирования Python приобрел большую популярность среди разработчиков благодаря различным решениям, заложенным в его архитектуру. Такими решениями являются сильная динамическая типизация, то есть язык не допускает неявных приведений типов в неоднозначных ситуациях (например, сложение букв и цифр), при этом тип переменной определяется во время присваивания ей значения и может изменяться по ходу программы. Также полезной функцией языка Python автоматическое управление памятью и поддержка множества парадигм программирования, таких как структурное и функциональное программирование. И наконец, Python является полностью объектно-ориентированным языком с поддержкой интроспекции, то есть способности определять тип объекта во время выполнения.
Но, как всякий другой язык программирования, Python имеет ряд уязвимостей, о которых мы подробно поговорим в этой статье. Так как статья ориентирована прежде всего на разработчиков Python, то мы будем говорить об ошибках в коде, которые по тем или иным причинам могут допустить программисты в процессе разработки. И начнем мы с рассмотрения инъекций команд.
Такие разные инъекции
Прежде всего, начнем с определения. Инъекция команд — это вид атаки, целью которой является выполнение произвольных команд ОС сервера или произвольных SQL запросов в случае с СУБД.
Инъекция команд позволяет выполнить практически любую команду ОС с правами текущего пользователя. Так, командная строка shell, запущенная с помощью инъекции /bin/sh в приложение, работающее с правами root, будет работать также с правами привилегированного пользователя. Причина такого “странного” поведения кода кроется в некорректной обработке данных при динамической оценке интерпретатором выполняемого кода.
Еще более интересными вариантами инъекции команд является возможность предоставить удаленный доступ с правами пользователя, запустившего приложения. Таким образом, очевидно, что выполнение произвольных команд посредством инъекций в приложения, написанные на Python, являются достаточно опасной уязвимостью. Далее мы рассмотрим несколько примеров использования небезопасных команд языка Python.
Объект Popen
Команда popen
, а также команды popen3
, popen4
выполняют переданную строку как команду, что создает возможность для инъекции произвольных команд. Рассмотрим небольшой пример:
import os
user_input = "/etc && cat /etc/passwd
os.popen("ls -l " + user_input)
Здесь мы в переменной user_input
в качестве пользовательского ввода получаем путь к тому каталогу, содержимое которого нужно вывести на экран. Но если пользователь передает в качестве параметра не только каталог, но и другую команду, то popen выполняет обе команды. То есть в данном случае выводит содержимое файла /etc/passwd.
Еще одна проблемная команда — это subprocess
. Команда subprocess
позволяет создавать новые процессы, подключаться к их каналам ввода/вывода/ошибок и получать их коды возврата. Данная команда позволяет создавать новые процессы, а метод call
выполняет переданную текстовую строку:
import subprocess
import sys
user_input = "/bin && cat /etc/passwd"
subprocess.call("grep -R {} .".format(user_input), shell=True)
И еще одна аналогичная команда, выполняющая переданные пользователем данные без какой-либо проверки это os.system:
import os
user_input = "/etc && cat /etc/passwd"
os.system("grep -R {} .".format(user_input))
SQL инъекции
Инъекции команд SQL, наверное, можно назвать наиболее распространенным видом инъекций команд. SQL инъекции — это метод внедрения кода, который позволяет злоумышленнику вставить или изменить SQL-запрос в приложении. Для понимания проблемы приведем пример фрагмента кода:
def list_users():
rank = request.args.get('rank', '')
if rank == 'admin':
return "Can't list admins!"
c = CONNECTION.cursor()
c.execute("SELECT username, rank FROM users WHERE rank = '{0}'".format(rank))
data = c.fetchall()
Здесь, если в качестве запроса к веб-приложению передать следующую строку:
http://localhost:5000/users?rank=user’ UNION ALL SELECT * FROM SSN—
Тогда приложение вернет все записи из таблицы SSN. SQL-инъекции являются проблемой для большинства языков программирования, не только для Python.
Общие рекомендации по защите
Мы рассмотрели несколько примеров использования небезопасных команд и способов эксплуатации инъекций. Теперь самое время поговорить о том, как можно защититься от таких уязвимостей. Сразу хочу заметить, что инъекции команд — это не единственный вид уязвимостей, которые есть в Python, и в следующих статьях мы поговорим о других проблемах данного языка.
Итак, список рекомендаций для разработчиков можно начать с «прописной истины», а именно: Никогда не доверяйте данным, передаваемым пользователями! Да, концепция нулевого доверия существует уже много лет и все вроде бы хорошо знают, что нельзя доверять данным передаваемым пользователями, но уязвимости инъекции команд по прежнему можно встретить, особенно в веб-приложениях.
Лучше всего вообще запретить выполнение любых команд, вводимых пользователем. То есть, сделайте список или какой-то другой интерфейс, в котором пользователь может выбрать то действие, которое ему нужно. При этом, лучше всего максимально ограничить ввод произвольных параметров, которые пользователь может передать команде.
Приведем небольшой пример. Пусть у нас в приложении используется команда find
. Мы можем «захардкодить» выполнение этой команды, но оставить ввод произвольных параметров. Вроде бы пользователь не может осуществить инъекцию в чистом виде, но у find есть ключик exec, позволяющий выполнить любую команду. И если наше исходное приложение будет запущено под sudo или с SUID bit, то после инъекции ключа exec с нужным значением злоумышленник получит права root.
sudo find . -exec /bin/sh ; -quit
Таким образом, необходимо контролировать не только сами команды, вводимые пользователем но и ключи к командам, которые они собираются использовать. В случае с командой find
разработчик приложения должен жестко ограничить, какие именно ключи и с какими значениями могут использоваться. Пользователю должно быть доступно для ввода только значение маски искомого файла, все остальные параметры должны устанавливаться с помощью переключателей в интерфейсе приложения.
Кроме того, не стоит пренебрегать рекомендациями сообщества в части использования безопасных команд и параметров.
Безопасные команды
Рассмотрим безопасные варианты из приведенных выше примеров кода:
user_input = "cat /etc/passwd”
subprocess.popen (['ls', '-l', user_input ]) # команда отдельно
Здесь пользовательский ввод будет обрабатываться отдельно от команды и содержимое user_input
не будет выполнено как команда.
Команда subprocess.check_output
позволит проверить пользовательский ввод на наличие команд:
subprocess.check_output('ls -l dir/’)# проверка ввода
Значение shell=False
не позволит выполнить команды:
subprocess.call('echo $HOME', shell=False)# запрет выполнения
И наконец, проверка переданных приложению аргументов на соответствие имени какого-либо существующего файла.
os.path.isfile(sys.argv [ 1])# проверка передаваемых аргументов
В случае если избежать пользовательского ввода по тем или иным причинам нельзя, можно воспользоваться специальными библиотеками для контроля вводимых команд. Посмотрим несколько примеров использования таких библиотек.
Модуль Re
Принцип работы модуля — это использование регулярных выражений для проверки вводимых пользователем строк на наличие потенциально опасных команд.
import re
Python_code = input()
Pattern = re.compile(‘re_command_pattern’)
If pattern.fullmatch(python_code):
# выполнение python_code
Модуль Yaml
Еще одно средство контроля пользовательского ввода — это модуль Yaml. С помощью этого модуля мы можем проверить является ли переданный пользователем файл потенциально опасным или это обычный безобидный файл.
В примере ниже мы сначала проверяем обычный текстовый файл, на который программа никак не реагирует, а затем передаем в качестве входной строки путь к файлу /bin/ls и получаем кучу различных сообщений об ошибках.
import yaml
user_input = input()
with open(user_input) as secure_file:
contents = yaml.safe_load(secure_file)
Заключение
В этой статье мы поговорили об уязвимостях языка Python, связанных с инъекциями команд, а также о том, с помощью каких средств можно бороться с данными уязвимостями. В следующей статье мы продолжим рассмотрение проблем небезопасной разработки в Python.
А сейчас рекомендую всем желающим посетить открытый урок «Основы ООП в Python», на котором мы познакомимся с наследованием и научимся работать с классами. В результате этого занятия вы:
научитесь создавать свои классы;
разберётесь с наследованием;
научитесь переопределять методы и обращаться к методам суперклассов.
Записаться на открытый урок можно на странице специализации "Python Developer".
Комментарии (2)
Svetovoyvv
25.04.2023 08:32Python имеет ряд уязвимостей, о которых мы подробно поговорим в этой статье
это не единственный вид уязвимостей, которые есть в Python, и в следующих статьях мы поговорим о других проблемах данного языка.
Про уязвимости языка тут ничего нет, скорее ошибки разработчиков, которые несут угрозу безопасности.
Блок про yaml вообще бред
CrazyElf
Тогда уж про
exec
иeval
тоже надо было написать )