Сигурност на 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 база данни и паролата е необходима за отваряне на DB връзката.

Ако кодът беше в съответствие с...

# 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 за дешифриране на хеша, това не е полезно.

P.P.S. Dycrypter в момента е скрипт на Python. Ако трябваше да добавя memset към скрипта и след това да го „компилирам“ с помощта на py2exe или pyinstaller... това всъщност ще направи ли нещо, за да защити паролата? Инстинктът ми казва не, тъй като всичко, което pyinstaller прави, е да пакетира нормалния интерпретатор и същия байткод, който създава локалният интерпретатор... но не знам достатъчно за него...?


И така...следвайки предложението на Aya за създаване на модула за криптиране на 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 и след това да презапишете RAM с произволни данни.   -  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
@aya: Добре. Не съм сигурен колко информация ще ви трябва. Най-вече съм загрижен за хората, които се озовават в системата, въпреки че това е закален 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 работи на същия сървър като уеб сървъра, тогава винаги ще бъде уязвим, ако потребителят може да кракне руут, независимо от паролата, тъй като те могат просто да имат достъп до DB данните директно чрез /var/lib/mysql или където и да го съхранявате . Като се има предвид, че неизвестна „сол“ (която ефективно се превръща в парола) е необходима за достъп до MySQL, атака с инжектиране на SQL е малко вероятна, въпреки че се чудя как планирате да удостоверявате потребителите без достъп до MySQL DB? Ако приемем, че случаят е такъв и като се има предвид, че всичко друго освен порт 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
Избрах повредени променливи най-вече, за да предотвратя отгатването на името на променливата. Предполагам, че паролата = decrypt(encoded_password) е по-лесна за надушване от hidden_p_varble_weird_name = decrypt(__inputpasswordstringvarfromlocarea51). Но аз съм нов в писането на защитен питон... така че може би това не означава нищо. - 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