Язык программирования 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)


  1. CrazyElf
    25.04.2023 08:32
    +1

    Тогда уж про exec и eval тоже надо было написать )


  1. Svetovoyvv
    25.04.2023 08:32

    Python имеет ряд уязвимостей, о которых мы подробно поговорим в этой статье

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

    Про уязвимости языка тут ничего нет, скорее ошибки разработчиков, которые несут угрозу безопасности.

    Блок про yaml вообще бред