Pysa:如何避免Python代码中的安全性问题



8月7日,Facebook推出了Pysa,这是一种以开源安全性为重点的静态分析器,可帮助您处理数百万个Instagram字符串。揭示了局限性,触及了设计解决方案,当然还有帮助避免误报的手段。当Pysa最有用,并且分析仪不适用的代码时,将显示这种情况。详情来自Facebook Engineering博客。



去年,我们撰写了有关如何构建Zoncolan的文章Zoncolan是一种静态分析工具,可以分析超过1亿行Hack代码,并可以帮助工程师预防成千上万的潜在安全问题。成功启发了Pysa -Python静态分析器。该解析器基于Facebook的Python类型检查工具Pyre构建。 Pysa使用代码处理数据流。数据流分析非常有用,因为通常将安全性和隐私问题建模为数据去向不宜的地方。



Pysa可帮助您确定许多类型的问题。分析器根据技术隐私政策检查代码是否正确使用某些内部结构来防止访问或泄露用户数据。此外,分析器还检测常见的Web应用程序安全问题,例如XSS和SQL注入。与Zoncolan一样,新工具也有助于扩大Python应用程序的安全性。对于Instagram尤其如此。



Pysa在Instagram上



Facebook上最大的Python存储库是Instagram服务器上的数百万行。当Pysa在开发人员建议的代码更改上运行时,它可以在大约一个小时内提供结果,而不是手动检查可能需要花费数周或数月的时间。这可以帮助您足够迅速地发现和预防问题,以免它不会进入您的代码库。检查结果将直接发送给开发人员或安全工程师,具体取决于问题的类型和特定情况下的信噪比。



Pysa和开源



Pysa源代码许多问题定义可供其他开发人员用来分析其项目的代码。我们使用诸如DjangoTornado之类的开放源代码服务器端框架,因此Pysa从Facebook内部的首次发布开始就发现了使用这些框架的项目中的安全性问题。对于尚未覆盖的框架使用Pysa通常就像添加几行配置一样容易。您只需要告诉分析器数据从何处到达服务器。



Pysa已用于检测开源Python项目中的问题,例如CVE-2019-19775我们还与Zulip项目合作 并将Pysa包含在其代码库中。



怎么运行的?



Pysa的设计汲取了Zoncolan的经验教训。它使用相同的算法执行静态分析,甚至与Zoncolan共享代码。与Zoncolan一样,Pysa监视程序中的数据流。用户定义重要数据的来源和数据来源。在安全应用程序中,最常见的来源是用户控制的数据进入应用程序的位置,例如Django中HttpRequest.GET字典。接收器通常变化更多,并且可以包括执行API。例如,evalos.open... Pysa反复进行分析,以建立摘要,以确定哪些函数从源返回数据,以及哪些函数到达目的地。当分析仪检测到信号源最终正在连接到接收器时,它将报告该问题。该过程的可视化是一棵树,该树的顶部有问题,叶子中的源和流是:







要执行跨过程分析(以跟踪函数调用之间的数据流),您需要能够将函数调用映射到其实现。为此,您需要使用代码中的所有可用信息,包括可选的静态类型(如果有)。我们与Pyre合作以找出此信息。尽管Pysa严重依赖Pyre,并且两个工具共享同一个存储库,但必须注意,这些是具有独立应用程序的独立产品。 



误报



安全工程师是Facebook上Pysa的主要用户。像任何使用自动错误检测工具的工程师一样,我们必须找出如何处理误报(无问题,无信号)和否定(无问题,无信号)的方法。



Pysa的设计旨在避免忽视问题并尽可能地检测出实际问题。但是,减少错误警报的数量可能需要权衡取舍,从而增加不必要的警报的数量。过多的误报会导致焦虑疲劳,并有可能在噪音中忽略实际问题的风险。 Pysa有两种去除有害信号的工具:消毒剂和标牌。



消毒液是一个简单的工具。它告诉解析器在数据流通过函数或属性后不跟随数据流。消毒程序允许您对域转换知识进行编码,该知识始终以安全和机密的方式呈现数据。



符号是微妙的:它们是Pysa在跟踪时附加到数据流的一小部分元数据。与消毒剂不同,标志不能消除分析结果中的问题。属性和其他元数据可用于在分析后过滤结果。通常,已针对特定的源-目的地对编写过滤器,以在针对特定类型(但不是所有类型)的目的地处理了数据时忽略问题。



要了解Pysa在哪种情况下最有用,请想象下面的代码运行以加载用户配置文件:



# views/user.py
async def get_profile(request: HttpRequest) -> HttpResponse:
   profile = load_profile(request.GET['user_id'])
   ...
 
# controller/user.py
async def load_profile(user_id: str):
   user = load_user(user_id) # Loads a user safely; no SQL injection
   pictures = load_pictures(user.id)
   ...
 
# model/media.py
async def load_pictures(user_id: str):
   query = f"""
      SELECT *
      FROM pictures
      WHERE user_id = {user_id}
   """
   result = run_query(query)
   ...
 
# model/shared.py
async def run_query(query: str):
   connection = create_sql_connection()
   result = await connection.execute(query)
   ...


这是在load_pictures潜在的SQL注入不能被利用:此功能总是得到有效user_id的功能load_userload_profile如果配置正确,Pysa可能不会报告问题。现在想象一下,一个进取的工程师编写了控制器级代码,就会意识到同时获取用户数据和图像可以更快地返回结果:



# controller/user.py
async def load_profile(user_id: str):
   user, pictures = await asyncio.gather(
       load_user(user_id),
       load_pictures(user_id) # no longer 'user.id'!
   )
   ...


所做的更改可能看起来无害,但实际上最终将用户控制的字符串user_id与中的SQL注入问题合并在一起load_pictures在入口点和数据库查询之间具有许多层的应用程序中,工程师可能不会意识到数据完全由用户控制,或者注入问题隐藏在调用的函数中。这正是分析器编写的情况。当工程师在Instagram上提出类似的更改时,Pysa发现数据正在从用户驱动的输入传递到SQL查询,并报告问题。



分析仪限制



编写完美的静态分析器是不可能的Pysa在范围,数据流和设计决策方面存在局限性,从而影响了精度和准确性的性能。作为一种动态语言的Python具有独特的特征,这些特征是其中一些设计决策的基础。



问题空间



Pysa被构建为仅检测与数据流相关的安全问题。并非所有安全性或隐私问题都被建模为数据流。看看一个例子:



def admin_operation(request: HttpRequest):
  if not user_is_admin():
      return Http404
 
  delete_user(request.GET["user_to_delete"])


Pysa不是确保user_is_admin特权操作之前运行授权检查的正确工具delete_user仪可通过检测数据request.GET指向delete_user,但数据永远不会通过验证进入user_is_admin您可以重写代码以使问题成为Pysa建模的,或者可以将权限检查构建到管理操作中delete_user但是,这段代码首先显示了Pysa无法解决的问题。



资源限制



我们对约束进行了设计决策,以便Pysa可以在开发人员提出的更改纳入代码库之前完成分析。当分析器监视对象的太多属性中的数据流时,有时您必须简化并将整个对象视为恰好包含该数据。这可能导致误报。



另一个限制是开发时间。它在支持哪些Python功能上做出了折衷。当调用函数时,Pysa尚未在调用图中包括装饰器,因此跳过了装饰器内部的问题。 



Python作为一种动态语言



Python的灵活性使静态分析变得困难。没有类型信息的方法调用很难跟踪数据流。在下面的代码中,无法确定fly调用哪个实现



class Bird:
  def fly(self): ...
 
class Airplane:
  def fly(self): ...
 
def take_off(x):
  x.fly()  # Which function does this call?


分析仪可用于完全无类型的项目。但是,只需花很少的精力就可以覆盖重要的类型。



Python的动态特性强加了另一个限制。见下文:



def secret_eval(request: HttpRequest):
  os = importlib.import_module("os")
 
  # Pysa won't know what 'os' is, and thus won't
  # catch this remote code execution issue
  os.system(request.GET["command"])


执行漏洞在这里清晰可见,但是分析器将跳过它。该模块是os动态导入的。Pysa不能理解局部变量os恰好表示模块osPython使您可以随时动态导入几乎所有代码。此外,该语言可以更改几乎所有对象的函数调用行为。Pysa可以学习分析操作系统并检测问题。但是Python的动态性意味着存在分析器看不到的无尽病理数据流示例。



结果



2020年上半年,Pysa占了Instagram检测到的所有问题的44%。在所有漏洞类型中,建议的代码更改中发现了330个独特的问题。事实证明,有49个问题(占15%)是很重要的问题,其中131个问题(占40%)是真实的,但有缓解措施。在150(45%)例中记录了假阴性。



我们会定期审查以其他方式报告的问题。例如,通过Bug Bounty程序。这就是我们确保纠正所有错误负信号的方式。每种类型的漏洞的检测都是可配置的。通过不断的完善,安全工程师已将其转移到更好的类型,以100%的时间报告实际问题。



总体而言,我们对为帮助安全工程师扩展规模所做的权衡感到满意。但是总会有发展的空间。我们创建Pysa的目的是通过安全工程师和程序员之间的密切协作来不断提高代码质量。这使我们能够快速迭代并创建一个比任何现成的解决方案都更好地满足我们需求的工具。工程师的合作导致Pysa机芯的增加和改进。例如,您查看问题跟踪的方式已更改。现在更容易看到误报。



Pysa分析器文档教程

图片


通过在线SkillFactory课程,了解如何从头开始或在技能和薪资水平上获得高知名度的职业的详细信息:





E







All Articles