【问题标题】:Python security: Danger of uncollected variables out of scopePython 安全性:未收集的变量超出范围的危险
【发布时间】:2013-05-22 13:09:18
【问题描述】:

我在一个类中有一个方法可以解密一个变量并返回它。使用后我用“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])  

这会(至少)解决python留下垃圾的问题吗?


附:我还发现了关于 memset (Mark data as sensitive in python) 的帖子,但我假设如果我使用 C 来解密哈希,这没有帮助。

附言dycrypter 目前是一个 python 脚本。如果我将 memset 添加到脚本中,然后使用 py2exe 或 pyinstaller “编译”它……这实际上有助于保护密码吗?我的直觉说不,因为 pyinstaller 所做的只是将普通解释器和本地解释器创建的相同字节码打包......但我对此知之甚少......?


所以...按照 Aya 建议在 C 中制作加密模块,以下设置会留下多少可识别的内存占用。部分大问题是;解密密码的能力必须在整个程序运行期间保持可用,因为它将被重复调用......这不是一次性的事情。

创建一个在用户登录时启动的 C 对象。它包含解密例程和保存用户在登录时输入的盐的副本。存储的盐在运行对象(在内存中)中被隐藏,因为它自己的加密例程使用随机生成的盐进行了散列。

随机生成的盐仍然必须保存在对象的变量中。这并不是为了保护盐,而只是为了尝试混淆内存足迹,如果有人应该偷看它(使盐难以识别)。 IE。 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)

这会留下什么样的内存占用,可以窥探?

【问题讨论】:

  • 值得注意的是,删除名称并不能保证该对象将被删除 - 它的其他名称可能仍然存在。
  • 如果您担心未经授权访问明文密码,可能有很多方法可以在它被 gc'd 后访问它。如果你特别偏执,你可能想在 C 中做这部分,然后用随机数据覆盖 RAM。
  • 关于您的编辑:即使您在 C 中实现了整个过程,并在完成后将进程地址空间归零,也不能保证操作系统没有分页出地址空间部分其中包含明文密码,因此它可能会在磁盘上保留很长时间。而且即使你禁用了虚拟内存,如果有人可以访问进程的内存空间,也总会有一些机会抢到明文密码。 TBH,我不会担心 - 如果密码只是 MySQL 的,有更简单的方法可以绕过它的安全性。
  • 您是否特别担心密码安全问题?随机人是否有可能访问运行此 Python 代码的系统?
  • @aya:我最担心的是;如果用户获得对系统的访问权限(甚至是 root)......文件中没有任何内容可以帮助他们。我什至不认为你使用的 MySQLdb 漏洞会起作用,因为脚本不会在没有 salt 的情况下传递登录凭据……必须在脚本启动时传入。但是,盐将存在于内存中的变量中。据我所知,获取此变量的唯一方法是嗅出或转储内存(或检查交换文件......但在 C 中使用 memlock 应该可以防止它被交换)。所以,这就是我想要保护的。

标签: python security exploit garbage


【解决方案1】:

即使您调用 gc.collect 并且这些字符串被释放,它们可能仍保留在内存中。此外,字符串是不可变的,这意味着您没有(标准)方法来覆盖它们。另请注意,如果您对这些字符串执行了操作,它们的一些副本可能会散落。

所以尽可能不要使用字符串。

您需要覆盖内存(即使这样,内存也可能被转储到某个地方,例如页面文件中)。完成后使用字节数组并覆盖内存。

【讨论】:

    【解决方案2】:

    如果不存在对该值的其他引用,您的 gc.collect 通常会销毁该对象。

    但是,像字符串实习或缓存这样简单的事情可能会保留意外的引用,从而使值在内存中仍然存在。 Python 有许多在内部做不同事情的实现(PyPy、Jython、PyPy)。语言本身很少保证该值是否或何时会真正从内存中删除。

    在您的示例中,您还使用了名称修饰。因为修改很容易手工复制,所以这根本不会增加任何安全性。

    进一步思考:不清楚您的安全模型是什么。如果攻击者可以调用您的解密函数并在同一进程中运行任意代码,那么什么会阻止他们包装解密以保留输入和输出的代码。

    【讨论】:

    • 解密实际上使用了保存在另一个正在运行的进程中的盐密钥。当用户登录并且必须从终端主动传入盐(它不在服务器中的文件上)时,此过程就会启动。由于此过程中的变量必须保持活动状态,因此被发现的风险可能更大……但不可避免的是,所有组件在某个时间点同时可用。我只是希望通过将它们散布一点,我会混淆它。
    • 我选择重整变量主要是为了防止变量名被猜到。 AKA 我假设“password = decrypt(encoded_pa​​ssword)”比“hidden_​​p_varble_weird_name = decrypt(__inputpasswordstringvarfromlocarea51)”更容易被发现。但我是编写安全 python 的新手……所以,也许这没什么意义。
    • @BurningKrome 这确实没有任何意义。名称修改的安全价值为零。名称修改的目的是为创建类本地引用提供标准模式(可以假定子类不会覆盖类内部的引用)。
    • @Aya:请看我上面的编辑。我在正确的学习轨道上吗?谢谢!
    • @BurningKrome 你能否就这个问题而不是这个答案回复我的 cmets,否则我不会收到通知。
    【解决方案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 服务器?

    如果您使用“salt”作为加密 MySQL 密码的解密密钥,那么授权用户如何将该值传递给系统?他们是否必须通过例如 ssh 登录到服务器,然后从命令行运行脚本,或者通过网络服务器等可访问的东西?

    无论哪种方式,如果有人确实破坏了运行 Python 脚本的系统,他们只需等到下一个授权用户出现,然后“嗅探”他们输入的“盐”。

    【讨论】:

    • 好的。不确定您需要多少信息。我最关心的是人们最终会使用这个系统,尽管它是一个强化的 centos6。由于我不会进入的原因,一个进程将需要反复登录和退出数据库(MySQL)。数据库的用户名和密码作为 512 位加密文本保存在文件中。要取消加密,您需要用于加密文本的相同“盐”。盐不会保留在系统中,但必须由用户在启动时输入到脚本中。因此,它必须保存在脚本中,并且用户/通行证对每次登录进行解密。
    • 我最担心的是;如果用户获得对系统的访问权限(甚至是 root)......文件中没有任何内容可以帮助他们。我什至不认为你使用的 MySQLdb 漏洞会起作用,因为脚本不会在没有 salt 的情况下传递登录凭据……必须在脚本启动时传入。但是,盐将存在于内存中的变量中。据我所知,获取此变量的唯一方法是嗅出或转储内存(或检查交换文件......但在 C 中使用 memlock 应该可以防止它被交换)。所以,这就是我想要保护的。
    • 谢谢。优点 :-) 服务器是独立的 LAMP。运行 MySQL 数据库和 Web 服务器……以及 python 脚本。它不与其他服务器通信。该计划是让用户能够通过网络登录。仍在研究该安全问题 :-) 老实说,这个问题背后有两个想法: 1. 虽然这是一个处理真实(并且非常重要)用户数据的真实服务器......这对我来说也是运行安全脚本的教育经验.
    • 2.我的安全理念是;没有单点入口是安全的(有时即使您关闭了服务器:D)。但是……安全级别越高;更好。
    • 即首先破解者必须通过门(进入服务器),然后尝试破解加密文件(并发现那里没有有用的数据),然后尝试破解内存(希望失败),然后等待用户登录所以他/她可以嗅探数据流。就像一栋真正的房子一样,安全并不在于建造诺克斯堡……但它使获取珠宝变得如此困难、令人沮丧和耗时……以至于当她/他拥有的时候,他们已经放弃或被抓住了:-) 这有意义吗?
    猜你喜欢
    • 1970-01-01
    • 2016-09-05
    • 2012-11-20
    • 1970-01-01
    • 2016-04-30
    • 1970-01-01
    • 1970-01-01
    • 2019-03-30
    • 2011-07-28
    相关资源
    最近更新 更多