Amazon S3直接从客户端浏览器上传文件 – 私钥泄露
我正在实现从客户端机器通过REST API直接上传到Amazon S3,只使用JavaScript,没有任何服务器端代码。 一切正常,但有一件事让我担心…
当我向Amazon S3 REST API发送请求时,我需要签署请求并将签名放入Authentication
标头中。 要创build签名,我必须使用我的密钥。 但所有的事情都发生在客户端,所以,密钥可以很容易从页面源显示(即使我混淆/encryption我的来源)。
我该如何处理? 这是一个问题呢? 也许我可以限制特定的私钥使用仅限于来自特定CORS Origin的REST API调用,并且仅限于PUT和POST方法,或者可能只链接到S3和特定存储桶的密钥? 可能有另一种authentication方法?
“无服务器”解决scheme是理想的,但我可以考虑涉及一些服务器端处理,不包括上传文件到我的服务器,然后发送到S3。
我想你想要的是基于浏览器的使用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 – 而不是通过您的服务器。
每当您的用户想要上传文件时,您都需要在服务器上创buildPOLICY
和SIGNATURE
。 您将该页面返回给用户的浏览器。 用户可以直接上传文件到S3,而无需通过服务器。
在签署策略时,通常会在几分钟后使策略过期。 这会强制用户在上传之前与您的服务器通话。 这可以让你监视和限制上传,如果你的愿望。
去往或来自服务器的唯一数据是签名的URL。 您的密钥在服务器上保密。
您可以通过AWS S3 Cognito来尝试此链接:
http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/browser-examples.html#Amazon_S3
也试试这个代码
只需更改区域,IdentityPoolId和您的存储桶名称
<!DOCTYPE html> <html> <head> <title>AWS S3 File Upload</title> <script src="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>
你说你想要一个“无服务器”的解决scheme。 但是这意味着你没有能力把任何“你的”代码放在循环中。 (注意:一旦将代码提供给客户端,现在就是“他们的”代码。)lockingCORS不会有帮助:人们可以轻松地编写一个非基于Web的工具(或基于Web的代理)正确的CORS头滥用您的系统。
最大的问题是你不能区分不同的用户。 您不能允许一个用户列出/访问他的文件,但是阻止他人这样做。 如果您发现了滥用行为,除了更改密钥外,您无能为力。 (攻击者可以想象得到哪一个。)
你最好的select是用你的javascript客户端的键创build一个“IAM用户”。 只给它只写一个桶的权限。 (但理想情况下,不要启用ListBucket操作,这将使攻击者更有吸引力)。
如果你有一台服务器(即使是20美元/月的简单微型实例),也可以在服务器上签名,同时实时监控/防止滥用。 如果没有服务器,最好的办法是定期监视事后的滥用情况。 这是我会做的:
1)定期轮换该IAM用户的密钥:每天晚上为该IAM用户生成一个新密钥,并更换最早的密钥。 由于有2个密钥,每个密钥有效期为2天。
2)启用S3日志logging,每小时下载一次日志。 在“上传次数过多”和“下载量过多”上设置警报。 您将要检查总文件大小和上传的文件数量。 而且,您还需要监视全局总数以及每个IP地址总数(具有较低的阈值)。
这些检查可以“无服务器”完成,因为您可以在桌面上运行它们。 (例如S3完成所有的工作,这些stream程就在那里提醒你滥用你的S3存储桶,所以在本月底你不会得到巨额的AWS账单。)
要创build签名,我必须使用我的密钥。 但所有的事情都发生在客户端,所以,密钥可以很容易从页面源显示(即使我混淆/encryption我的来源)。
这是你误解的地方。 使用数字签名的原因是,您可以validation某些内容是正确的,而不会泄露您的密钥。 在这种情况下,数字签名被用来防止用户修改您为表单post设置的策略。
这里的数字签名被用于networking安全。 如果有人(国家安全局?)真的能够打破他们,他们将有比你的S3桶更大的目标:)
为接受的答案添加更多信息,可以使用AWS签名版本4引用我的博客以查看正在运行的代码版本。
这里总结一下:
只要用户select要上传的文件,请执行以下操作:1.拨打Web服务器以启动服务以生成所需的参数
-
在此服务中,拨打AWS IAM服务获得临时信用
-
一旦你有了信誉,创build一个桶策略(基64编码的string)。 然后使用临时秘密访问密钥签署存储桶策略以生成最终签名
-
将必要的参数发送回UI
-
一旦收到,创build一个html表单对象,设置所需的参数并将其发布。
有关详细信息,请参阅https://wordpress1763.wordpress.com/2016/10/03/browser-based-upload-aws-signature-version-4/
如果您没有任何服务器端代码,您的安全性取决于客户端对JavaScript代码的访问的安全性(也就是说每个拥有代码的人都可以上传)。
所以我build议,只需创build一个公共可写(但不可读)的特殊S3存储桶,因此您不需要客户端上的任何签名组件。
存储桶名称(例如GUID)将是您唯一防御恶意上传的防御(但潜在的攻击者无法使用您的存储桶来传输数据,因为它只写给他)
如果您愿意使用第三方服务,则auth0.com支持此集成。 auth0服务交换第三方SSO服务身份validation的AWS临时会话令牌将有限的权限。
请参阅: https : //github.com/auth0-samples/auth0-s3-sample/
和auth0文档。
我已经给出了一个简单的代码来从JavaScript浏览器上传文件到AWS S3,并列出S3存储桶中的所有文件。
脚步:
-
要知道如何创build创buildIdentityPoolId http://docs.aws.amazon.com/cognito/latest/developerguide/identity-pools.html
-
转到S3的控制台页面,并从桶属性打开Corsconfiguration,并写入以下XML代码。
<?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedMethod>GET</AllowedMethod> <AllowedMethod>PUT</AllowedMethod> <AllowedMethod>DELETE</AllowedMethod> <AllowedMethod>HEAD</AllowedMethod> <AllowedHeader>*</AllowedHeader> </CORSRule> </CORSConfiguration>
-
创build包含以下代码的HTML文件更改凭据,在浏览器中打开文件并享受。
<script type="text/javascript"> AWS.config.region = 'ap-north-1'; // Region AWS.config.credentials = new AWS.CognitoIdentityCredentials({ IdentityPoolId: 'ap-north-1:qb4g-cn3esdf7wbdw', }); var bucket = new AWS.S3({ params: { Bucket: 'MyBucket' } }); var fileChooser = document.getElementById('file-chooser'); var button = document.getElementById('upload-button'); var results = document.getElementById('results'); function upload() { var file = fileChooser.files[0]; console.log(file.name); if (file) { results.innerHTML = ''; var params = { Key: n + '.pdf', ContentType: file.type, Body: file }; bucket.upload(params, function(err, data) { results.innerHTML = err ? 'ERROR!' : 'UPLOADED.'; }); } else { results.innerHTML = 'Nothing to upload.'; } } </script> <body> <input type="file" id="file-chooser" /> <input type="button" onclick="upload()" value="Upload to S3"> <div id="results"></div> </body>
-