从客户端浏览器直接上传 Amazon S3 文件 - 私钥泄露

IT技术 javascript amazon-web-services authentication amazon-s3
2021-01-21 22:19:23

我仅使用 JavaScript 通过 REST API 实现从客户端计算机到 Amazon S3 的直接文件上传,无需任何服务器端代码。一切正常,但有一件事让我担心......

当我向 Amazon S3 REST API 发送请求时,我需要对请求进行签名并将签名放入Authentication标头中。要创建签名,我必须使用我的秘密密钥。但是所有事情都发生在客户端,因此,可以轻松地从页面源中泄露密钥(即使我混淆/加密了我的源)。

我该如何处理?这真的是个问题吗?也许我可以将特定私钥的使用限制为仅来自特定 CORS 源的 REST API 调用以及仅 PUT 和 POST 方法,或者仅将链接密钥链接到 S3 和特定存储桶?可能还有其他身份验证方法吗?

“无服务器”解决方案是理想的,但我可以考虑涉及一些服务器端处理,不包括将文件上传到我的服务器然后发送到 S3。

6个回答

我认为您想要的是使用 POST 的基于浏览器的上传。

基本上,您确实需要服务器端代码,但它所做的只是生成已签名的策略。一旦客户端代码具有签名策略,它就可以使用 POST 直接上传到 S3,而无需数据通过您的服务器。

这是官方文档链接:

图表:http : //docs.aws.amazon.com/AmazonS3/latest/dev/UsingHTTPPOST.html

示例代码:http : //docs.aws.amazon.com/AmazonS3/latest/dev/HTTPPOSTExamples.html

签署的政策将以如下形式出现在您的 html 中:

<html>
  <head>
    ...
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    ...
  </head>
  <body>
  ...
  <form action="http://johnsmith.s3.amazonaws.com/" method="post" enctype="multipart/form-data">
    Key to upload: <input type="input" name="key" value="user/eric/" /><br />
    <input type="hidden" name="acl" value="public-read" />
    <input type="hidden" name="success_action_redirect" value="http://johnsmith.s3.amazonaws.com/successful_upload.html" />
    Content-Type: <input type="input" name="Content-Type" value="image/jpeg" /><br />
    <input type="hidden" name="x-amz-meta-uuid" value="14365123651274" />
    Tags for File: <input type="input" name="x-amz-meta-tag" value="" /><br />
    <input type="hidden" name="AWSAccessKeyId" value="AKIAIOSFODNN7EXAMPLE" />
    <input type="hidden" name="Policy" value="POLICY" />
    <input type="hidden" name="Signature" value="SIGNATURE" />
    File: <input type="file" name="file" /> <br />
    <!-- The elements after this will be ignored -->
    <input type="submit" name="submit" value="Upload to Amazon S3" />
  </form>
  ...
</html>

请注意 FORM 操作是将文件直接发送到 S3 - 而不是通过您的服务器。

每次您的用户想要上传文件时,您都会在服务器上创建POLICYSIGNATURE您将页面返回到用户的浏览器。然后,用户可以将文件直接上传到 S3,而无需通过您的服务器。

签署策略时,您通常会使策略在几分钟后过期。这会强制您的用户在上传之前与您的服务器通话。这使您可以根据需要监控和限制上传。

传入或传出服务器的唯一数据是签名 URL。您的密钥在服务器上保密。

@Trip 由于浏览器正在将文件发送到 S3,您需要检测 Javascript 中的超时并自己启动重试。
2021-03-14 22:19:23
确保添加${filename}到键名,所以对于上面的例子,user/eric/${filename}而不是仅仅user/eric. 如果user/eric是一个已经存在的文件夹,则上传将无声地失败(您甚至会被重定向到 success_action_redirect)并且上传的内容将不存在。只是花了几个小时调试这个,认为这是一个权限问题。
2021-03-16 22:19:23
@secretmike 如果您因执行此方法而超时,您建议如何绕过它?
2021-04-06 22:19:23
@secretmike 闻起来像一个无限循环。由于超时将无限期地重复超过 10/mbs 的任何文件。
2021-04-08 22:19:23
请注意,这使用了很快将被 v4 取代的 Signature v2:docs.aws.amazon.com/AmazonS3/latest/API/...
2021-04-12 22:19:23

您可以通过 AWS S3 Cognito 执行此操作,请在此处尝试此链接:

http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/browser-examples.html#Amazon_S3

也试试这个代码

只需更改 Region、IdentityPoolId 和您的存储桶名称

<!DOCTYPE html>
<html>

<head>
    <title>AWS S3 File Upload</title>
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.12.min.js"></script>
</head>

<body>
    <input type="file" id="file-chooser" />
    <button id="upload-button">Upload to S3</button>
    <div id="results"></div>
    <script type="text/javascript">
    AWS.config.region = 'your-region'; // 1. Enter your region

    AWS.config.credentials = new AWS.CognitoIdentityCredentials({
        IdentityPoolId: 'your-IdentityPoolId' // 2. Enter your identity pool
    });

    AWS.config.credentials.get(function(err) {
        if (err) alert(err);
        console.log(AWS.config.credentials);
    });

    var bucketName = 'your-bucket'; // Enter your bucket name
    var bucket = new AWS.S3({
        params: {
            Bucket: bucketName
        }
    });

    var fileChooser = document.getElementById('file-chooser');
    var button = document.getElementById('upload-button');
    var results = document.getElementById('results');
    button.addEventListener('click', function() {

        var file = fileChooser.files[0];

        if (file) {

            results.innerHTML = '';
            var objKey = 'testing/' + file.name;
            var params = {
                Key: objKey,
                ContentType: file.type,
                Body: file,
                ACL: 'public-read'
            };

            bucket.putObject(params, function(err, data) {
                if (err) {
                    results.innerHTML = 'ERROR: ' + err;
                } else {
                    listObjs();
                }
            });
        } else {
            results.innerHTML = 'Nothing to upload.';
        }
    }, false);
    function listObjs() {
        var prefix = 'testing';
        bucket.listObjects({
            Prefix: prefix
        }, function(err, data) {
            if (err) {
                results.innerHTML = 'ERROR: ' + err;
            } else {
                var objKeys = "";
                data.Contents.forEach(function(obj) {
                    objKeys += obj.Key + "<br>";
                });
                results.innerHTML = objKeys;
            }
        });
    }
    </script>
</body>

</html>

更多详情,请查看 - Github
这应该是正确答案@Olegas
2021-03-27 22:19:23
这是否支持多个图像?
2021-03-30 22:19:23
@ user2722667 是的,确实如此。
2021-04-07 22:19:23
@Joomler 嗨,谢谢,但我在 firefox RequestTimeout 上遇到了这个问题 您到服务器的套接字连接在超时期限内未读取或写入。空闲连接将关闭,文件不会在 S3 上上传。你能帮我解决这个问题吗?谢谢
2021-04-11 22:19:23
@usama 你能不能在 github 中打开这个问题,因为我不清楚这个问题
2021-04-12 22:19:23

您是说您想要一个“无服务器”解决方案。但这意味着您无法将任何“您的”代码放入循环中。(注意:一旦您将代码提供给客户,它现在就是“他们的”代码。)锁定 CORS 无济于事:人们可以轻松编写一个非基于 Web 的工具(或基于 Web 的代理)来添加正确的 CORS 标头滥用您的系统。

最大的问题是您无法区分不同的用户。你不能允许一个用户列出/访问他的文件,但阻止其他人这样做。如果您发现滥用行为,除了更改密钥外,您无能为力。(攻击者大概可以再次获得。)

最好的办法是使用 JavaScript 客户端的密钥创建“IAM 用户”。只授予它对一个存储桶的写访问权限。(但理想情况下,不要启用 ListBucket 操作,这会使其对攻击者更具吸引力。)

如果您有一台服务器(即使是一个简单的微型实例,每月 20 美元),您就可以在服务器上签署密钥,同时实时监控/防止滥用。如果没有服务器,您能做的最好的事情就是在事后定期监控滥用情况。这是我会做的:

1) 定期轮换该 IAM 用户的密钥:每天晚上,为该 IAM 用户生成一个新密钥,并替换最旧的密钥。由于有 2 个密钥,每个密钥的有效期为 2 天。

2)开启S3日志,每小时下载一次日志。设置“上传过多”和“下载过多”的警报。您需要检查总文件大小和上传的文件数量。并且您将需要监视全局总数和每个 IP 地址的总数(具有较低的阈值)。

这些检查可以“无服务器”完成,因为您可以在桌面上运行它们。(即S3完成所有的工作,这些过程就在那里,提醒您滥用您的S3存储的,这样你就不会得到一个巨人在月底AWS账单。)

天啊,我忘记了在 Lambda 之前事情是多么复杂。
2021-03-24 22:19:23

在接受的答案中添加更多信息,您可以参考我的博客以查看代码的运行版本,使用 AWS 签名版本 4。

将在这里总结:

一旦用户选择了要上传的文件,请执行以下操作: 1. 调用 Web 服务器以启动服务以生成所需的参数

  1. 在此服务中,调用 AWS IAM 服务以获取临时凭证

  2. 获得信用后,创建一个存储桶策略(base 64 编码字符串)。然后用临时秘密访问密钥签署桶策略以生成最终签名

  3. 将必要的参数发送回 UI

  4. 收到后,创建一个 html 表单对象,设置所需的参数并发布它。

有关详细信息,请参阅 https://wordpress1763.wordpress.com/2016/10/03/browser-based-upload-aws-signature-version-4/

顺便说一句,超级代理有严重的 CORS 问题,所以 xmlhttprequest 似乎是目前唯一合理的方法
2021-03-26 22:19:23
我花了一整天的时间试图用 Javascript 来解决这个问题,这个答案告诉我如何使用 XMLhttprequest 来做到这一点。我很惊讶你被否决了。OP 要求使用 javascript 并在推荐的答案中获得表单。好伤心。感谢您的回答!
2021-04-11 22:19:23

要创建签名,我必须使用我的秘密密钥。但是所有事情都发生在客户端,因此,可以轻松地从页面源中泄露密钥(即使我混淆/加密了我的源)。

这就是你误会的地方。使用数字签名的真正原因是,您可以在不泄露密钥的情况下验证某些内容是否正确。在这种情况下,数字签名用于防止用户修改您为表单发布设置的策略。

诸如此处的数字签名用于整个网络的安全性。如果有人(NSA?)真的能够破解它们,他们的目标将比您的 S3 存储桶大得多 :)

但机器人可能会尝试快速上传无限文件。我可以设置每个存储桶最大文件数的策略吗?
2021-03-29 22:19:23