Безопасность Python: опасность несобранных переменных вне области видимости

У меня есть метод в классе, который расшифровывает переменную и возвращает ее. Я удаляю возвращаемую переменную с помощью «del» после использования.

В чем опасность доступа к этим мусорным значениям... и как я могу лучше всего защитить себя от них?

Вот код:

import decrypter
import gc

# mangled variable names used
def decrypt(__var):
    __cleartext = decrypter.removeencryption(__var)
    return __cleartext

__p_var = "<512 encrypted password text>"
__p_cleartext = decrypt(__p_var)
<....do login with __p_cleartext...>
del  __p_var, __p_cleartext
gc.collect()

Может ли какая-либо из переменных, включая __var и __cleartext, быть использована в данный момент?

Спасибо!


Я еще немного погуглил. Прежде чем я потрачу несколько часов на неправильный путь... я слышу следующее:

  1. Сохраните пароль в виде соленого хэша в системе (что он и делает сейчас).
  2. Соль для хэша должна быть введена пользователем при запуске пакета (сейчас делается)
  3. Однако соль должна храниться в процессе C, а не в python.
  4. Сценарий Python должен передать хэш процессу C для расшифровки.

Сценарий python обрабатывает вход в базу данных mysql, и пароль необходим для открытия соединения с БД.

Если бы код был похож на...

# MySQLdb.connect(host, user, password, database)
mysql_host = 'localhost'
mysql_db = 'myFunDatabase'
hashed_user = '\xghjd\xhjiw\xhjiw\x783\xjkgd6\xcdw8'
hashed_password = 'ghjkde\xhu78\x8y9tyk\x89g\x5de56x\xhyu8'
db = MySQLdb.connect(mysql_host, <call_c(hashed_user)>, <call_c(hashed_password)>, mysql_db])  

Решит ли это (по крайней мере) проблему того, что питон оставляет мусор повсюду?


P.S. Я также нашел сообщение о memset (Отметить данные как конфиденциальные в python), но я предполагаю, что если я использую C для расшифровки хэша, это бесполезно.

П.П.С. В настоящее время dycrypter представляет собой скрипт Python. Если бы я добавил memset в скрипт, а затем «скомпилировал» его с помощью py2exe или pyinstaller… помогло бы это защитить пароль? Мои инстинкты говорят нет, так как все, что делает pyinstaller, это упаковывает обычный интерпретатор и тот же байт-код, который создает локальный интерпретатор... но я недостаточно знаю об этом...?


Итак... следуя предложению Аи сделать модуль шифрования на C, сколько видимого следа памяти оставит следующая установка. Часть большой проблемы; возможность расшифровки пароля должна оставаться доступной на протяжении всего выполнения программы, так как она будет вызываться неоднократно... это не одноразовая вещь.

Создайте объект C, который запускается, когда пользователь входит в систему. Он содержит процедуру расшифровки и содержит копию соли, введенной пользователем при входе в систему. Сохраненная соль скрыта в работающем объекте (в памяти), поскольку она была хэширована его собственной процедурой шифрования с использованием случайно сгенерированной соли.

Случайно сгенерированная соль также должна храниться в переменной объекта. На самом деле это не для защиты соли, а просто для того, чтобы попытаться запутать след памяти, если кто-то заглянет в него (что затрудняет идентификацию соли). т.е. c-obj

mlock() /*to keep the code memory resident (no swap)*/

char encrypt(data, salt){ 
    (...) 
    return encrypted_data
}

char decrypt(data, salt){ 
    (...) 
    return decrypted_data
}

stream_callback(stream_data){
    return decrypt(stream_data, decrypt(s-gdhen, jhgtdyuwj))
}

void main{ 
    char jhgtdyuwj=rand();
    s-gdhen = encrypt(<raw_user_input>, jhgtdyuwj);
}

Затем скрипт python напрямую вызывает объект C, который передает незашифрованный результат прямо в вызов MySQLdb без сохранения каких-либо результатов в какой-либо переменной. т.е.

#!/usr/bin/python
encrypted_username = 'feh9876\xhu378\x&457(oy\x'
encrypted_password = 'dee\x\xhuie\xhjfirihy\x^\xhjfkekl'
# MySQLdb.connect(host, username, password, database)
db = MySQLdb.connect(self.mysql_host,
                     c-obj.stream_callabck(encrypted_username),
                     c-obj.stream_callback(encrypted_password),
                     self.mysql_database)

Какой след в памяти это может оставить, который можно было бы отследить?


person RightmireM    schedule 27.05.2013    source источник
comment
Стоит отметить, что удаление имени не гарантирует, что объект будет удален — другие имена для него все еще могут существовать.   -  person Gareth Latty    schedule 27.05.2013
comment
Если вас беспокоит несанкционированный доступ к открытому паролю, вероятно, есть много способов получить к нему доступ после того, как он предположительно был получен gc'd. Если вы особенно параноик, вы можете сделать эту часть на C, а затем перезаписать ОЗУ случайными данными.   -  person Aya    schedule 27.05.2013
comment
Что касается ваших правок: даже если вы все это реализовали на C и обнулили адресное пространство процесса, когда закончите, нет гарантии, что ОС не выгружает раздел адресного пространства, содержащий открытый пароль, поэтому он потенциально может оставаться на диске в течение очень долгого времени. И даже если вы отключили виртуальную память, если кто-то может получить доступ к пространству памяти процесса, всегда будет какая-то возможность получить пароль в открытом виде. TBH, я бы не стал об этом беспокоиться - если пароль только для MySQL, есть гораздо более простые способы обойти его безопасность.   -  person Aya    schedule 28.05.2013
comment
Есть ли какая-то причина, по которой вы особенно обеспокоены безопасностью пароля для этого? Вероятно ли, что случайные люди будут иметь доступ к системе, в которой работает этот код Python?   -  person Aya    schedule 28.05.2013
comment
@ая: хорошо. Не уверен, сколько информации вам понадобится. Я больше всего беспокоюсь о том, что люди попадут в систему, хотя это закаленный Centos6. По причинам, которые я не буду раскрывать, процессу потребуется неоднократно входить в базу данных (MySQL) и выходить из нее. Имя пользователя и пароль для базы данных хранятся в файле в виде 512-битного зашифрованного текста. Чтобы расшифровать, вам нужна та же соль, которая использовалась для шифрования текста. Соль не хранится в системе, но пользователь должен ввести ее в сценарий при запуске. Таким образом, это должно храниться в сценарии, и пользователь/пароль расшифровывает каждый вход в систему.   -  person RightmireM    schedule 29.05.2013
comment
@aya: Больше всего меня беспокоит; если пользователь получит доступ (даже root) к системе... в файле нет ничего, что могло бы ему помочь. Я даже не думаю, что эксплойт MySQLdb, который вы использовали, сработает, потому что скрипт не передаст учетные данные для входа без соли... которая должна быть передана при запуске скрипта. НО, соль будет жить в переменной в памяти. Насколько я могу судить, единственный способ получить эту переменную - это пронюхать или выгрузить память (или проверить файл подкачки... но использование memlock в C должно предотвратить его подкачку). Так вот, что я пытаюсь защитить.   -  person RightmireM    schedule 29.05.2013


Ответы (3)


Любая система безопасности настолько сильна, насколько сильно ее самое слабое звено.

Трудно сказать, какое самое слабое звено в вашей текущей системе, поскольку вы на самом деле не предоставили никаких подробностей об общей архитектуре, но если вы на самом деле используете код Python, как вы разместили в вопросе (давайте назовем это myscript.py). ..

#!/usr/bin/python
encrypted_username = 'feh9876\xhu378\x&457(oy\x'
encrypted_password = 'dee\x\xhuie\xhjfirihy\x^\xhjfkekl'
# MySQLdb.connect(host, username, password, database)
db = MySQLdb.connect(self.mysql_host,
                     c-obj.stream_callabck(encrypted_username),
                     c-obj.stream_callback(encrypted_password),
                     self.mysql_database)

... тогда независимо от того, как и где вы расшифровываете пароль, любой пользователь может прийти и запустить такой скрипт...

import MySQLdb

def my_connect(*args, **kwargs):
    print args, kwargs
    return MySQLdb.real_connect(*args, **kwargs)

MySQLdb.real_connect = MySQLdb.connect
MySQLdb.connect = my_connect
execfile('/path/to/myscript.py')

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

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

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

Однако, если вы удовлетворены тем, что машина, на которой вы запускаете этот код, безопасна (в том смысле, что к ней не могут получить доступ какие-либо «неавторизованные» пользователи), то ничего из этого запутывания пароля не требуется — вы можете также просто поместите пароль в виде открытого текста непосредственно в исходный код Python.


Обновить

Что касается архитектуры, я имел в виду, сколько отдельных серверов вы используете, какие у них обязанности и как они должны взаимодействовать друг с другом и/или с внешним миром?

Предполагая, что основная цель состоит в том, чтобы предотвратить несанкционированный доступ к серверу MySQL, и предполагая, что MySQL работает на сервере, отличном от скрипта Python, тогда почему вас больше беспокоит, что кто-то получит доступ к серверу, на котором запущен скрипт Python, и получит пароль для сервер MySQL, а не получать доступ к серверу MySQL напрямую?

Если вы используете «соль» в качестве ключа дешифрования для зашифрованного пароля MySQL, то как авторизованный пользователь передаст это значение в систему? Должны ли они войти на сервер, скажем, через ssh и запустить скрипт из командной строки, или это что-то доступное, скажем, через веб-сервер?

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

person Aya    schedule 28.05.2013
comment
В ПОРЯДКЕ. Не уверен, сколько информации вам понадобится. Я больше всего беспокоюсь о том, что люди попадут в систему, хотя это закаленный Centos6. По причинам, которые я не буду раскрывать, процессу потребуется неоднократно входить в базу данных (MySQL) и выходить из нее. Имя пользователя и пароль для базы данных хранятся в файле в виде 512-битного зашифрованного текста. Чтобы расшифровать, вам нужна та же соль, которая использовалась для шифрования текста. Соль не хранится в системе, но пользователь должен ввести ее в сценарий при запуске. Таким образом, это должно храниться в сценарии, и пользователь/пароль расшифровывает каждый вход в систему. - person RightmireM; 29.05.2013
comment
Меня больше всего беспокоит; если пользователь получит доступ (даже root) к системе... в файле нет ничего, что могло бы ему помочь. Я даже не думаю, что эксплойт MySQLdb, который вы использовали, сработает, потому что скрипт не передаст учетные данные для входа без соли... которая должна быть передана при запуске скрипта. НО, соль будет жить в переменной в памяти. Насколько я могу судить, единственный способ получить эту переменную - это пронюхать или выгрузить память (или проверить файл подкачки... но использование memlock в C должно предотвратить его подкачку). Так вот, что я пытаюсь защитить. - person RightmireM; 29.05.2013
comment
Спасибо. Положительные моменты :-) Сервер является автономным LAMP. Запуск базы данных MySQL и веб-сервера... вместе со скриптом Python. Он не взаимодействует с другими серверами. План состоит в том, чтобы пользователь мог войти в систему через Интернет. Все еще работаю над этой проблемой безопасности :-) Честно говоря, у этого вопроса есть две мысли: 1. Хотя это настоящий сервер, обрабатывающий реальные (и очень важные) пользовательские данные... это также образовательный опыт для меня в выполнении безопасных сценариев. . - person RightmireM; 29.05.2013
comment
2. Моя философия безопасности такова; ни одна точка входа никогда не является безопасной (иногда даже если вы отключите сервер :D). НО... чем больше уровней безопасности; лучшее. - person RightmireM; 29.05.2013
comment
т.е. Сначала взломщик должен пройти через дверь (на сервер), затем попытаться взломать зашифрованные файлы (и не обнаружить там никаких полезных данных), затем попытаться взломать память (надеюсь, это не удастся), затем дождаться, пока пользователь войдет в систему. так что он / она может обнюхать поток данных. Точно так же, как настоящий дом, безопасность заключается не в строительстве Форт-Нокса... но это делает его настолько трудным, разочаровывающим и трудоемким, чтобы добраться до драгоценностей... что к тому времени, когда он/она это сделает, они сдадутся или попался :-) Это имеет смысл? - person RightmireM; 29.05.2013
comment
@BurningKrome Что ж, если MySQL работает на том же сервере, что и веб-сервер, то он всегда будет уязвим, если пользователь сможет взломать root, независимо от пароля, поскольку он может просто получить доступ к данным БД напрямую через /var/lib/mysql или где бы вы их ни хранили. . Учитывая, что для доступа к MySQL требуется неизвестная «соль» (которая фактически становится паролем), атака с внедрением SQL маловероятна, хотя мне интересно, как вы планируете аутентифицировать пользователей без доступа к базе данных MySQL? Предполагая, что это так, и учитывая, что все, кроме порта 80, защищено брандмауэром, наиболее вероятная точка входа... - person Aya; 29.05.2013
comment
@BurningKrome ... будет либо другой формой атаки с внедрением (например, непроверенным вызовом os.system()), либо атакой с переполнением буфера, конечной целью которой является возможность выполнения произвольного кода в качестве UID веб-сервера. Что они будут делать оттуда, будет зависеть от того, как система аутентифицирует пользователей и что им на самом деле нужно. - person Aya; 29.05.2013

Даже если вы вызовете gc.collect и эти строки будут освобождены, они могут остаться в памяти. Кроме того, строки неизменяемы, что означает, что у вас нет (стандартного) способа их перезаписи. Также обратите внимание, что если вы выполняли операции с этими строками, некоторые их копии могут лежать где-то поблизости.

Поэтому не используйте строки, если это возможно.

Вам нужно перезаписать память (и даже тогда память может быть сброшена куда-то, например, в файл подкачки). Используйте массив байтов и перезапишите память, когда закончите.

person Radiance Wei Qi Ong    schedule 27.05.2013

Если других ссылок на значение не существует, ваш gc.collect обычно уничтожает объект.

Однако такая простая вещь, как интернирование или кэширование строк, может сохранить неожиданную ссылку, оставив значение в памяти. У Python есть несколько реализаций (PyPy, Jython, PyPy), которые внутри делают разные вещи. Сам язык дает очень мало гарантий относительно того, действительно ли значение будет стерто из памяти и когда.

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

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

person Raymond Hettinger    schedule 27.05.2013
comment
Для расшифровки фактически используется солт-ключ, который хранится в другом запущенном процессе. Этот процесс запускается, когда пользователь входит в систему, и соль должна активно передаваться с терминала (она не хранится в файле на сервере). Поскольку переменная в этих процессах должна оставаться активной, вероятно, существует больший риск быть обнаруженной... но неизбежно, что все компоненты будут доступны одновременно в какой-то момент. Я просто надеюсь, немного разбросав их, я запутаю это. - person RightmireM; 27.05.2013
comment
Я выбрал искаженные переменные в основном для того, чтобы предотвратить угадывание имени переменной. АКА Я предположил, что password = decrypt(encoded_password) легче обнаружить, чем hidden_p_varble_weird_name = decrypt(__inputpasswordstringvarfromlocarea51). Но я новичок в написании безопасного python... так что, возможно, это ничего не значит. - person RightmireM; 27.05.2013
comment
@BurningKrome Это ничего не значит. Изменение имени не имеет никакой ценности для безопасности. Цель изменения имени — предоставить стандартный шаблон для создания локальных ссылок класса (ссылка внутри класса, которая, как предполагается, не может быть переопределена подклассами). - person Raymond Hettinger; 27.05.2013
comment
@Aya: Пожалуйста, смотрите мои правки выше. Я на правильном пути обучения здесь? Спасибо! - person RightmireM; 28.05.2013
comment
@BurningKrome Можете ли вы ответить на мои комментарии к вопросу, а не к этому ответу, иначе я не получу уведомление. - person Aya; 28.05.2013