PYSA:用于检测和预防Python代码中的安全问题的开源工具

2020-08-09 11:57:40

今天,我们将分享有关Pysa的详细信息,这是我们构建的一个开源静态分析工具,用于检测和防止Python代码中的安全和隐私问题。去年,我们分享了我们是如何构建Zoncolan的,这是一个静态分析工具,帮助我们分析了1亿多行黑客代码,并帮助工程师防止了数千个潜在的安全问题。这一成功激发了我们开发PYSA的灵感,PYSA是Python Static Analyzer的缩写。

Pysa是一个以安全为重点的工具,构建在我们的Python类型检查器pyre之上。它用于查看代码并分析数据如何通过它。分析数据流很有用,因为许多安全和隐私问题都可以建模为数据流向不应该的位置。

PYSA帮助我们检测广泛的问题。例如,我们使用它来检查我们的Python代码是否正确使用了某些内部框架,这些框架旨在根据技术隐私策略防止访问或泄露用户数据。PYSA还检测常见的Web应用安全问题,如XSS和SQL注入。就像Zoncolan为Hack代码所做的那样,Pysa帮助我们扩展了Python的应用程序安全努力,最引人注目的是为Instagram的服务器提供支持的代码库。

我们最大的Python代码库是为Instagram服务器提供动力的数百万行代码。像PYSA这样的自动分析器是维护此代码库中的质量和安全性的重要工具。当我们对开发人员提出的代码更改运行PYSA时,该工具在大约一个小时内提供结果,而不是手动审查可能需要几周或几个月的时间。这些快速的结果帮助我们足够快地找到并防止问题被引入到我们的代码库中。结果要么直接交给开发人员,要么交给安全工程师,这取决于检测到的问题的类型和我们对该特定问题检测的信噪比。

我们已经使Pysa开源,以及帮助它发现安全问题所需的许多定义,这样其他人就可以将该工具用于他们自己的Python代码。因为我们在自己的产品中使用开放源码的Python服务器框架,比如Django和Tornado,所以Pysa可以从第一次运行就开始发现使用这些框架的项目中的安全问题。对于我们还没有复盖的框架,使用PYSA通常就像添加几行配置来告诉PYSA数据进入服务器的位置一样简单。

我们已经使用PYSA来检测和披露开源Python项目的安全问题,比如CVE2019-19775。我们还与Zulip开源项目合作,将PYSA整合到其代码库中。

PYSA是根据从Zoncolan学到的经验开发的。它使用相同的算法执行静态分析,甚至与Zoncolan共享一些代码。与Zoncolan一样,PYSA通过程序跟踪数据流。用户定义源(重要数据的发源地)和汇点(源中的数据不应该结束的地方)。对于安全应用程序,最常见的源类型是用户控制的数据进入应用程序的位置,例如Django的HttpRequest.GET字典。接收器往往多种多样,但可以包括执行代码的API(如eval)或访问文件系统的API(如os.open)。PYSA执行迭代轮次分析以构建摘要,以确定哪些函数从源返回数据,哪些函数具有最终到达接收器的参数。如果PYSA发现源最终连接到接收器,它会报告问题。可视化此过程将创建一棵树,问题在顶点,源和汇在叶:

为了能够执行过程间分析-跟踪跨函数调用的数据流-我们需要能够将函数调用映射到它们的实现。为此,我们需要使用代码中的所有可用信息,包括可选的静态类型(如果存在)。我们使用静态类型检查器(称为pyre)来理解此信息。虽然Pysa在很大程度上依赖于pyre,并且两者共享一个存储库,但重要的是要注意,它们是独立的产品,具有不同的应用程序。

我们的安全工程师是Facebook内PYSA的主要用户。就像任何使用自动化工具进行检测的工程师一样,我们必须决定如何处理假阳性和假阴性。

当工具报告存在实际不存在的安全问题时,就会出现误报。

当工具未能检测到并报告存在真正的安全问题时,就会出现误报。

由于捕获安全问题的重要性,我们构建PYSA以避免误报并捕获尽可能多的问题。然而,减少假阴性可能需要权衡增加假阳性。过多的误报可能会导致警觉疲劳,并有可能在噪音中遗漏真正的问题。

我们在PYSA中内置了两种功能,为用户提供了消除误报的工具:消毒器和功能。

消毒器是一种直截了当的工具:在分析过程中,它们告诉PYSA在数据流通过函数或属性后完全停止跟踪。它们允许用户对他们关于转换的领域特定知识进行编码,从安全或隐私的角度来看,这些转换总是会使数据变得良性。

特性允许一种更细微的方法:特性是PYSA可以附加到数据流的一小段元数据,因为它们在整个代码中被跟踪。与消毒剂不同,添加功能并不能消除Pysa结果中的任何问题。

在完成分析之后,可以使用要素和其他元数据来过滤结果。筛选器通常是针对特定的源-接收器对编写的,以忽略数据对于特定类型的接收器(而不是针对所有类型的接收器)呈现为良性的问题。

要理解PYSA在哪里最有用,假设使用以下代码加载用户的配置文件:

#views/user.py异步def get_profile(请求:HttpRequest)->;HttpResponse:profile=load_profile(request.GET[';user_id';])...#Controller/user.py异步def load_profile(user_id:str):user=load_user(User_Id)#安全加载用户;无SQL注入图片=load_pictures(user.id)...#model/media.py异步定义load_pictures(user_id:str):query=f";";";SELECT*FROM PICTICES WHERE user_id={user_id}";";";RESULT=RUN_QUERY(查询)...#model/shared.py异步定义RUN_QUERY(QUERY:STR):CONNECTION=CREATE_SQL_CONNECTION()RESULT=等待连接。EXECUTE(查询)...。

按照目前的情况,Load_Pictures中潜在的SQL注入是不可利用的,因为该函数将只接收通过调用load_profile函数中的load_user而产生的有效user_id。正确配置后,PYSA可能不会在此处报告问题。

现在,假设一位在应用程序的控制器层工作的进取工程师意识到并发获取用户和图片数据可以更快地返回结果:

#Controller/user.py Async def load_profile(user_id:str):user,pictures=await asyncio.ather(load_user(User_Id),load_pictures(User_Id)#不再';user.id';!)...。

此更改看起来可能无关紧要,但实际上最终将用户控制的user_id字符串直接连接到load_pictures中的SQL注入问题。但是,在入口点和数据库查询之间有许多层的大型应用程序中,该工程师可能永远不会意识到数据完全由用户控制,或者SQL注入问题潜伏在所调用的函数之一中。然而,这正是PYSA设计要知道的。如果工程师在Instagram上提出了这样的更改,Pysa可以检测到有数据从用户控制的输入一路流向SQL查询,并标记出问题。

从根本上说,没有办法构建完美的静态分析器。PYSA在解决与数据流相关的安全问题时所做的选择,以及在精确度和准确性方面权衡性能的设计决策,都存在一定的局限性。Python作为一种动态语言,具有独特的特性,这些特性构成了某些设计决策的基础。

PYSA的构建目的是仅发现与数据流相关的安全问题,但并不是所有的安全或隐私问题都可以建模为数据流。请考虑以下示例代码:

PYSA不是确保授权检查(如USER_IS_ADMIN)在特权操作(如DELETE_USER)之前触发的正确工具。PYSA可以检测来自request.GET的数据流入DELETE_USER,但是该数据不会通过USER_IS_ADMIN检查。可以重写此代码以使问题可由PYSA建模,或者通过在管理DELETE_USER操作中嵌入权限检查来使其更安全,但它用于表明PYSA不能检测所有形式的安全问题。

我们必须做出设计决策,这样Pysa才能在开发人员提议的更改落地之前完成分析。例如,当PYSA跟踪进入对象的太多属性的数据流时,它有时必须简化整个对象并将其视为包含该数据,这可能会导致误报。

开发时间也是一个资源限制,这使得我们不得不权衡我们所支持的Python特性。例如,PYSA在调用函数时还没有在调用图中包含修饰符,因此可能会遗漏修饰符中出现的问题。

使Python成为如此灵活的语言的一些特性也使得静态分析变得更加困难。例如,在没有类型信息的情况下,很难通过方法调用跟踪数据流。在下面的代码中,不可能知道调用了Fly的哪个实现:

类鸟:定义苍蝇(自我):...类别飞机:定义飞行(自我):...Def Take_Off(X):x.Fly()#此函数调用哪个函数?

这并不是说Pysa不能在非类型化代码上运行;它可以而且已经在完全无类型化的项目中发现了安全问题,但是需要做一些工作来增加重要类型的覆盖范围。

Def ret_eval(request:HttpRequest):os=import lib.import_module(";os";)#Pysa Won';不知道';os';是什么,因此无法';t#捕获此远程代码执行问题os.system(request.GET[";command";])。

这段代码中有一个代码执行漏洞,但是Pysa不会捕获它,因为os模块是动态导入的,而pysa没有意识到这就是本地os变量所代表的。Python允许您在任何时候动态导入几乎任何代码。Python还允许您更改对几乎任何对象的函数调用将执行的操作。虽然可以更新Pysa以解析上面示例中的“os”字符串并检测问题,但是Python的动态性意味着有无数的病态数据流示例是Pysa无法检测到的。

PYSA帮助安全工程师检测代码库中的现有问题,并防止通过提议的代码更改引入新问题。在2020年上半年,PYSA检测到了我们的工程师在Instagram服务器代码库中发现的44%的问题。

在所有漏洞类型中,PYSA在建议的代码更改中检测到330个独特的问题,我们根据严重程度对这些问题进行了分类。总体而言,49个(15%)是重大问题,其余131个(40%)结果是真实的,但情况较轻,使其不那么严重。即使PYSA偏向于避免假阴性,并且我们愿意接受大量的假阳性,我们仍然设法将假阳性限制在所报告问题的150(45%)。

我们定期检查通过其他途径报告的问题,例如我们的错误赏金计划,以确保我们纠正了任何假阴性。针对一种漏洞类型的每个检测都是可调的。通过不断改进,安全工程师能够将更精细的类型报告为100%有效的问题。

总体而言,我们对与PYSA在帮助安全工程师扩展方面所做的权衡感到满意,但总有改进的空间。多亏了安全工程师和软件工程师之间的密切合作,我们构建了PYSA以实现持续改进。这使我们能够快速迭代并构建一个适合我们需求的工具,这是我们用现成的解决方案永远做不到的。这种合作已经对PYSA的工作方式进行了补充和改进。例如,它帮助我们改进了浏览问题跟踪的方式,从而更容易识别问题何时为假阳性。

Pysa正在追随Zoncolan的脚步,允许我们自动检测Python代码中的安全问题。它使我们能够检测我们自己的代码和开放源码项目中的安全问题。PYSA是免费的、开放源码的,任何人都可以按照文档和教程开始分析他们的Python代码。我们希望您能试一试!建造Pysa并将其投入使用是一项巨大的事业。感谢所有这些人的贡献:马克西姆·阿尔托,Apurv Bhargava,Jia Chen,Manuel Fahndrich,Lorenzo Fontana,Dominik Gabi,Nolan Alexander Jimenez,Zack Landau,Mark Mdoza,Eray Mitrani,Ibrahim Mohamed,Maggie Moss,Radu Nesiu,Nicholas O‘Brien,Edward Chu,PraDeep Kumar Srinivasan和Shannon