通过反编译公共存储库中的Python字节码来查找机密

2020-05-31 11:08:08

TL;DR:缓存统治着我周围的一切。PYC文件可以包含机密,不应签入到源代码管理。使用标准的Python.gitignore。

当您第一次导入Python文件时,Python解释器将对其进行编译,并将结果字节码缓存到.pyc文件中,以便后续导入不必处理再次解析或编译代码的开销。

对于Python项目来说,将配置、密钥和密码(统称为“秘密”)存储在一个名为诸如Secrets.py、config.py或settings.py之类的巨型Python文件中也是一种常见的做法,项目的其他部分会导入这些文件。这在秘密和签入的源代码之间提供了很好的分离,而且在大多数情况下,这种设置工作得很好。而且因为它重用了该语言的导入机制,所以这些项目不必为文件I/O或JSON之类的格式而大惊小怪。

但是出于同样的原因,该模式既快速又方便,也存在潜在的不安全因素。因为它重用了该语言的导入机制(习惯于创建和缓存.pyc文件),所以这些秘密也存在于编译后的字节码中!使用GitHub API进行的一些初步研究显示,数千个GitHub存储库在其字节码中隐藏着秘密。

现有的在存储库中查找秘密的工具(我最喜欢的是trufflehog)跳过诸如.pyc文件之类的二进制文件,而只扫描诸如源代码或配置文件之类的纯文本文件。

早期版本的Python将这些文件存储在原始源文件旁边,但是从Python3.2开始,这些文件都位于导入模块根目录下名为__pycache__的文件夹中。

请注意,变量名和字符串是完整复制的!此外,事实证明,Python字节码通常包含足够的信息来恢复代码的原始结构。像uncompyle6这样的工具可以将.pyc文件转换回其原始形式。*大部分时间。

$uncompyle6 Secrets.cpython-38.pyc#uncompyle6版本3.6.7#Python字节码3.8(3413)#反编译自:Python 3.8.2(默认,2020年4月8日,14:31:25)#[GCC 9.3.0]#嵌入式文件名:Secrets.py#编译时间:2020-05-12 17:16:29#源模块大小2**32:34字节SECRET_KEY=';绿蛋和火腿'

为了调查这个问题到底有多普遍,我编写了一个简短的脚本来搜索GitHub以查找.pyc文件,并对其进行反编译以查找秘密。我最终找到了数千个推特密钥、条纹令牌、AWS凭证和社交媒体密码。我通知了所有我以这种方式找到钥匙的组织。

导入base64导入io导入os导入临时文件从GitHub导入uncompyle6导入Github GitHub_Key=os。环境。get(";GitHub_Key";)g=Github(GitHub_Key)Items=g。项目中项目的搜索代码(";文件名:Secrets.pyc&34;):打印(f";反编译repo https://github.com/{item.repository.full_name}";)打印(f";所有者类型:{item.repository.owner.type}";)尝试:内容=base64。b64decode(项目。内容)和临时文件。NamedTemporaryFile(后缀=";.pyc";)作为f:f。写(内容)f.。查找(0)out=io。StringIO()不兼容6.。反编译_文件(f.。姓名,出局)出局。查找(0)打印(输出。Read())除e例外:打印(E)打印(f";无法反编译repo https://github.com/{item.repository.full_name}";)继续打印(";\n\n\n";)。

这篇文章附带了一个小型的捕获旗帜风格的实验室,让你自己尝试这种风格的攻击。

缓存字节码是一种低级的内部性能优化,这是Python应该让我们不必考虑的事情!如果没有像反汇编程序或反编译器这样的特殊工具,.pyc文件的内容是难以理解的。当这些文件被隐藏在__pycache__内(双下划线信号“禁止进入;仅限内部使用”)时,很容易忽略它们。许多文本编辑器和IDE将这些文件夹和文件从源代码树中隐藏起来,以避免弄乱屏幕,甚至很容易忘记它们的存在。

也就是说,对于一个有经验的程序员来说,很容易意外地提交他们的秘密,而且几乎可以保证初学者会犯这个错误。要避免这种情况,要么需要运气好的gitignore,要么需要对git和Python内部有一般的了解。

如果您有.pyc文件,并且其中包含机密,则撤销并轮换您的机密